mirror of https://github.com/Hilbis/Hilbish
feat: add fuzzy searching for completion and history search
parent
0379e302ac
commit
4593a2006c
1
go.mod
1
go.mod
|
@ -8,6 +8,7 @@ require (
|
||||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9
|
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9
|
||||||
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036
|
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036
|
||||||
github.com/pborman/getopt v1.1.0
|
github.com/pborman/getopt v1.1.0
|
||||||
|
github.com/sahilm/fuzzy v0.1.0
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
|
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
|
||||||
mvdan.cc/sh/v3 v3.5.1
|
mvdan.cc/sh/v3 v3.5.1
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -49,6 +49,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
|
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
|
||||||
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||||
|
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||||
|
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||||
|
|
|
@ -71,10 +71,9 @@ func (g *CompletionGroup) init(rl *Instance) {
|
||||||
// The rx parameter is passed, as the shell already checked that the search pattern is valid.
|
// The rx parameter is passed, as the shell already checked that the search pattern is valid.
|
||||||
func (g *CompletionGroup) updateTabFind(rl *Instance) {
|
func (g *CompletionGroup) updateTabFind(rl *Instance) {
|
||||||
|
|
||||||
suggs := make([]string, 0)
|
suggs := rl.Searcher(rl.search, g.Suggestions)
|
||||||
|
|
||||||
// We perform filter right here, so we create a new completion group, and populate it with our results.
|
// We perform filter right here, so we create a new completion group, and populate it with our results.
|
||||||
for i := range g.Suggestions {
|
/*for i := range g.Suggestions {
|
||||||
if rl.regexSearch == nil { continue }
|
if rl.regexSearch == nil { continue }
|
||||||
if rl.regexSearch.MatchString(g.Suggestions[i]) {
|
if rl.regexSearch.MatchString(g.Suggestions[i]) {
|
||||||
suggs = append(suggs, g.Suggestions[i])
|
suggs = append(suggs, g.Suggestions[i])
|
||||||
|
@ -82,7 +81,7 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) {
|
||||||
// this is a list so lets also check the descriptions
|
// this is a list so lets also check the descriptions
|
||||||
suggs = append(suggs, g.Suggestions[i])
|
suggs = append(suggs, g.Suggestions[i])
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// We overwrite the group's items, (will be refreshed as soon as something is typed in the search)
|
// We overwrite the group's items, (will be refreshed as soon as something is typed in the search)
|
||||||
g.Suggestions = suggs
|
g.Suggestions = suggs
|
||||||
|
|
|
@ -112,8 +112,10 @@ type Instance struct {
|
||||||
modeAutoFind bool // for when invoked via ^R or ^F outside of [tab]
|
modeAutoFind bool // for when invoked via ^R or ^F outside of [tab]
|
||||||
searchMode FindMode // Used for varying hints, and underlying functions called
|
searchMode FindMode // Used for varying hints, and underlying functions called
|
||||||
regexSearch *regexp.Regexp // Holds the current search regex match
|
regexSearch *regexp.Regexp // Holds the current search regex match
|
||||||
|
search string
|
||||||
mainHist bool // Which history stdin do we want
|
mainHist bool // Which history stdin do we want
|
||||||
histInfo []rune // We store a piece of hist info, for dual history sources
|
histInfo []rune // We store a piece of hist info, for dual history sources
|
||||||
|
Searcher func(string, []string) []string
|
||||||
|
|
||||||
//
|
//
|
||||||
// History -----------------------------------------------------------------------------------
|
// History -----------------------------------------------------------------------------------
|
||||||
|
@ -229,6 +231,25 @@ func NewInstance() *Instance {
|
||||||
rl.HintFormatting = "\x1b[2m"
|
rl.HintFormatting = "\x1b[2m"
|
||||||
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
|
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
|
||||||
rl.TempDirectory = os.TempDir()
|
rl.TempDirectory = os.TempDir()
|
||||||
|
rl.Searcher = func(needle string, haystack []string) []string {
|
||||||
|
suggs := make([]string, 0)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
|
||||||
|
if err != nil {
|
||||||
|
rl.RefreshPromptLog(err.Error())
|
||||||
|
rl.infoText = []rune(Red("Failed to match search regexp"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hay := range haystack {
|
||||||
|
if rl.regexSearch == nil { continue }
|
||||||
|
if rl.regexSearch.MatchString(hay) {
|
||||||
|
suggs = append(suggs, hay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggs
|
||||||
|
}
|
||||||
|
|
||||||
// Registers
|
// Registers
|
||||||
rl.initRegisters()
|
rl.initRegisters()
|
||||||
|
|
|
@ -94,7 +94,7 @@ func (rl *Instance) getTabSearchCompletion() {
|
||||||
rl.getCurrentGroup()
|
rl.getCurrentGroup()
|
||||||
|
|
||||||
// Set the info for this completion mode
|
// Set the info for this completion mode
|
||||||
rl.infoText = append([]rune("Completion search: "), rl.tfLine...)
|
rl.infoText = append([]rune("Completion search: " + UNDERLINE + BOLD), rl.tfLine...)
|
||||||
|
|
||||||
for _, g := range rl.tcGroups {
|
for _, g := range rl.tcGroups {
|
||||||
g.updateTabFind(rl)
|
g.updateTabFind(rl)
|
||||||
|
@ -102,7 +102,7 @@ func (rl *Instance) getTabSearchCompletion() {
|
||||||
|
|
||||||
// If total number of matches is zero, we directly change the info, and return
|
// If total number of matches is zero, we directly change the info, and return
|
||||||
if comps, _, _ := rl.getCompletionCount(); comps == 0 {
|
if comps, _, _ := rl.getCompletionCount(); comps == 0 {
|
||||||
rl.infoText = append(rl.infoText, []rune(DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...)
|
rl.infoText = append(rl.infoText, []rune(RESET+DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FindMode defines how the autocomplete suggestions display
|
// FindMode defines how the autocomplete suggestions display
|
||||||
type FindMode int
|
type FindMode int
|
||||||
|
|
||||||
|
@ -30,12 +26,7 @@ func (rl *Instance) updateTabFind(r []rune) {
|
||||||
rl.tfLine = append(rl.tfLine, r...)
|
rl.tfLine = append(rl.tfLine, r...)
|
||||||
|
|
||||||
// The search regex is common to all search modes
|
// The search regex is common to all search modes
|
||||||
var err error
|
rl.search = string(rl.tfLine)
|
||||||
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
|
|
||||||
if err != nil {
|
|
||||||
rl.RefreshPromptLog(err.Error())
|
|
||||||
rl.infoText = []rune(Red("Failed to match search regexp"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// We update and print
|
// We update and print
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
|
|
|
@ -14,6 +14,7 @@ var (
|
||||||
// effects
|
// effects
|
||||||
BOLD = "\033[1m"
|
BOLD = "\033[1m"
|
||||||
DIM = "\033[2m"
|
DIM = "\033[2m"
|
||||||
|
UNDERLINE = "\033[4m"
|
||||||
RESET = "\033[0m"
|
RESET = "\033[0m"
|
||||||
// colors
|
// colors
|
||||||
RED = "\033[31m"
|
RED = "\033[31m"
|
||||||
|
|
14
rl.go
14
rl.go
|
@ -7,8 +7,9 @@ import (
|
||||||
|
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
"github.com/maxlandon/readline"
|
||||||
|
"github.com/sahilm/fuzzy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type lineReader struct {
|
type lineReader struct {
|
||||||
|
@ -24,6 +25,17 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||||
rl: rl,
|
rl: rl,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rl.Searcher = func(needle string, haystack []string) []string {
|
||||||
|
matches := fuzzy.Find(needle, haystack)
|
||||||
|
suggs := make([]string, 0)
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
suggs = append(suggs, match.Str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggs
|
||||||
|
}
|
||||||
|
|
||||||
// we don't mind hilbish.read rl instances having completion,
|
// we don't mind hilbish.read rl instances having completion,
|
||||||
// but it cant have shared history
|
// but it cant have shared history
|
||||||
if !noHist {
|
if !noHist {
|
||||||
|
|
Loading…
Reference in New Issue