mirror of https://github.com/Hilbis/Hilbish
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
parent
3194add3dc
commit
abfbeb5f84
14
api.go
14
api.go
|
@ -149,19 +149,7 @@ Check out the {blue}{bold}guide{reset} command to get started.
|
|||
util.Document(historyModule, "History interface for Hilbish.")
|
||||
|
||||
// hilbish.completion table
|
||||
hshcomp := rt.NewTable()
|
||||
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")
|
||||
|
||||
hshcomp := completionLoader(rtm)
|
||||
util.Document(hshcomp, "Completions interface for Hilbish.")
|
||||
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))
|
||||
|
||||
|
|
104
complete.go
104
complete.go
|
@ -11,6 +11,8 @@ import (
|
|||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var completer rt.Value
|
||||
|
||||
func fileComplete(query, ctx string, fields []string) ([]string, string) {
|
||||
return matchPath(query)
|
||||
}
|
||||
|
@ -117,6 +119,100 @@ func escapeFilename(fname string) string {
|
|||
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) {
|
||||
if err := c.CheckNArgs(4); err != nil {
|
||||
return nil, err
|
||||
|
@ -162,14 +258,14 @@ func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
completions, _ := fileComplete(query, ctx, fds)
|
||||
completions, pfx := fileComplete(query, ctx, fds)
|
||||
luaComps := rt.NewTable()
|
||||
|
||||
for i, comp := range completions {
|
||||
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) {
|
||||
|
@ -178,14 +274,14 @@ func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
completions, _ := binaryComplete(query, ctx, fds)
|
||||
completions, pfx := binaryComplete(query, ctx, fds)
|
||||
luaComps := rt.NewTable()
|
||||
|
||||
for i, comp := range completions {
|
||||
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) {
|
||||
|
|
206
rl.go
206
rl.go
|
@ -84,151 +84,91 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||
return highlighted
|
||||
}
|
||||
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"))
|
||||
err := rt.Call(l.MainThread(), compHandle, []rt.Value{rt.StringValue(string(line)),
|
||||
rt.IntValue(int64(pos))}, term)
|
||||
|
||||
var compGroup []*readline.CompletionGroup
|
||||
|
||||
ctx = strings.TrimLeft(ctx, " ")
|
||||
if len(ctx) == 0 {
|
||||
return "", compGroup
|
||||
var compGroups []*readline.CompletionGroup
|
||||
if err != nil {
|
||||
return "", compGroups
|
||||
}
|
||||
|
||||
fields := strings.Split(ctx, " ")
|
||||
if len(fields) == 0 {
|
||||
return "", compGroup
|
||||
luaCompGroups := term.Get(0)
|
||||
luaPrefix := term.Get(1)
|
||||
|
||||
if luaCompGroups.Type() != rt.TableType {
|
||||
return "", compGroups
|
||||
}
|
||||
query := fields[len(fields) - 1]
|
||||
|
||||
ctx = aliases.Resolve(ctx)
|
||||
groups := luaCompGroups.AsTable()
|
||||
// prefix is optional
|
||||
pfx, _ := luaPrefix.TryString()
|
||||
|
||||
if len(fields) == 1 {
|
||||
completions, prefix := binaryComplete(query, ctx, fields)
|
||||
util.ForEach(groups, func(key rt.Value, val rt.Value) {
|
||||
if key.Type() != rt.IntType || val.Type() != rt.TableType {
|
||||
return
|
||||
}
|
||||
|
||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
Suggestions: completions,
|
||||
valTbl := val.AsTable()
|
||||
luaCompType := valTbl.Get(rt.StringValue("type"))
|
||||
luaCompItems := valTbl.Get(rt.StringValue("items"))
|
||||
|
||||
if luaCompType.Type() != rt.StringType || luaCompItems.Type() != rt.TableType {
|
||||
return
|
||||
}
|
||||
|
||||
items := []string{}
|
||||
itemDescriptions := make(map[string]string)
|
||||
|
||||
util.ForEach(luaCompItems.AsTable(), func(lkey rt.Value, lval rt.Value) {
|
||||
if keytyp := lkey.Type(); keytyp == rt.StringType {
|
||||
// ['--flag'] = {'description', '--flag-alias'}
|
||||
itemName, ok := lkey.TryString()
|
||||
vlTbl, okk := lval.TryTable()
|
||||
if !ok && !okk {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
|
||||
items = append(items, itemName)
|
||||
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
||||
if !ok {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
itemDescriptions[itemName] = itemDescription
|
||||
} else if keytyp == rt.IntType {
|
||||
vlStr, ok := lval.TryString()
|
||||
if !ok {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
items = append(items, vlStr)
|
||||
} else {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
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))
|
||||
|
||||
if err != nil {
|
||||
return "", compGroup
|
||||
}
|
||||
|
||||
/*
|
||||
as an example with git,
|
||||
completion table should be structured like:
|
||||
{
|
||||
{
|
||||
items = {
|
||||
'add',
|
||||
'clone',
|
||||
'init'
|
||||
},
|
||||
type = 'grid'
|
||||
},
|
||||
{
|
||||
items = {
|
||||
'-c',
|
||||
'--git-dir'
|
||||
},
|
||||
type = 'list'
|
||||
}
|
||||
}
|
||||
^ a table of completion groups.
|
||||
it is the responsibility of the completer
|
||||
to work on subcommands and subcompletions
|
||||
*/
|
||||
if cmpTbl, ok := luacompleteTable.TryTable(); ok {
|
||||
util.ForEach(cmpTbl, func(key rt.Value, val rt.Value) {
|
||||
if key.Type() != rt.IntType && val.Type() != rt.TableType {
|
||||
return
|
||||
}
|
||||
|
||||
valTbl := val.AsTable()
|
||||
luaCompType := valTbl.Get(rt.StringValue("type"))
|
||||
luaCompItems := valTbl.Get(rt.StringValue("items"))
|
||||
|
||||
if luaCompType.Type() != rt.StringType && luaCompItems.Type() != rt.TableType {
|
||||
return
|
||||
}
|
||||
|
||||
items := []string{}
|
||||
itemDescriptions := make(map[string]string)
|
||||
|
||||
util.ForEach(luaCompItems.AsTable(), func(lkey rt.Value, lval rt.Value) {
|
||||
if keytyp := lkey.Type(); keytyp == rt.StringType {
|
||||
// ['--flag'] = {'description', '--flag-alias'}
|
||||
itemName, ok := lkey.TryString()
|
||||
vlTbl, okk := lval.TryTable()
|
||||
if !ok && !okk {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
|
||||
items = append(items, itemName)
|
||||
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
||||
if !ok {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
itemDescriptions[itemName] = itemDescription
|
||||
} else if keytyp == rt.IntType {
|
||||
vlStr, ok := lval.TryString()
|
||||
if !ok {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
items = append(items, vlStr)
|
||||
} else {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
var dispType readline.TabDisplayType
|
||||
switch luaCompType.AsString() {
|
||||
case "grid": dispType = readline.TabDisplayGrid
|
||||
case "list": dispType = readline.TabDisplayList
|
||||
// need special cases, will implement later
|
||||
//case "map": dispType = readline.TabDisplayMap
|
||||
}
|
||||
|
||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
||||
DisplayType: dispType,
|
||||
Descriptions: itemDescriptions,
|
||||
Suggestions: items,
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
var dispType readline.TabDisplayType
|
||||
switch luaCompType.AsString() {
|
||||
case "grid": dispType = readline.TabDisplayGrid
|
||||
case "list": dispType = readline.TabDisplayList
|
||||
// need special cases, will implement later
|
||||
//case "map": dispType = readline.TabDisplayMap
|
||||
}
|
||||
|
||||
if len(compGroup) == 0 {
|
||||
completions, p := fileComplete(query, ctx, fields)
|
||||
fcompGroup := []*readline.CompletionGroup{{
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
Suggestions: completions,
|
||||
}}
|
||||
compGroups = append(compGroups, &readline.CompletionGroup{
|
||||
DisplayType: dispType,
|
||||
Descriptions: itemDescriptions,
|
||||
Suggestions: items,
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
})
|
||||
})
|
||||
|
||||
return p, fcompGroup
|
||||
}
|
||||
}
|
||||
return "", compGroup
|
||||
return pfx, compGroups
|
||||
}
|
||||
|
||||
return &lineReader{
|
||||
|
|
Loading…
Reference in New Issue