Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support substring pattern mode for history searching #162

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 43 additions & 11 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type commonState struct {
inputRedirected bool
history []string
historyMutex sync.RWMutex
historyMode HistoryMode
completer WordCompleter
columns int
killRing *ring.Ring
Expand Down Expand Up @@ -52,6 +53,21 @@ const (
TabPrints
)

// HistoryMode defines the type of matching is used when searching
// through previously entered items.
type HistoryMode int

const (
// HistoryModePrefix will match a given item against items in the history
// by history items' prefix using the strings.HasPrefix function.
HistoryModePrefix HistoryMode = iota

// HistoryModePattern will match a given item against items in the history
// by substring matching. History items matching the given patter will
// be returned.
HistoryModePattern
)

// ErrPromptAborted is returned from Prompt or PasswordPrompt when the user presses Ctrl-C
// if SetCtrlCAborts(true) has been called on the State
var ErrPromptAborted = errors.New("prompt aborted")
Expand Down Expand Up @@ -152,16 +168,6 @@ func (s *State) ClearHistory() {
s.history = nil
}

// Returns the history lines starting with prefix
func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
for _, h := range s.history {
if strings.HasPrefix(h, prefix) {
ph = append(ph, h)
}
}
return
}

// Returns the history lines matching the intelligent search
func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) {
if pattern == "" {
Expand Down Expand Up @@ -234,8 +240,15 @@ func (s *State) SetMultiLineMode(mlmode bool) {
s.multiLineMode = mlmode
}

// SetHistoryMode sets the pattern behavior when searching through the history.
// The default is HistoryModePrefix; where returned history items are matched
// by the string prefix of a history item.
func (s *State) SetHistoryMode(mode HistoryMode) {
s.historyMode = mode
}

// ShouldRestart is passed the error generated by readNext and returns true if
// the the read should be restarted or false if the error should be returned.
// the read should be restarted or false if the error should be returned.
type ShouldRestart func(err error) bool

// SetShouldRestart sets the restart function that Liner will call to determine
Expand All @@ -260,3 +273,22 @@ func (s *State) promptUnsupported(p string) (string, error) {
}
return string(linebuf), nil
}

func (s *State) getHistory(line string) (ph []string) {
switch s.historyMode {
case HistoryModePattern:
if line == "" {
ph = append(ph, s.history...)
} else {
ph, _ = s.getHistoryByPattern(line)
}
return ph
default:
for _, h := range s.history {
if strings.HasPrefix(h, line) {
ph = append(ph, h)
}
}
return ph
}
}
65 changes: 65 additions & 0 deletions common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package liner

import (
"reflect"
"testing"
)

func TestState_getHistory(t *testing.T) {
tests := []struct {
name string
historyMode HistoryMode
line string
history []string
want []string
}{
{
name: "no specified mode uses default prefix matching mode",
line: "foo",
history: []string{"food", "foot", "tool"},
want: []string{"food", "foot"},
},
{
name: "explicit prefix mode matches",
line: "foo",
historyMode: HistoryModePrefix,
history: []string{"food", "foot", "tool"},
want: []string{"food", "foot"},
},
{
name: "pattern mode matches history substrings",
line: "oo",
historyMode: HistoryModePattern,
history: []string{"food", "foot", "tool"},
want: []string{"food", "foot", "tool"},
},
{
name: "empty string with pattern mode matches whole history",
line: "",
historyMode: HistoryModePattern,
history: []string{"food", "foot", "tool"},
want: []string{"food", "foot", "tool"},
},
{
name: "empty string with prefix mode matches whole history",
line: "",
historyMode: HistoryModePrefix,
history: []string{"food", "foot", "tool"},
want: []string{"food", "foot", "tool"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewLiner()
s.SetHistoryMode(tt.historyMode)
for _, line := range tt.history {
s.AppendHistory(line)
}

got := s.getHistory(tt.line)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getHistory() got = %v, want %v", got, tt.want)
}
})
}
}
8 changes: 4 additions & 4 deletions line.go
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ mainLoop:
case ctrlP: // up
historyAction = true
if historyStale {
historyPrefix = s.getHistoryByPrefix(string(line))
historyPrefix = s.getHistory(string(line))
historyPos = len(historyPrefix)
historyStale = false
}
Expand All @@ -746,7 +746,7 @@ mainLoop:
case ctrlN: // down
historyAction = true
if historyStale {
historyPrefix = s.getHistoryByPrefix(string(line))
historyPrefix = s.getHistory(string(line))
historyPos = len(historyPrefix)
historyStale = false
}
Expand Down Expand Up @@ -913,7 +913,7 @@ mainLoop:
case up:
historyAction = true
if historyStale {
historyPrefix = s.getHistoryByPrefix(string(line))
historyPrefix = s.getHistory(string(line))
historyPos = len(historyPrefix)
historyStale = false
}
Expand All @@ -930,7 +930,7 @@ mainLoop:
case down:
historyAction = true
if historyStale {
historyPrefix = s.getHistoryByPrefix(string(line))
historyPrefix = s.getHistory(string(line))
historyPos = len(historyPrefix)
historyStale = false
}
Expand Down