feat: add full tab completion to alternate readline lib

windows-fixes
TorchedSammy 2022-02-27 19:13:41 -04:00
parent d9d2152e04
commit 311d7d56bd
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
1 changed files with 170 additions and 2 deletions

View File

@ -7,9 +7,11 @@ package main
// this is hilbiline's, as is obvious by the filename
import (
"io"
"strings"
"fmt"
"io"
"path/filepath"
"strings"
"os"
"github.com/maxlandon/readline"
"github.com/yuin/gopher-lua"
@ -23,6 +25,172 @@ type lineReader struct {
func newLineReader(prompt string) *lineReader {
rl := readline.NewInstance()
rl.Multiline = true
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
ctx := string(line)
var completions []string
compGroup := []*readline.CompletionGroup{
&readline.CompletionGroup{
TrimSlash: false,
NoSpace: true,
},
}
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 {
fileCompletions := fileComplete(query, ctx, fields)
if len(fileCompletions) != 0 {
for _, f := range fileCompletions {
name := strings.Replace(query + f, "~", curuser.HomeDir, 1)
if info, err := os.Stat(name); err == nil && info.Mode().Perm() & 0100 == 0 {
continue
}
completions = append(completions, f)
}
compGroup[0].Suggestions = completions
return "", compGroup
}
// filter out executables, but in path
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
// print dir to stderr for debugging
// search for an executable which matches our query string
if matches, err := filepath.Glob(filepath.Join(dir, query + "*")); err == nil {
// get basename from matches
for _, match := range matches {
// check if we have execute permissions for our match
if info, err := os.Stat(match); err == nil && info.Mode().Perm() & 0100 == 0 {
continue
}
// get basename from match
name := filepath.Base(match)
// print name to stderr for debugging
// add basename to completions
completions = append(completions, name)
}
}
}
// add lua registered commands to completions
for cmdName := range commands {
if strings.HasPrefix(cmdName, query) {
completions = append(completions, cmdName)
}
}
compGroup[0].Suggestions = completions
return query, compGroup
} else {
if completecb, ok := luaCompletions["command." + fields[0]]; ok {
err := l.CallByParam(lua.P{
Fn: completecb,
NRet: 1,
Protect: true,
})
if err != nil {
return "", compGroup
}
luacompleteTable := l.Get(-1)
l.Pop(1)
if cmpTbl, ok := luacompleteTable.(*lua.LTable); ok {
cmpTbl.ForEach(func(key lua.LValue, value lua.LValue) {
// if key is a number (index), we just check and complete that
if key.Type() == lua.LTNumber {
// if we have only 2 fields then this is fine
if len(fields) == 2 {
if strings.HasPrefix(value.String(), fields[1]) {
completions = append(completions, value.String())
}
}
} else if key.Type() == lua.LTString {
if len(fields) == 2 {
if strings.HasPrefix(key.String(), fields[1]) {
completions = append(completions, key.String())
}
} else {
// if we have more than 2 fields, we need to check if the key matches
// the current field and if it does, we need to check if the value is a string
// or table (nested sub completions)
if key.String() == fields[1] {
// if value is a table, we need to iterate over it
// and add each value to completions
// check if value is either a table or function
if value.Type() == lua.LTTable {
valueTbl := value.(*lua.LTable)
valueTbl.ForEach(func(key lua.LValue, value lua.LValue) {
val := value.String()
if val == "<file>" {
// complete files
completions = append(completions, fileComplete(query, ctx, fields)...)
} else {
if strings.HasPrefix(val, query) {
completions = append(completions, val)
}
}
})
} else if value.Type() == lua.LTFunction {
// if value is a function, we need to call it
// and add each value to completions
// completionsCtx is the context we pass to the function,
// removing 2 fields from the fields array
completionsCtx := strings.Join(fields[2:], " ")
err := l.CallByParam(lua.P{
Fn: value,
NRet: 1,
Protect: true,
}, lua.LString(query), lua.LString(completionsCtx))
if err != nil {
return
}
luacompleteTable := l.Get(-1)
l.Pop(1)
// just check if its actually a table and add it to the completions
if cmpTbl, ok := luacompleteTable.(*lua.LTable); ok {
cmpTbl.ForEach(func(key lua.LValue, value lua.LValue) {
val := value.String()
if strings.HasPrefix(val, query) {
completions = append(completions, val)
}
})
}
} else {
// throw lua error
// complete.cmdname: error message...
l.RaiseError("complete." + fields[0] + ": completion value is not a table or function")
}
}
}
}
})
}
}
if len(completions) == 0 {
completions = fileComplete(query, ctx, fields)
}
}
compGroup[0].Suggestions = completions
return "", compGroup
}
return &lineReader{
rl,