mirror of https://github.com/Hilbis/Hilbish
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 changelognotifications
parent
cbc5e81c9d
commit
3eae0f07be
|
@ -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
1
go.mod
|
@ -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
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.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
2
lua.go
|
@ -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.")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)...)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
21
rl.go
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue