Skip to content

Commit bb5b23f

Browse files
committed
lock search results
1 parent c35f64e commit bb5b23f

File tree

3 files changed

+104
-13
lines changed

3 files changed

+104
-13
lines changed

keycodes.go

+11
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,14 @@ var (
2727
KeyForward rune = readline.CharForward
2828
KeyForwardDisplay = "→"
2929
)
30+
31+
const (
32+
// KeyRefresh is the key to refresh the current status
33+
KeyRefresh = '\x01'
34+
35+
// KeyCtrlE is the key to erase the search keywords
36+
KeyCtrlE = '\x05'
37+
38+
// KeySoftEnter is the key to lock the search keywords
39+
KeySoftEnter = '\x1e'
40+
)

list/list.go

+9
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ func (l *List) CanPageUp() bool {
201201
// Index returns the index of the item currently selected inside the searched list. If no item is selected,
202202
// the NotFound (-1) index is returned.
203203
func (l *List) Index() int {
204+
if l.cursor >= len(l.scope) {
205+
return NotFound
206+
}
207+
204208
selected := l.scope[l.cursor]
205209

206210
for i, item := range l.items {
@@ -235,3 +239,8 @@ func (l *List) Items() ([]interface{}, int) {
235239

236240
return result, active
237241
}
242+
243+
// VisibleSize returns the size of the current visible items.
244+
func (l *List) VisibleSize() int {
245+
return len(l.scope)
246+
}

select.go

+84-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"os"
8+
"strings"
89
"text/tabwriter"
910
"text/template"
1011

@@ -18,9 +19,6 @@ import (
1819
// SelectWithAdd's logic.
1920
const SelectedAdd = -1
2021

21-
// KeyRefresh indicates to refresh the current status
22-
const KeyRefresh = '\x01'
23-
2422
// Select represents a list of items used to enable selections, they can be used as search engines, menus
2523
// or as a list of items in a cli based prompt.
2624
type Select struct {
@@ -83,6 +81,9 @@ type Select struct {
8381

8482
Stdin io.ReadCloser
8583
Stdout io.WriteCloser
84+
85+
// keywords for search
86+
keywords string
8687
}
8788

8889
// SelectKeys defines the available keys used by select mode to enable the user to move around the list
@@ -171,19 +172,27 @@ type SelectTemplates struct {
171172
// it shows keys for movement and search.
172173
Help string
173174

175+
// SearchTips is a text/template for displaying search tips while search having results.
176+
SearchTips string
177+
178+
// Keywords is a text/template for the search keywords.
179+
Keywords string
180+
174181
// FuncMap is a map of helper functions that can be used inside of templates according to the text/template
175182
// documentation.
176183
//
177184
// By default, FuncMap contains the color functions used to color the text in templates. If FuncMap
178185
// is overridden, the colors functions must be added in the override from promptui.FuncMap to work.
179186
FuncMap template.FuncMap
180187

181-
label *template.Template
182-
active *template.Template
183-
inactive *template.Template
184-
selected *template.Template
185-
details *template.Template
186-
help *template.Template
188+
label *template.Template
189+
active *template.Template
190+
inactive *template.Template
191+
selected *template.Template
192+
details *template.Template
193+
help *template.Template
194+
searchTips *template.Template
195+
keywords *template.Template
187196
}
188197

189198
// SearchPrompt is the prompt displayed in search mode.
@@ -225,6 +234,17 @@ func (s *Select) RunCursorAt(cursorPos, scroll int) (int, string, error) {
225234
return s.innerRun(cursorPos, scroll, ' ')
226235
}
227236

237+
func (s *Select) getKeywords(keywords string) string {
238+
keywords = strings.Join(strings.Fields(keywords), " ")
239+
if keywords == "" {
240+
return s.keywords
241+
}
242+
if s.keywords == "" {
243+
return keywords
244+
}
245+
return s.keywords + " " + keywords
246+
}
247+
228248
func (s *Select) innerRun(cursorPos, scroll int, top rune) (int, string, error) {
229249
c := &readline.Config{
230250
Stdin: s.Stdin,
@@ -275,7 +295,11 @@ func (s *Select) innerRun(cursorPos, scroll int, top rune) (int, string, error)
275295
if searchMode {
276296
searchMode = false
277297
cur.Replace("")
278-
s.list.CancelSearch()
298+
if s.keywords == "" {
299+
s.list.CancelSearch()
300+
} else {
301+
s.list.Search(s.keywords)
302+
}
279303
} else {
280304
searchMode = true
281305
}
@@ -286,31 +310,49 @@ func (s *Select) innerRun(cursorPos, scroll int, top rune) (int, string, error)
286310

287311
cur.Backspace()
288312
if len(cur.Get()) > 0 {
289-
s.list.Search(cur.Get())
290-
} else {
313+
s.list.Search(s.getKeywords(cur.Get()))
314+
} else if s.keywords == "" {
291315
s.list.CancelSearch()
316+
} else {
317+
s.list.Search(s.keywords)
292318
}
293319
case key == s.Keys.PageUp.Code || (key == 'h' && !searchMode):
294320
s.list.PageUp()
295321
case key == s.Keys.PageDown.Code || (key == 'l' && !searchMode):
296322
s.list.PageDown()
297323
case key == KeyRefresh:
298324
break
325+
case canSearch && key == KeyCtrlE && (s.keywords != "" || searchMode):
326+
s.keywords = ""
327+
searchMode = false
328+
cur.Replace("")
329+
s.list.CancelSearch()
330+
case canSearch && searchMode && key == KeySoftEnter && s.list.VisibleSize() > 0:
331+
s.keywords = s.getKeywords(cur.Get())
332+
searchMode = false
333+
cur.Replace("")
299334
default:
300335
if canSearch && searchMode {
301336
cur.Update(string(line))
302-
s.list.Search(cur.Get())
337+
s.list.Search(s.getKeywords(cur.Get()))
303338
}
304339
}
305340

306341
if searchMode {
307342
header := SearchPrompt + cur.Format()
343+
if s.list.VisibleSize() > 0 {
344+
header += string(render(s.Templates.searchTips, nil))
345+
}
308346
sb.WriteString(header)
309347
} else if !s.HideHelp {
310348
help := s.renderHelp(canSearch)
311349
sb.Write(help)
312350
}
313351

352+
if s.keywords != "" {
353+
sb.Write(render(s.Templates.keywords, s.keywords))
354+
}
355+
314356
label := render(s.Templates.label, s.Label)
315357
sb.Write(label)
316358

@@ -421,6 +463,17 @@ func (s *Select) GetCurrentIndex() int {
421463
return s.list.Index()
422464
}
423465

466+
// GetVisibleSize returns the size of the current visible items.
467+
func (s *Select) GetVisibleSize() int {
468+
return s.list.VisibleSize()
469+
}
470+
471+
// GetVisibleItems returns the current visible items.
472+
func (s *Select) GetVisibleItems() []interface{} {
473+
items, _ := s.list.Items()
474+
return items
475+
}
476+
424477
func (s *Select) prepareTemplates() error {
425478
tpls := s.Templates
426479
if tpls == nil {
@@ -496,6 +549,24 @@ func (s *Select) prepareTemplates() error {
496549

497550
tpls.help = tpl
498551

552+
if tpls.SearchTips == "" {
553+
tpls.SearchTips = `{{ " Enter to lock the search results" | faint }}`
554+
}
555+
tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.SearchTips)
556+
if err != nil {
557+
return err
558+
}
559+
tpls.searchTips = tpl
560+
561+
if tpls.Keywords == "" {
562+
tpls.Keywords = `Keywords: {{ . | blue }} {{ " Ctrl+E to erase the search keywords" | faint }}`
563+
}
564+
tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Keywords)
565+
if err != nil {
566+
return err
567+
}
568+
tpls.keywords = tpl
569+
499570
s.Templates = tpls
500571

501572
return nil

0 commit comments

Comments
 (0)