From b37715f4834cf13451566334f90fd88d9f0b6011 Mon Sep 17 00:00:00 2001 From: TorchedSammy <38820196+TorchedSammy@users.noreply.github.com> Date: Wed, 30 Nov 2022 17:23:48 -0400 Subject: [PATCH 1/5] feat: add case insensitive tab completion --- complete.go | 2 +- readline/tab-virtual.go | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/complete.go b/complete.go index c2a107c..df20f72 100644 --- a/complete.go +++ b/complete.go @@ -150,7 +150,7 @@ func matchPath(query string) ([]string, string) { files, _ := os.ReadDir(path) for _, file := range files { - if strings.HasPrefix(file.Name(), baseName) { + if strings.HasPrefix(strings.ToLower(file.Name()), strings.ToLower(baseName)) { entry := file.Name() if file.IsDir() { entry = entry + string(os.PathSeparator) diff --git a/readline/tab-virtual.go b/readline/tab-virtual.go index d1e1d76..a44db8b 100644 --- a/readline/tab-virtual.go +++ b/readline/tab-virtual.go @@ -74,7 +74,8 @@ func (rl *Instance) insertCandidate() { // Ensure no indexing error happens with prefix if len(completion) >= prefix { - rl.insert([]rune(completion[prefix:])) + rl.viDeleteByAdjust(-prefix) + rl.insert([]rune(completion)) if !cur.TrimSlash && !cur.NoSpace { rl.insert([]rune(" ")) } @@ -86,7 +87,6 @@ func (rl *Instance) insertCandidate() { func (rl *Instance) updateVirtualComp() { cur := rl.getCurrentGroup() if cur != nil { - completion := cur.getCurrentCell(rl) prefix := len(rl.tcPrefix) @@ -99,6 +99,9 @@ func (rl *Instance) updateVirtualComp() { rl.viUndoSkipAppend = true rl.resetTabCompletion() } else { + if strings.HasSuffix(string(rl.line), rl.tcPrefix) { + rl.viDeleteByAdjust(-prefix) + } // Special case for the only special escape, which // if not handled, will make us insert the first @@ -109,7 +112,7 @@ func (rl *Instance) updateVirtualComp() { // Or insert it virtually. if len(completion) >= prefix { - rl.insertCandidateVirtual([]rune(completion[prefix:])) + rl.insertCandidateVirtual([]rune(completion)) } } } @@ -173,7 +176,7 @@ func (rl *Instance) resetVirtualComp(drop bool) { completion = completion + " " } } - rl.insertCandidateVirtual([]rune(completion[prefix:])) + rl.insertCandidateVirtual([]rune(completion)) } // Reset virtual From 8b4766028aaed37b598c59599715e85a1a4d4217 Mon Sep 17 00:00:00 2001 From: TorchedSammy <38820196+TorchedSammy@users.noreply.github.com> Date: Wed, 30 Nov 2022 19:03:45 -0400 Subject: [PATCH 2/5] feat: add an opt for case insensitive completion --- complete.go | 53 ++++++++++++++++++++++++------------- docs/completions.txt | 4 +-- nature/completions.lua | 4 +-- nature/opts/init.lua | 3 ++- nature/opts/insensitive.lua | 1 + 5 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 nature/opts/insensitive.lua diff --git a/complete.go b/complete.go index df20f72..0458a27 100644 --- a/complete.go +++ b/complete.go @@ -72,13 +72,13 @@ func splitForFile(str string) []string { return split } -func fileComplete(query, ctx string, fields []string) ([]string, string) { +func fileComplete(query, ctx string, fields []string, caseInsensitive bool) ([]string, string) { q := splitForFile(ctx) - return matchPath(q[len(q) - 1]) + return matchPath(q[len(q) - 1], caseInsensitive) } -func binaryComplete(query, ctx string, fields []string) ([]string, string) { +func binaryComplete(query, ctx string, fields []string, caseInsensitive bool) ([]string, string) { q := splitForFile(ctx) query = q[len(q) - 1] @@ -87,7 +87,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) { prefixes := []string{"./", "../", "/", "~/"} for _, prefix := range prefixes { if strings.HasPrefix(query, prefix) { - fileCompletions, filePref := matchPath(query) + fileCompletions, filePref := matchPath(query, caseInsensitive) if len(fileCompletions) != 0 { for _, f := range fileCompletions { fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref))) @@ -132,7 +132,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) { return completions, query } -func matchPath(query string) ([]string, string) { +func matchPath(query string, caseInsensitive bool) ([]string, string) { oldQuery := query query = strings.TrimPrefix(query, "\"") var entries []string @@ -148,9 +148,17 @@ func matchPath(query string) ([]string, string) { baseName = filepath.Base(query) } + if caseInsensitive { + baseName = strings.ToLower(baseName) + } + files, _ := os.ReadDir(path) for _, file := range files { - if strings.HasPrefix(strings.ToLower(file.Name()), strings.ToLower(baseName)) { + fname := file.Name() + if caseInsensitive { + fname = strings.ToLower(fname) + } + if strings.HasPrefix(fname, baseName) { entry := file.Name() if file.IsDir() { entry = entry + string(os.PathSeparator) @@ -174,8 +182,8 @@ func escapeFilename(fname string) string { func completionLoader(rtm *rt.Runtime) *rt.Table { exports := map[string]util.LuaExport{ - "files": {luaFileComplete, 3, false}, - "bins": {luaBinaryComplete, 3, false}, + "files": {luaFileComplete, 3, true}, + "bins": {luaBinaryComplete, 3, true}, "call": {callLuaCompleter, 4, false}, "handler": {completionHandler, 2, false}, } @@ -231,12 +239,12 @@ func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { } func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { - query, ctx, fds, err := getCompleteParams(t, c) + query, ctx, fds, insensitive, err := getCompleteParams(t, c) if err != nil { return nil, err } - completions, pfx := fileComplete(query, ctx, fds) + completions, pfx := fileComplete(query, ctx, fds, insensitive) luaComps := rt.NewTable() for i, comp := range completions { @@ -247,12 +255,12 @@ func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { } func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { - query, ctx, fds, err := getCompleteParams(t, c) + query, ctx, fds, insensitive, err := getCompleteParams(t, c) if err != nil { return nil, err } - completions, pfx := binaryComplete(query, ctx, fds) + completions, pfx := binaryComplete(query, ctx, fds, insensitive) luaComps := rt.NewTable() for i, comp := range completions { @@ -262,21 +270,30 @@ func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil } -func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, error) { +func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, bool, error) { if err := c.CheckNArgs(3); err != nil { - return "", "", []string{}, err + return "", "", []string{}, false, err } query, err := c.StringArg(0) if err != nil { - return "", "", []string{}, err + return "", "", []string{}, false, err } ctx, err := c.StringArg(1) if err != nil { - return "", "", []string{}, err + return "", "", []string{}, false, err } fields, err := c.TableArg(2) if err != nil { - return "", "", []string{}, err + return "", "", []string{}, false, err + } + var insensitive bool + if len(c.Etc()) != 0 { + typ := c.Etc()[0] + var ok bool + insensitive, ok = typ.TryBool() + if !ok { + return "", "", []string{}, false, errors.New("bad argument #4 (expected bool, got " + typ.TypeName() + ")") + } } var fds []string @@ -286,5 +303,5 @@ func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, er } }) - return query, ctx, fds, err + return query, ctx, fds, insensitive, err } diff --git a/docs/completions.txt b/docs/completions.txt index 1354dc0..8b0b3ed 100644 --- a/docs/completions.txt +++ b/docs/completions.txt @@ -36,9 +36,9 @@ then there is the `files` function, which is mentioned below. # Completion Interface ## Functions -- `files(query, ctx, fields)` -> table, prefix: get file completions, based +- `files(query, ctx, fields, caseInsensitive)` -> table, prefix: get file completions, based on the user's query. -- `bins(query, ctx, fields)` -> table, prefix: get binary/executable +- `bins(query, ctx, fields, caseInsensitive)` -> table, prefix: get binary/executable completions, based on user query. - `call(scope, query, ctx, fields)` -> table, prefix: call a completion handler with `scope`, usually being in the form of `command.` diff --git a/nature/completions.lua b/nature/completions.lua index d20cc59..fc08261 100644 --- a/nature/completions.lua +++ b/nature/completions.lua @@ -15,7 +15,7 @@ function hilbish.completion.handler(line, pos) local query = fields[#fields] if #fields == 1 then - local comps, pfx = hilbish.completion.bins(query, ctx, fields) + local comps, pfx = hilbish.completion.bins(query, ctx, fields, hilbish.opts.insensitive) local compGroup = { items = comps, type = 'grid' @@ -29,7 +29,7 @@ function hilbish.completion.handler(line, pos) return compGroups, pfx end - local comps, pfx = hilbish.completion.files(query, ctx, fields) + local comps, pfx = hilbish.completion.files(query, ctx, fields, hilbish.opts.insensitive) local compGroup = { items = comps, type = 'grid' diff --git a/nature/opts/init.lua b/nature/opts/init.lua index ae95ee1..fa6ba9c 100644 --- a/nature/opts/init.lua +++ b/nature/opts/init.lua @@ -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, + insensitive = true } for optsName, default in pairs(defaultOpts) do diff --git a/nature/opts/insensitive.lua b/nature/opts/insensitive.lua new file mode 100644 index 0000000..1fcd659 --- /dev/null +++ b/nature/opts/insensitive.lua @@ -0,0 +1 @@ +-- noop From 745e21d5bf1d4c478e77e811104d807909264ee6 Mon Sep 17 00:00:00 2001 From: TorchedSammy <38820196+TorchedSammy@users.noreply.github.com> Date: Wed, 30 Nov 2022 19:50:46 -0400 Subject: [PATCH 3/5] chore: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c621d1..f64fce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ random errors introduced with the new Lua runtime (see [#197]) - `exec`, `clear` and `cat` builtin commands - `hilbish.cancel` hook - 1st item on history is now inserted when history search menu is opened ([#148]) +- Case insensitive tab completion! It can be enabled with the `insensitive` opt. [#148]: https://github.com/Rosettea/Hilbish/issues/148 [#197]: https://github.com/Rosettea/Hilbish/issues/197 From b9364bd8b4cc6a71eca0f1fb2a612dd885cc03c1 Mon Sep 17 00:00:00 2001 From: TorchedSammy <38820196+TorchedSammy@users.noreply.github.com> Date: Wed, 30 Nov 2022 20:10:52 -0400 Subject: [PATCH 4/5] chore: change insensitive opt default from true to false --- nature/opts/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nature/opts/init.lua b/nature/opts/init.lua index fa6ba9c..067801b 100644 --- a/nature/opts/init.lua +++ b/nature/opts/init.lua @@ -26,7 +26,7 @@ local defaultOpts = { The nice lil shell for {blue}Lua{reset} fanatics! ]], hilbish.user), motd = true, - insensitive = true + insensitive = false } for optsName, default in pairs(defaultOpts) do From 4e40bc57e0591a236eda628017ab7413721325a7 Mon Sep 17 00:00:00 2001 From: TorchedSammy <38820196+TorchedSammy@users.noreply.github.com> Date: Mon, 5 Dec 2022 17:55:43 -0400 Subject: [PATCH 5/5] fix: only remove prefix if not in history find mode --- readline/tab-virtual.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/readline/tab-virtual.go b/readline/tab-virtual.go index a44db8b..37df4d5 100644 --- a/readline/tab-virtual.go +++ b/readline/tab-virtual.go @@ -75,6 +75,7 @@ func (rl *Instance) insertCandidate() { // Ensure no indexing error happens with prefix if len(completion) >= prefix { rl.viDeleteByAdjust(-prefix) + rl.insert([]rune(completion)) if !cur.TrimSlash && !cur.NoSpace { rl.insert([]rune(" ")) @@ -99,7 +100,7 @@ func (rl *Instance) updateVirtualComp() { rl.viUndoSkipAppend = true rl.resetTabCompletion() } else { - if strings.HasSuffix(string(rl.line), rl.tcPrefix) { + if strings.HasSuffix(string(rl.line), rl.tcPrefix) && (!rl.modeAutoFind || rl.searchMode != HistoryFind) { rl.viDeleteByAdjust(-prefix) } @@ -112,7 +113,12 @@ func (rl *Instance) updateVirtualComp() { // Or insert it virtually. if len(completion) >= prefix { - rl.insertCandidateVirtual([]rune(completion)) + comp := completion + if rl.modeAutoFind && rl.searchMode == HistoryFind { + comp = completion[prefix:] + } + + rl.insertCandidateVirtual([]rune(comp)) } } }