2
3
зеркало из https://github.com/sammy-ette/Hilbish synced 2025-08-10 02:52:03 +00:00

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.
Этот коммит содержится в:
TorchedSammy 2022-04-22 21:16:35 -04:00
родитель 3194add3dc
Коммит abfbeb5f84
Подписано: sammyette
Идентификатор ключа GPG: 904FC49417B44DCD
3 изменённых файлов: 174 добавлений и 150 удалений

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))

Просмотреть файл

@ -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) {

102
rl.go
Просмотреть файл

@ -84,76 +84,29 @@ func newLineReader(prompt string, noHist bool) *lineReader {
return highlighted
}
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
ctx := string(line)
var compGroup []*readline.CompletionGroup
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))
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 compGroups []*readline.CompletionGroup
if err != nil {
return "", compGroup
return "", compGroups
}
/*
as an example with git,
completion table should be structured like:
{
{
items = {
'add',
'clone',
'init'
},
type = 'grid'
},
{
items = {
'-c',
'--git-dir'
},
type = 'list'
luaCompGroups := term.Get(0)
luaPrefix := term.Get(1)
if luaCompGroups.Type() != rt.TableType {
return "", compGroups
}
}
^ 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 {
groups := luaCompGroups.AsTable()
// prefix is optional
pfx, _ := luaPrefix.TryString()
util.ForEach(groups, func(key rt.Value, val rt.Value) {
if key.Type() != rt.IntType || val.Type() != rt.TableType {
return
}
@ -161,7 +114,7 @@ func newLineReader(prompt string, noHist bool) *lineReader {
luaCompType := valTbl.Get(rt.StringValue("type"))
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
}
@ -206,7 +159,7 @@ func newLineReader(prompt string, noHist bool) *lineReader {
//case "map": dispType = readline.TabDisplayMap
}
compGroup = append(compGroup, &readline.CompletionGroup{
compGroups = append(compGroups, &readline.CompletionGroup{
DisplayType: dispType,
Descriptions: itemDescriptions,
Suggestions: items,
@ -214,21 +167,8 @@ func newLineReader(prompt string, noHist bool) *lineReader {
NoSpace: true,
})
})
}
}
if len(compGroup) == 0 {
completions, p := fileComplete(query, ctx, fields)
fcompGroup := []*readline.CompletionGroup{{
TrimSlash: false,
NoSpace: true,
Suggestions: completions,
}}
return p, fcompGroup
}
}
return "", compGroup
return pfx, compGroups
}
return &lineReader{