mirror of https://github.com/Hilbis/Hilbish
311 lines
7.3 KiB
Go
311 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"strings"
|
|
"os"
|
|
|
|
"hilbish/util"
|
|
|
|
rt "github.com/arnodel/golua/runtime"
|
|
)
|
|
|
|
func fileComplete(query, ctx string, fields []string) ([]string, string) {
|
|
return matchPath(query)
|
|
}
|
|
|
|
func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
|
var completions []string
|
|
|
|
prefixes := []string{"./", "../", "/", "~/"}
|
|
for _, prefix := range prefixes {
|
|
if strings.HasPrefix(query, prefix) {
|
|
fileCompletions, filePref := matchPath(query)
|
|
if len(fileCompletions) != 0 {
|
|
for _, f := range fileCompletions {
|
|
fullPath, _ := filepath.Abs(expandHome(query + strings.TrimPrefix(f, filePref)))
|
|
if err := findExecutable(fullPath, false, true); err != nil {
|
|
continue
|
|
}
|
|
completions = append(completions, f)
|
|
}
|
|
}
|
|
return completions, filePref
|
|
}
|
|
}
|
|
|
|
// 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
|
|
err := findExecutable(match, true, false)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
// get basename from match
|
|
name := filepath.Base(match)
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
completions = removeDupes(completions)
|
|
|
|
return completions, query
|
|
}
|
|
|
|
func matchPath(query string) ([]string, string) {
|
|
var entries []string
|
|
var baseName string
|
|
|
|
path, _ := filepath.Abs(expandHome(filepath.Dir(query)))
|
|
if string(query) == "" {
|
|
// filepath base below would give us "."
|
|
// which would cause a match of only dotfiles
|
|
path, _ = filepath.Abs(".")
|
|
} else if !strings.HasSuffix(query, string(os.PathSeparator)) {
|
|
baseName = filepath.Base(query)
|
|
}
|
|
|
|
files, _ := os.ReadDir(path)
|
|
for _, file := range files {
|
|
if strings.HasPrefix(file.Name(), baseName) {
|
|
entry := file.Name()
|
|
if file.IsDir() {
|
|
entry = entry + string(os.PathSeparator)
|
|
}
|
|
entry = escapeFilename(entry)
|
|
entries = append(entries, entry)
|
|
}
|
|
}
|
|
|
|
return entries, baseName
|
|
}
|
|
|
|
func escapeFilename(fname string) string {
|
|
args := []string{
|
|
"\"", "\\\"",
|
|
"'", "\\'",
|
|
"`", "\\`",
|
|
" ", "\\ ",
|
|
"(", "\\(",
|
|
")", "\\)",
|
|
"[", "\\[",
|
|
"]", "\\]",
|
|
"$", "\\$",
|
|
"&", "\\&",
|
|
"*", "\\*",
|
|
">", "\\>",
|
|
"<", "\\<",
|
|
"|", "\\|",
|
|
}
|
|
|
|
r := strings.NewReplacer(args...)
|
|
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
|
|
}
|
|
completer, err := c.StringArg(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
query, err := c.StringArg(1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ctx, err := c.StringArg(2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fields, err := c.TableArg(3)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var completecb *rt.Closure
|
|
var ok bool
|
|
if completecb, ok = luaCompletions[completer]; !ok {
|
|
return nil, errors.New("completer " + completer + " does not exist")
|
|
}
|
|
|
|
// we must keep the holy 80 cols
|
|
completerReturn, err := rt.Call1(l.MainThread(),
|
|
rt.FunctionValue(completecb), rt.StringValue(query),
|
|
rt.StringValue(ctx), rt.TableValue(fields))
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return c.PushingNext1(t.Runtime, completerReturn), nil
|
|
}
|
|
|
|
func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
query, ctx, fds, err := getCompleteParams(t, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil
|
|
}
|
|
|
|
func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
query, ctx, fds, err := getCompleteParams(t, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil
|
|
}
|
|
|
|
func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, error) {
|
|
if err := c.CheckNArgs(3); err != nil {
|
|
return "", "", []string{}, err
|
|
}
|
|
query, err := c.StringArg(0)
|
|
if err != nil {
|
|
return "", "", []string{}, err
|
|
}
|
|
ctx, err := c.StringArg(1)
|
|
if err != nil {
|
|
return "", "", []string{}, err
|
|
}
|
|
fields, err := c.TableArg(2)
|
|
if err != nil {
|
|
return "", "", []string{}, err
|
|
}
|
|
|
|
var fds []string
|
|
util.ForEach(fields, func(k rt.Value, v rt.Value) {
|
|
if v.Type() == rt.StringType {
|
|
fds = append(fds, v.AsString())
|
|
}
|
|
})
|
|
|
|
return query, ctx, fds, err
|
|
}
|