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.")
|
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))
|
||||||
|
|
||||||
|
|
104
complete.go
104
complete.go
|
@ -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) {
|
||||||
|
|
206
rl.go
206
rl.go
|
@ -84,151 +84,91 @@ 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"))
|
||||||
|
err := rt.Call(l.MainThread(), compHandle, []rt.Value{rt.StringValue(string(line)),
|
||||||
|
rt.IntValue(int64(pos))}, term)
|
||||||
|
|
||||||
var compGroup []*readline.CompletionGroup
|
var compGroups []*readline.CompletionGroup
|
||||||
|
if err != nil {
|
||||||
ctx = strings.TrimLeft(ctx, " ")
|
return "", compGroups
|
||||||
if len(ctx) == 0 {
|
|
||||||
return "", compGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.Split(ctx, " ")
|
luaCompGroups := term.Get(0)
|
||||||
if len(fields) == 0 {
|
luaPrefix := term.Get(1)
|
||||||
return "", compGroup
|
|
||||||
|
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 {
|
util.ForEach(groups, func(key rt.Value, val rt.Value) {
|
||||||
completions, prefix := binaryComplete(query, ctx, fields)
|
if key.Type() != rt.IntType || val.Type() != rt.TableType {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
valTbl := val.AsTable()
|
||||||
TrimSlash: false,
|
luaCompType := valTbl.Get(rt.StringValue("type"))
|
||||||
NoSpace: true,
|
luaCompItems := valTbl.Get(rt.StringValue("items"))
|
||||||
Suggestions: completions,
|
|
||||||
|
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
|
var dispType readline.TabDisplayType
|
||||||
} else {
|
switch luaCompType.AsString() {
|
||||||
if completecb, ok := luaCompletions["command." + fields[0]]; ok {
|
case "grid": dispType = readline.TabDisplayGrid
|
||||||
luaFields := rt.NewTable()
|
case "list": dispType = readline.TabDisplayList
|
||||||
for i, f := range fields {
|
// need special cases, will implement later
|
||||||
luaFields.Set(rt.IntValue(int64(i + 1)), rt.StringValue(f))
|
//case "map": dispType = readline.TabDisplayMap
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(compGroup) == 0 {
|
compGroups = append(compGroups, &readline.CompletionGroup{
|
||||||
completions, p := fileComplete(query, ctx, fields)
|
DisplayType: dispType,
|
||||||
fcompGroup := []*readline.CompletionGroup{{
|
Descriptions: itemDescriptions,
|
||||||
TrimSlash: false,
|
Suggestions: items,
|
||||||
NoSpace: true,
|
TrimSlash: false,
|
||||||
Suggestions: completions,
|
NoSpace: true,
|
||||||
}}
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return p, fcompGroup
|
return pfx, compGroups
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", compGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &lineReader{
|
return &lineReader{
|
||||||
|
|
Loading…
Reference in New Issue