feat: add fuzzy searching for completion and history search (#247)

* feat: add fuzzy searching for completion and history search

* feat: add fuzzy opt for fuzzy history searching

* chore: add fuzzy opt to changelog
notifications
sammyette 2023-07-10 00:06:29 -04:00 committed by GitHub
parent cbc5e81c9d
commit 3eae0f07be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 60 additions and 23 deletions

View File

@ -6,6 +6,7 @@
- `read()` method for retrieving input (so now the `in` sink of commanders is useful)
- `flush()` and `autoFlush()` related to flushing outputs
- `pipe` property to check if a sink with input is a pipe (like stdin)
- Add fuzzy search to history search (enable via `hilbish.opts.fuzzy = true`)
- Show indexes on cdr list
### Fixed

1
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036
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/term v0.0.0-20220411215600-e5f449aeb171
mvdan.cc/sh/v3 v3.5.1

2
go.sum
View File

@ -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.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/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=

2
lua.go
View File

@ -68,7 +68,7 @@ func luaInit() {
}
// Add more paths that Lua can require from
err := util.DoString(l, "package.path = package.path .. " + requirePaths)
_, err := util.DoString(l, "package.path = package.path .. " + requirePaths)
if err != nil {
fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.")
}

View File

@ -16,7 +16,7 @@ setmetatable(hilbish.opts, {
local function setupOpt(name, default)
opts[name] = default
require('nature.opts.' .. name)
pcall(require, 'nature.opts.' .. name)
end
local defaultOpts = {
@ -25,7 +25,8 @@ local defaultOpts = {
greeting = string.format([[Welcome to {magenta}Hilbish{reset}, {cyan}%s{reset}.
The nice lil shell for {blue}Lua{reset} fanatics!
]], hilbish.user),
motd = true
motd = true,
fuzzy = false
}
for optsName, default in pairs(defaultOpts) do

View File

@ -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.
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.
for i := range g.Suggestions {
/*for i := range g.Suggestions {
if rl.regexSearch == nil { continue }
if rl.regexSearch.MatchString(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
suggs = append(suggs, g.Suggestions[i])
}
}
}*/
// We overwrite the group's items, (will be refreshed as soon as something is typed in the search)
g.Suggestions = suggs

View File

@ -112,8 +112,10 @@ type Instance struct {
modeAutoFind bool // for when invoked via ^R or ^F outside of [tab]
searchMode FindMode // Used for varying hints, and underlying functions called
regexSearch *regexp.Regexp // Holds the current search regex match
search string
mainHist bool // Which history stdin do we want
histInfo []rune // We store a piece of hist info, for dual history sources
Searcher func(string, []string) []string
//
// History -----------------------------------------------------------------------------------
@ -229,6 +231,25 @@ func NewInstance() *Instance {
rl.HintFormatting = "\x1b[2m"
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
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
rl.initRegisters()

View File

@ -94,7 +94,7 @@ func (rl *Instance) getTabSearchCompletion() {
rl.getCurrentGroup()
// 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 {
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 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)...)
}
}

View File

@ -1,9 +1,5 @@
package readline
import (
"regexp"
)
// FindMode defines how the autocomplete suggestions display
type FindMode int
@ -30,12 +26,7 @@ func (rl *Instance) updateTabFind(r []rune) {
rl.tfLine = append(rl.tfLine, r...)
// The search regex is common to all search modes
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"))
}
rl.search = string(rl.tfLine)
// We update and print
rl.clearHelpers()

View File

@ -14,6 +14,7 @@ var (
// effects
BOLD = "\033[1m"
DIM = "\033[2m"
UNDERLINE = "\033[4m"
RESET = "\033[0m"
// colors
RED = "\033[31m"

21
rl.go
View File

@ -7,8 +7,9 @@ import (
"hilbish/util"
"github.com/maxlandon/readline"
rt "github.com/arnodel/golua/runtime"
"github.com/maxlandon/readline"
"github.com/sahilm/fuzzy"
)
type lineReader struct {
@ -24,6 +25,24 @@ func newLineReader(prompt string, noHist bool) *lineReader {
rl: rl,
}
regexSearcher := rl.Searcher
rl.Searcher = func(needle string, haystack []string) []string {
fz, _ := util.DoString(l, "return hilbish.opts.fuzzy")
fuzz, ok := fz.TryBool()
if !fuzz || !ok {
return regexSearcher(needle, haystack)
}
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,
// but it cant have shared history
if !noHist {

View File

@ -26,13 +26,14 @@ func SetFieldProtected(module, realModule *rt.Table, field string, value rt.Valu
}
// DoString runs the code string in the Lua runtime.
func DoString(rtm *rt.Runtime, code string) error {
func DoString(rtm *rt.Runtime, code string) (rt.Value, error) {
chunk, err := rtm.CompileAndLoadLuaChunk("<string>", []byte(code), rt.TableValue(rtm.GlobalEnv()))
var ret rt.Value
if chunk != nil {
_, err = rt.Call1(rtm.MainThread(), rt.FunctionValue(chunk))
ret, err = rt.Call1(rtm.MainThread(), rt.FunctionValue(chunk))
}
return err
return ret, err
}
// DoFile runs the contents of the file in the Lua runtime.