Compare commits

...

3 Commits

Author SHA1 Message Date
TorchedSammy 10f6db20d4
feat: implement terminal 2022-03-29 08:52:26 -04:00
TorchedSammy e1d4258e07
chore: remove comment 2022-03-29 07:06:10 -04:00
TorchedSammy ead72f0a5c
feat: implement completions 2022-03-29 07:03:58 -04:00
6 changed files with 136 additions and 103 deletions

17
api.go
View File

@ -25,8 +25,8 @@ var exports = map[string]util.LuaExport{
/* /*
"alias": hlalias, "alias": hlalias,
"appendPath": hlappendPath, "appendPath": hlappendPath,
"complete": hlcomplete,
*/ */
"complete": util.LuaExport{hlcomplete, 2, false},
"cwd": util.LuaExport{hlcwd, 0, false}, "cwd": util.LuaExport{hlcwd, 0, false},
/* /*
"exec": hlexec, "exec": hlexec,
@ -121,7 +121,7 @@ Check out the {blue}{bold}guide{reset} command to get started.
util.Document(L, historyModule, "History interface for Hilbish.") util.Document(L, historyModule, "History interface for Hilbish.")
L.SetField(mod, "history", historyModule) L.SetField(mod, "history", historyModule)
// hilbish.completions table // hilbish.completion table
hshcomp := L.NewTable() hshcomp := L.NewTable()
util.SetField(L, hshcomp, "files", L.NewFunction(luaFileComplete), "Completer for files") util.SetField(L, hshcomp, "files", L.NewFunction(luaFileComplete), "Completer for files")
@ -446,6 +446,7 @@ func hlinterval(L *lua.LState) int {
L.Push(lua.LChannel(stop)) L.Push(lua.LChannel(stop))
return 1 return 1
} }
*/
// complete(scope, cb) // complete(scope, cb)
// Registers a completion handler for `scope`. // Registers a completion handler for `scope`.
@ -457,15 +458,17 @@ func hlinterval(L *lua.LState) int {
// `grid` (the normal file completion display) or `list` (with a description) // `grid` (the normal file completion display) or `list` (with a description)
// --- @param scope string // --- @param scope string
// --- @param cb function // --- @param cb function
func hlcomplete(L *lua.LState) int { func hlcomplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
scope := L.CheckString(1) scope, cb, err := util.HandleStrCallback(t, c)
cb := L.CheckFunction(2) if err != nil {
return nil, err
}
luaCompletions[scope] = cb luaCompletions[scope] = cb
return 0 return c.Next(), nil
} }
/*
// prependPath(dir) // prependPath(dir)
// Prepends `dir` to $PATH // Prepends `dir` to $PATH
// --- @param dir string // --- @param dir string

View File

@ -1,5 +1,3 @@
// The fs module provides easy and simple access to filesystem functions and other
// things, and acts an addition to the Lua standard library's I/O and fs functions.
package fs package fs
import ( import (
@ -8,6 +6,7 @@ import (
"strings" "strings"
"hilbish/util" "hilbish/util"
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib" "github.com/arnodel/golua/lib/packagelib"
) )

View File

@ -5,76 +5,78 @@ import (
"hilbish/util" "hilbish/util"
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib"
"golang.org/x/term" "golang.org/x/term"
"github.com/yuin/gopher-lua"
) )
var termState *term.State var termState *term.State
var Loader = packagelib.Loader{
func Loader(L *lua.LState) int { Load: loaderFunc,
mod := L.SetFuncs(L.NewTable(), exports) Name: "terminal",
util.Document(L, mod, "The terminal library is a simple and lower level library for certain terminal interactions.")
L.Push(mod)
return 1
} }
var exports = map[string]lua.LGFunction{ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
"setRaw": termraw, exports := map[string]util.LuaExport{
"restoreState": termrestoreState, "setRaw": util.LuaExport{termsetRaw, 0, false},
"size": termsize, "restoreState": util.LuaExport{termrestoreState, 0, false},
"saveState": termsaveState, "size": util.LuaExport{termsize, 0, false},
"saveState": util.LuaExport{termsaveState, 0, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
//util.Document(L, mod, "The terminal library is a simple and lower level library for certain terminal interactions.")
return rt.TableValue(mod), nil
} }
// size() // size()
// Gets the dimensions of the terminal. Returns a table with `width` and `height` // Gets the dimensions of the terminal. Returns a table with `width` and `height`
// Note: this is not the size in relation to the dimensions of the display // Note: this is not the size in relation to the dimensions of the display
func termsize(L *lua.LState) int { func termsize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
w, h, err := term.GetSize(int(os.Stdin.Fd())) w, h, err := term.GetSize(int(os.Stdin.Fd()))
if err != nil { if err != nil {
L.RaiseError(err.Error()) return nil, err
return 0
} }
dimensions := L.NewTable()
L.SetField(dimensions, "width", lua.LNumber(w))
L.SetField(dimensions, "height", lua.LNumber(h))
L.Push(dimensions) dimensions := rt.NewTable()
return 1 dimensions.Set(rt.StringValue("width"), rt.IntValue(int64(w)))
dimensions.Set(rt.StringValue("height"), rt.IntValue(int64(h)))
return c.PushingNext1(t.Runtime, rt.TableValue(dimensions)), nil
} }
// saveState() // saveState()
// Saves the current state of the terminal // Saves the current state of the terminal
func termsaveState(L *lua.LState) int { func termsaveState(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
state, err := term.GetState(int(os.Stdin.Fd())) state, err := term.GetState(int(os.Stdin.Fd()))
if err != nil { if err != nil {
L.RaiseError(err.Error()) return nil, err
return 0
} }
termState = state termState = state
return 0 return c.Next(), nil
} }
// restoreState() // restoreState()
// Restores the last saved state of the terminal // Restores the last saved state of the terminal
func termrestoreState(L *lua.LState) int { func termrestoreState(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
err := term.Restore(int(os.Stdin.Fd()), termState) err := term.Restore(int(os.Stdin.Fd()), termState)
if err != nil { if err != nil {
L.RaiseError(err.Error()) return nil, err
} }
return 0 return c.Next(), nil
} }
// setRaw() // setRaw()
// Puts the terminal in raw mode // Puts the terminal in raw mode
func termraw(L *lua.LState) int { func termsetRaw(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
_, err := term.MakeRaw(int(os.Stdin.Fd())) _, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil { if err != nil {
L.RaiseError(err.Error()) return nil, err
} }
return 0 return c.Next(), nil
} }

7
lua.go
View File

@ -8,9 +8,8 @@ import (
"hilbish/golibs/bait" "hilbish/golibs/bait"
"hilbish/golibs/commander" "hilbish/golibs/commander"
"hilbish/golibs/fs" "hilbish/golibs/fs"
/*
"hilbish/golibs/terminal" "hilbish/golibs/terminal"
*/
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib" "github.com/arnodel/golua/lib"
) )
@ -27,9 +26,7 @@ func luaInit() {
// Add fs and terminal module module to Lua // Add fs and terminal module module to Lua
lib.LoadLibs(l, fs.Loader) lib.LoadLibs(l, fs.Loader)
/* lib.LoadLibs(l, terminal.Loader)
l.PreloadModule("terminal", terminal.Loader)
*/
cmds := commander.New() cmds := commander.New()
// When a command from Lua is added, register it for use // When a command from Lua is added, register it for use

View File

@ -14,7 +14,6 @@ import (
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"github.com/pborman/getopt" "github.com/pborman/getopt"
"github.com/yuin/gopher-lua"
"github.com/maxlandon/readline" "github.com/maxlandon/readline"
"golang.org/x/term" "golang.org/x/term"
) )
@ -24,7 +23,7 @@ var (
lr *lineReader lr *lineReader
commands = map[string]*rt.Closure{} commands = map[string]*rt.Closure{}
luaCompletions = map[string]*lua.LFunction{} luaCompletions = map[string]*rt.Closure{}
confDir string confDir string
userDataDir string userDataDir string

109
rl.go
View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/maxlandon/readline" "github.com/maxlandon/readline"
rt "github.com/arnodel/golua/runtime"
) )
type lineReader struct { type lineReader struct {
@ -17,7 +18,6 @@ var hinter lua.LValue = lua.LNil
var highlighter lua.LValue = lua.LNil var highlighter lua.LValue = lua.LNil
*/ */
// other gophers might hate this naming but this is local, shut up
func newLineReader(prompt string, noHist bool) *lineReader { func newLineReader(prompt string, noHist bool) *lineReader {
rl := readline.NewInstance() rl := readline.NewInstance()
// we don't mind hilbish.read rl instances having completion, // we don't mind hilbish.read rl instances having completion,
@ -93,6 +93,7 @@ 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) ctx := string(line)
var completions []string var completions []string
@ -124,23 +125,20 @@ func newLineReader(prompt string, noHist bool) *lineReader {
return prefix, compGroup return prefix, compGroup
} else { } else {
if completecb, ok := luaCompletions["command." + fields[0]]; ok { if completecb, ok := luaCompletions["command." + fields[0]]; ok {
luaFields := l.NewTable() luaFields := rt.NewTable()
for _, f := range fields { for i, f := range fields {
luaFields.Append(lua.LString(f)) luaFields.Set(rt.IntValue(int64(i + 1)), rt.StringValue(f))
} }
err := l.CallByParam(lua.P{
Fn: completecb, // we must keep the holy 80 cols
NRet: 1, luacompleteTable, err := rt.Call1(l.MainThread(),
Protect: true, rt.FunctionValue(completecb), rt.StringValue(query),
}, lua.LString(query), lua.LString(ctx), luaFields) rt.StringValue(ctx), rt.TableValue(luaFields))
if err != nil { if err != nil {
return "", compGroup return "", compGroup
} }
luacompleteTable := l.Get(-1)
l.Pop(1)
/* /*
as an example with git, as an example with git,
completion table should be structured like: completion table should be structured like:
@ -165,41 +163,78 @@ func newLineReader(prompt string, noHist bool) *lineReader {
it is the responsibility of the completer it is the responsibility of the completer
to work on subcommands and subcompletions to work on subcommands and subcompletions
*/ */
/* if cmpTbl, ok := luacompleteTable.TryTable(); ok {
if cmpTbl, ok := luacompleteTable.(*lua.LTable); ok { nextVal := rt.NilValue
cmpTbl.ForEach(func(key lua.LValue, value lua.LValue) { for {
if key.Type() == lua.LTNumber { next, val, ok := cmpTbl.Next(nextVal)
// completion group if next == rt.NilValue {
if value.Type() == lua.LTTable { break
luaCmpGroup := value.(*lua.LTable)
compType := luaCmpGroup.RawGet(lua.LString("type"))
compItems := luaCmpGroup.RawGet(lua.LString("items"))
if compType.Type() != lua.LTString {
l.RaiseError("bad type name for completion (expected string, got %v)", compType.Type().String())
} }
if compItems.Type() != lua.LTTable { nextVal = next
l.RaiseError("bad items for completion (expected table, got %v)", compItems.Type().String())
_, ok = next.TryInt()
valTbl, okk := val.TryTable()
if !ok || !okk {
// TODO: error?
break
} }
luaCompType := valTbl.Get(rt.StringValue("type"))
luaCompItems := valTbl.Get(rt.StringValue("items"))
compType, ok := luaCompType.TryString()
compItems, okk := luaCompItems.TryTable()
if !ok || !okk {
// TODO: error
break
}
var items []string var items []string
itemDescriptions := make(map[string]string) itemDescriptions := make(map[string]string)
compItems.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { nxVal := rt.NilValue
if k.Type() == lua.LTString { for {
// ['--flag'] = {'description', '--flag-alias'} nx, vl, _ := compItems.Next(nxVal)
itm := v.(*lua.LTable) if nx == rt.NilValue {
items = append(items, k.String()) break
itemDescriptions[k.String()] = itm.RawGet(lua.LNumber(1)).String() }
} else { nxVal = nx
items = append(items, v.String())
if tstr := nx.Type(); tstr == rt.StringType {
// ['--flag'] = {'description', '--flag-alias'}
nxStr, ok := nx.TryString()
vlTbl, okk := vl.TryTable()
if !ok || !okk {
// TODO: error
continue
}
items = append(items, nxStr)
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
if !ok {
// TODO: error
continue
}
itemDescriptions[nxStr] = itemDescription
} else if tstr == rt.IntType {
vlStr, okk := vl.TryString()
if !okk {
// TODO: error
continue
}
items = append(items, vlStr)
} else {
// TODO: error
continue
}
} }
})
var dispType readline.TabDisplayType var dispType readline.TabDisplayType
switch compType.String() { switch compType {
case "grid": dispType = readline.TabDisplayGrid case "grid": dispType = readline.TabDisplayGrid
case "list": dispType = readline.TabDisplayList case "list": dispType = readline.TabDisplayList
// need special cases, will implement later // need special cases, will implement later
//case "map": dispType = readline.TabDisplayMap //case "map": dispType = readline.TabDisplayMap
} }
compGroup = append(compGroup, &readline.CompletionGroup{ compGroup = append(compGroup, &readline.CompletionGroup{
DisplayType: dispType, DisplayType: dispType,
Descriptions: itemDescriptions, Descriptions: itemDescriptions,
@ -209,8 +244,6 @@ func newLineReader(prompt string, noHist bool) *lineReader {
}) })
} }
} }
})
}
} }
if len(compGroup) == 0 { if len(compGroup) == 0 {
@ -223,7 +256,7 @@ func newLineReader(prompt string, noHist bool) *lineReader {
} }
} }
return "", compGroup return "", compGroup
}*/ }
return &lineReader{ return &lineReader{
rl, rl,