feat: allow overwrite of completion handler (closes #122)

this also makes the completion functions `bins`
and `files` also return the prefix to pass
to the completion handler.

this is an overhaul to the completion system,
which gets the completion handler from lua
instead of being made to only have lua provided
*command* completions.

it does not have any performance deficit, even
though it calls in to golua for completions.
insensitive-tab^2
TorchedSammy 2022-04-22 21:16:35 -04:00
parent 3194add3dc
commit abfbeb5f84
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
3 changed files with 174 additions and 150 deletions

14
api.go
View File

@ -149,19 +149,7 @@ Check out the {blue}{bold}guide{reset} command to get started.
util.Document(historyModule, "History interface for Hilbish.") util.Document(historyModule, "History interface for Hilbish.")
// hilbish.completion table // hilbish.completion table
hshcomp := rt.NewTable() hshcomp := completionLoader(rtm)
util.SetField(rtm, hshcomp, "files",
rt.FunctionValue(rt.NewGoFunction(luaFileComplete, "files", 3, false)),
"Completer for files")
util.SetField(rtm, hshcomp, "bins",
rt.FunctionValue(rt.NewGoFunction(luaBinaryComplete, "bins", 3, false)),
"Completer for executables/binaries")
util.SetField(rtm, hshcomp, "call",
rt.FunctionValue(rt.NewGoFunction(callLuaCompleter, "call", 4, false)),
"Calls a completer and get its entries for completions")
util.Document(hshcomp, "Completions interface for Hilbish.") util.Document(hshcomp, "Completions interface for Hilbish.")
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp)) mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))

View File

@ -11,6 +11,8 @@ import (
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
) )
var completer rt.Value
func fileComplete(query, ctx string, fields []string) ([]string, string) { func fileComplete(query, ctx string, fields []string) ([]string, string) {
return matchPath(query) return matchPath(query)
} }
@ -117,6 +119,100 @@ func escapeFilename(fname string) string {
return r.Replace(fname) return r.Replace(fname)
} }
func completionLoader(rtm *rt.Runtime) *rt.Table {
exports := map[string]util.LuaExport{
"files": {luaFileComplete, 3, false},
"bins": {luaBinaryComplete, 3, false},
"call": {callLuaCompleter, 4, false},
"handler": {completionHandler, 2, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
return mod
}
func completionHandler(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(2); err != nil {
return nil, err
}
line, err := c.StringArg(0)
if err != nil {
return nil, err
}
// just for validation
_, err = c.IntArg(1)
if err != nil {
return nil, err
}
ctx := strings.TrimLeft(line, " ")
if len(ctx) == 0 {
return c.PushingNext(t.Runtime, rt.TableValue(rt.NewTable()), rt.StringValue("")), nil
}
ctx = aliases.Resolve(ctx)
fields := strings.Split(ctx, " ")
query := fields[len(fields) - 1]
luaFields := rt.NewTable()
for i, f := range fields {
luaFields.Set(rt.IntValue(int64(i + 1)), rt.StringValue(f))
}
compMod := hshMod.Get(rt.StringValue("completion")).AsTable()
var term *rt.Termination
if len(fields) == 1 {
term = rt.NewTerminationWith(t.CurrentCont(), 2, false)
err := rt.Call(t, compMod.Get(rt.StringValue("bins")), []rt.Value{
rt.StringValue(query),
rt.StringValue(ctx),
rt.TableValue(luaFields),
}, term)
if err != nil {
return nil, err
}
} else {
gterm := rt.NewTerminationWith(t.CurrentCont(), 2, false)
err := rt.Call(t, compMod.Get(rt.StringValue("call")), []rt.Value{
rt.StringValue("commands." + fields[0]),
rt.StringValue(query),
rt.StringValue(ctx),
rt.TableValue(luaFields),
}, gterm)
if err == nil {
groups := gterm.Get(0)
pfx := gterm.Get(1)
return c.PushingNext(t.Runtime, groups, pfx), nil
}
// error means there isnt a command handler - default to files in that case
term = rt.NewTerminationWith(t.CurrentCont(), 2, false)
err = rt.Call(t, compMod.Get(rt.StringValue("files")), []rt.Value{
rt.StringValue(query),
rt.StringValue(ctx),
rt.TableValue(luaFields),
}, term)
}
comps := term.Get(0)
pfx := term.Get(1)
groups := rt.NewTable()
compGroup := rt.NewTable()
compGroup.Set(rt.StringValue("items"), comps)
compGroup.Set(rt.StringValue("type"), rt.StringValue("grid"))
groups.Set(rt.IntValue(1), rt.TableValue(compGroup))
return c.PushingNext(t.Runtime, rt.TableValue(groups), pfx), nil
}
func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(4); err != nil { if err := c.CheckNArgs(4); err != nil {
return nil, err return nil, err
@ -162,14 +258,14 @@ func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
completions, _ := fileComplete(query, ctx, fds) completions, pfx := fileComplete(query, ctx, fds)
luaComps := rt.NewTable() luaComps := rt.NewTable()
for i, comp := range completions { for i, comp := range completions {
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp)) luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
} }
return c.PushingNext1(t.Runtime, rt.TableValue(luaComps)), nil return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil
} }
func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -178,14 +274,14 @@ func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
completions, _ := binaryComplete(query, ctx, fds) completions, pfx := binaryComplete(query, ctx, fds)
luaComps := rt.NewTable() luaComps := rt.NewTable()
for i, comp := range completions { for i, comp := range completions {
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp)) luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
} }
return c.PushingNext1(t.Runtime, rt.TableValue(luaComps)), nil 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, error) {

102
rl.go
View File

@ -84,76 +84,29 @@ func newLineReader(prompt string, noHist bool) *lineReader {
return highlighted return highlighted
} }
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) { rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
ctx := string(line) term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 2, false)
compHandle := hshMod.Get(rt.StringValue("completion")).AsTable().Get(rt.StringValue("handler"))
var compGroup []*readline.CompletionGroup err := rt.Call(l.MainThread(), compHandle, []rt.Value{rt.StringValue(string(line)),
rt.IntValue(int64(pos))}, term)
ctx = strings.TrimLeft(ctx, " ")
if len(ctx) == 0 {
return "", compGroup
}
fields := strings.Split(ctx, " ")
if len(fields) == 0 {
return "", compGroup
}
query := fields[len(fields) - 1]
ctx = aliases.Resolve(ctx)
if len(fields) == 1 {
completions, prefix := binaryComplete(query, ctx, fields)
compGroup = append(compGroup, &readline.CompletionGroup{
TrimSlash: false,
NoSpace: true,
Suggestions: completions,
})
return prefix, compGroup
} else {
if completecb, ok := luaCompletions["command." + fields[0]]; ok {
luaFields := rt.NewTable()
for i, f := range fields {
luaFields.Set(rt.IntValue(int64(i + 1)), rt.StringValue(f))
}
// we must keep the holy 80 cols
luacompleteTable, err := rt.Call1(l.MainThread(),
rt.FunctionValue(completecb), rt.StringValue(query),
rt.StringValue(ctx), rt.TableValue(luaFields))
var compGroups []*readline.CompletionGroup
if err != nil { if err != nil {
return "", compGroup return "", compGroups
} }
/* luaCompGroups := term.Get(0)
as an example with git, luaPrefix := term.Get(1)
completion table should be structured like:
{ if luaCompGroups.Type() != rt.TableType {
{ return "", compGroups
items = {
'add',
'clone',
'init'
},
type = 'grid'
},
{
items = {
'-c',
'--git-dir'
},
type = 'list'
} }
}
^ a table of completion groups. groups := luaCompGroups.AsTable()
it is the responsibility of the completer // prefix is optional
to work on subcommands and subcompletions pfx, _ := luaPrefix.TryString()
*/
if cmpTbl, ok := luacompleteTable.TryTable(); ok { util.ForEach(groups, func(key rt.Value, val rt.Value) {
util.ForEach(cmpTbl, func(key rt.Value, val rt.Value) { if key.Type() != rt.IntType || val.Type() != rt.TableType {
if key.Type() != rt.IntType && val.Type() != rt.TableType {
return return
} }
@ -161,7 +114,7 @@ func newLineReader(prompt string, noHist bool) *lineReader {
luaCompType := valTbl.Get(rt.StringValue("type")) luaCompType := valTbl.Get(rt.StringValue("type"))
luaCompItems := valTbl.Get(rt.StringValue("items")) luaCompItems := valTbl.Get(rt.StringValue("items"))
if luaCompType.Type() != rt.StringType && luaCompItems.Type() != rt.TableType { if luaCompType.Type() != rt.StringType || luaCompItems.Type() != rt.TableType {
return return
} }
@ -206,7 +159,7 @@ func newLineReader(prompt string, noHist bool) *lineReader {
//case "map": dispType = readline.TabDisplayMap //case "map": dispType = readline.TabDisplayMap
} }
compGroup = append(compGroup, &readline.CompletionGroup{ compGroups = append(compGroups, &readline.CompletionGroup{
DisplayType: dispType, DisplayType: dispType,
Descriptions: itemDescriptions, Descriptions: itemDescriptions,
Suggestions: items, Suggestions: items,
@ -214,21 +167,8 @@ func newLineReader(prompt string, noHist bool) *lineReader {
NoSpace: true, NoSpace: true,
}) })
}) })
}
}
if len(compGroup) == 0 { return pfx, compGroups
completions, p := fileComplete(query, ctx, fields)
fcompGroup := []*readline.CompletionGroup{{
TrimSlash: false,
NoSpace: true,
Suggestions: completions,
}}
return p, fcompGroup
}
}
return "", compGroup
} }
return &lineReader{ return &lineReader{