@@ -2,6 +2,7 @@ package app
22
33import (
44 "context"
5+ "fmt"
56 "strings"
67
78 tea "github.com/charmbracelet/bubbletea"
@@ -23,7 +24,30 @@ func (m Model) loadContexts() tea.Cmd {
2324}
2425
2526func (m Model ) updateContextPicker (msg tea.KeyMsg ) (tea.Model , tea.Cmd ) {
26- switch msg .String () {
27+ key := msg .String ()
28+
29+ // If filter is active, handle text input
30+ if m .ctxFilterActive {
31+ switch key {
32+ case "esc" :
33+ m .ctxFilterActive = false
34+ case "enter" :
35+ m .ctxFilterActive = false
36+ case "backspace" :
37+ if len (m .ctxFilterInput ) > 0 {
38+ m .ctxFilterInput = m .ctxFilterInput [:len (m .ctxFilterInput )- 1 ]
39+ m .applyCtxFilter ()
40+ }
41+ default :
42+ if len (key ) == 1 {
43+ m .ctxFilterInput += key
44+ m .applyCtxFilter ()
45+ }
46+ }
47+ return m , nil
48+ }
49+
50+ switch key {
2751 case "q" :
2852 m .quitting = true
2953 return m , tea .Quit
@@ -32,6 +56,8 @@ func (m Model) updateContextPicker(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
3256 // If initial launch, quit.
3357 if m .cfg .ContextName != "" {
3458 m .screen = m .ctxPrevScreen
59+ m .ctxFilterInput = ""
60+ m .filteredCtxList = m .ctxList
3561 } else {
3662 m .quitting = true
3763 return m , tea .Quit
@@ -41,12 +67,14 @@ func (m Model) updateContextPicker(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
4167 m .ctxIdx --
4268 }
4369 case "down" , "j" :
44- if m .ctxIdx < len (m .ctxList )- 1 {
70+ if m .ctxIdx < len (m .filteredCtxList )- 1 {
4571 m .ctxIdx ++
4672 }
73+ case "/" :
74+ m .ctxFilterActive = true
4775 case "enter" :
48- if len (m .ctxList ) > 0 && m .ctxIdx < len (m .ctxList ) {
49- selected := m .ctxList [m .ctxIdx ]
76+ if len (m .filteredCtxList ) > 0 && m .ctxIdx < len (m .filteredCtxList ) {
77+ selected := m .filteredCtxList [m .ctxIdx ]
5078 m .pendingContextName = selected .Name
5179 m .screen = screenLoading
5280 return m , m .switchContext (selected .Name )
@@ -63,6 +91,22 @@ func (m Model) updateContextPicker(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
6391 return m , nil
6492}
6593
94+ func (m * Model ) applyCtxFilter () {
95+ if m .ctxFilterInput == "" {
96+ m .filteredCtxList = m .ctxList
97+ } else {
98+ query := strings .ToLower (m .ctxFilterInput )
99+ var result []config.ContextInfo
100+ for _ , ctx := range m .ctxList {
101+ if strings .Contains (ctx .FilterText (), query ) {
102+ result = append (result , ctx )
103+ }
104+ }
105+ m .filteredCtxList = result
106+ }
107+ m .ctxIdx = 0
108+ }
109+
66110func (m Model ) switchContext (name string ) tea.Cmd {
67111 return func () tea.Msg {
68112 if err := config .SetCurrent (m .configPath , name ); err != nil {
@@ -127,17 +171,28 @@ func (m Model) doFinalizeContextSwitch() tea.Cmd {
127171func (m Model ) viewContextPicker () string {
128172 var b strings.Builder
129173 b .WriteString (titleStyle .Render ("Select Context" ))
174+ b .WriteString ("\n " )
175+
176+ // Filter bar
177+ if m .ctxFilterActive {
178+ b .WriteString (filterStyle .Render (fmt .Sprintf ("Filter: %s▏" , m .ctxFilterInput )))
179+ } else if m .ctxFilterInput != "" {
180+ b .WriteString (dimStyle .Render (fmt .Sprintf ("Filter: %s" , m .ctxFilterInput )))
181+ }
130182 b .WriteString ("\n \n " )
131183
132184 if len (m .ctxList ) == 0 {
133185 b .WriteString (normalStyle .Render (" No contexts defined." ))
134186 b .WriteString ("\n \n " )
135187 b .WriteString (dimStyle .Render (" Press 'a' to add your first context." ))
136188 b .WriteString ("\n " )
189+ } else if len (m .filteredCtxList ) == 0 {
190+ b .WriteString (dimStyle .Render (" No matching contexts" ))
191+ b .WriteString ("\n " )
137192 } else {
138193 // Measure max widths for alignment
139194 maxName , maxRegion := 4 , 6 // "NAME", "REGION"
140- for _ , ctx := range m .ctxList {
195+ for _ , ctx := range m .filteredCtxList {
141196 if len (ctx .Name ) > maxName {
142197 maxName = len (ctx .Name )
143198 }
@@ -153,16 +208,16 @@ func (m Model) viewContextPicker() string {
153208 b .WriteString (dimStyle .Render (" " + nameCol .Render ("NAME" ) + regionCol .Render ("REGION" ) + "AUTH" ))
154209 b .WriteString ("\n " )
155210
156- // overhead: title (1) + blank (1) + table header (1) + blank (1) + footer (1) = 5
157- visibleLines := max (m .height - 5 , 3 )
211+ // overhead: title (1) + filter (1) + blank (1) + table header (1) + blank (1) + footer (1) = 6
212+ visibleLines := max (m .height - 6 , 3 )
158213 start := 0
159214 if m .ctxIdx >= visibleLines {
160215 start = m .ctxIdx - visibleLines + 1
161216 }
162- end := min (start + visibleLines , len (m .ctxList ))
217+ end := min (start + visibleLines , len (m .filteredCtxList ))
163218
164219 for i := start ; i < end ; i ++ {
165- ctx := m .ctxList [i ]
220+ ctx := m .filteredCtxList [i ]
166221 cursor := " "
167222 style := normalStyle
168223 if i == m .ctxIdx {
@@ -181,9 +236,9 @@ func (m Model) viewContextPicker() string {
181236
182237 b .WriteString ("\n " )
183238 if m .cfg .ContextName != "" {
184- b .WriteString (dimStyle .Render ("↑/↓: navigate • enter: select • a: add • esc: back • q: quit" ))
239+ b .WriteString (dimStyle .Render ("↑/↓: navigate • /: filter • enter: select • a: add • esc: back • q: quit" ))
185240 } else {
186- b .WriteString (dimStyle .Render ("↑/↓: navigate • enter: select • a: add • q: quit" ))
241+ b .WriteString (dimStyle .Render ("↑/↓: navigate • /: filter • enter: select • a: add • q: quit" ))
187242 }
188243 return b .String ()
189244}
0 commit comments