2021-10-16 15:31:01 +00:00
|
|
|
package main
|
|
|
|
|
2021-11-22 16:24:31 +00:00
|
|
|
import (
|
2022-02-27 23:21:21 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2021-11-22 16:24:31 +00:00
|
|
|
"strings"
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
"hilbish/util"
|
|
|
|
|
2022-02-27 23:21:21 +00:00
|
|
|
"github.com/maxlandon/readline"
|
2022-04-04 10:40:02 +00:00
|
|
|
rt "github.com/arnodel/golua/runtime"
|
2021-11-22 16:24:31 +00:00
|
|
|
)
|
2021-05-17 23:03:56 +00:00
|
|
|
|
2021-12-06 21:21:31 +00:00
|
|
|
type lineReader struct {
|
2022-02-27 23:21:21 +00:00
|
|
|
rl *readline.Instance
|
2021-05-17 23:03:56 +00:00
|
|
|
}
|
2022-03-05 19:57:46 +00:00
|
|
|
var fileHist *fileHistory
|
2022-04-04 10:40:02 +00:00
|
|
|
var hinter *rt.Closure
|
|
|
|
var highlighter *rt.Closure
|
2021-05-17 23:03:56 +00:00
|
|
|
|
2022-03-06 18:38:27 +00:00
|
|
|
func newLineReader(prompt string, noHist bool) *lineReader {
|
2022-02-27 23:21:21 +00:00
|
|
|
rl := readline.NewInstance()
|
2022-03-06 18:38:27 +00:00
|
|
|
// we don't mind hilbish.read rl instances having completion,
|
|
|
|
// but it cant have shared history
|
|
|
|
if !noHist {
|
2022-03-13 01:43:02 +00:00
|
|
|
fileHist = newFileHistory()
|
2022-03-19 17:24:12 +00:00
|
|
|
rl.SetHistoryCtrlR("History", fileHist)
|
2022-03-06 18:38:27 +00:00
|
|
|
rl.HistoryAutoWrite = false
|
2022-03-05 01:55:37 +00:00
|
|
|
}
|
2022-03-02 02:00:46 +00:00
|
|
|
rl.ShowVimMode = false
|
|
|
|
rl.ViModeCallback = func(mode readline.ViMode) {
|
|
|
|
modeStr := ""
|
|
|
|
switch mode {
|
|
|
|
case readline.VimKeys: modeStr = "normal"
|
|
|
|
case readline.VimInsert: modeStr = "insert"
|
|
|
|
case readline.VimDelete: modeStr = "delete"
|
|
|
|
case readline.VimReplaceOnce:
|
|
|
|
case readline.VimReplaceMany: modeStr = "replace"
|
|
|
|
}
|
|
|
|
setVimMode(modeStr)
|
|
|
|
}
|
2022-03-13 20:44:11 +00:00
|
|
|
rl.ViActionCallback = func(action readline.ViAction, args []string) {
|
|
|
|
actionStr := ""
|
|
|
|
switch action {
|
|
|
|
case readline.VimActionPaste: actionStr = "paste"
|
|
|
|
case readline.VimActionYank: actionStr = "yank"
|
|
|
|
}
|
|
|
|
hooks.Em.Emit("hilbish.vimAction", actionStr, args)
|
|
|
|
}
|
2022-03-26 21:34:09 +00:00
|
|
|
rl.HintText = func(line []rune, pos int) []rune {
|
2022-04-04 10:40:02 +00:00
|
|
|
if hinter == nil {
|
2022-03-26 22:24:49 +00:00
|
|
|
return []rune{}
|
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
retVal, err := rt.Call1(l.MainThread(), rt.FunctionValue(highlighter),
|
|
|
|
rt.StringValue(string(line)), rt.IntValue(int64(pos)))
|
2022-03-26 21:34:09 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return []rune{}
|
|
|
|
}
|
|
|
|
|
|
|
|
hintText := ""
|
2022-04-04 10:40:02 +00:00
|
|
|
if luaStr, ok := retVal.TryString(); ok {
|
|
|
|
hintText = luaStr
|
2022-03-26 21:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return []rune(hintText)
|
|
|
|
}
|
2022-03-26 22:25:19 +00:00
|
|
|
rl.SyntaxHighlighter = func(line []rune) string {
|
2022-04-04 10:40:02 +00:00
|
|
|
if highlighter == nil {
|
2022-03-26 22:25:19 +00:00
|
|
|
return string(line)
|
|
|
|
}
|
2022-04-04 10:40:02 +00:00
|
|
|
retVal, err := rt.Call1(l.MainThread(), rt.FunctionValue(highlighter),
|
|
|
|
rt.StringValue(string(line)))
|
2022-03-26 22:25:19 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return string(line)
|
|
|
|
}
|
|
|
|
|
|
|
|
highlighted := ""
|
2022-04-04 10:40:02 +00:00
|
|
|
if luaStr, ok := retVal.TryString(); ok {
|
|
|
|
highlighted = luaStr
|
2022-03-26 22:25:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return highlighted
|
|
|
|
}
|
2022-02-27 23:21:21 +00:00
|
|
|
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
|
|
|
|
ctx := string(line)
|
2021-11-22 16:24:31 +00:00
|
|
|
var completions []string
|
2022-03-05 19:59:00 +00:00
|
|
|
|
|
|
|
var compGroup []*readline.CompletionGroup
|
2022-02-27 23:21:21 +00:00
|
|
|
|
2021-11-22 16:24:31 +00:00
|
|
|
ctx = strings.TrimLeft(ctx, " ")
|
2021-11-24 00:09:07 +00:00
|
|
|
if len(ctx) == 0 {
|
2022-02-27 23:21:21 +00:00
|
|
|
return "", compGroup
|
2021-11-24 00:09:07 +00:00
|
|
|
}
|
2022-02-27 23:21:21 +00:00
|
|
|
|
2021-11-23 02:52:58 +00:00
|
|
|
fields := strings.Split(ctx, " ")
|
2021-11-22 16:24:31 +00:00
|
|
|
if len(fields) == 0 {
|
2022-02-27 23:21:21 +00:00
|
|
|
return "", compGroup
|
2021-11-22 16:24:31 +00:00
|
|
|
}
|
2022-02-27 23:21:21 +00:00
|
|
|
query := fields[len(fields) - 1]
|
2021-11-22 16:24:31 +00:00
|
|
|
|
2021-12-15 00:54:23 +00:00
|
|
|
ctx = aliases.Resolve(ctx)
|
2022-03-05 19:59:00 +00:00
|
|
|
|
2021-11-22 16:24:31 +00:00
|
|
|
if len(fields) == 1 {
|
2022-03-05 19:59:00 +00:00
|
|
|
completions, prefix := binaryComplete(query, ctx, fields)
|
2022-03-05 12:58:12 +00:00
|
|
|
|
2022-03-05 19:59:00 +00:00
|
|
|
compGroup = append(compGroup, &readline.CompletionGroup{
|
|
|
|
TrimSlash: false,
|
|
|
|
NoSpace: true,
|
|
|
|
Suggestions: completions,
|
|
|
|
})
|
|
|
|
|
|
|
|
return prefix, compGroup
|
2021-11-22 16:24:31 +00:00
|
|
|
} else {
|
|
|
|
if completecb, ok := luaCompletions["command." + fields[0]]; ok {
|
2022-04-04 10:40:02 +00:00
|
|
|
luaFields := rt.NewTable()
|
|
|
|
for i, f := range fields {
|
|
|
|
luaFields.Set(rt.IntValue(int64(i + 1)), rt.StringValue(f))
|
2022-03-05 19:59:00 +00:00
|
|
|
}
|
2022-04-04 10:40:02 +00:00
|
|
|
|
|
|
|
// 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))
|
2021-11-22 16:24:31 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2022-02-27 23:21:21 +00:00
|
|
|
return "", compGroup
|
2021-11-22 16:24:31 +00:00
|
|
|
}
|
|
|
|
|
2022-03-05 19:59:00 +00:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
*/
|
2022-04-04 10:40:02 +00:00
|
|
|
if cmpTbl, ok := luacompleteTable.TryTable(); ok {
|
|
|
|
nextVal := rt.NilValue
|
|
|
|
for {
|
|
|
|
next, val, ok := cmpTbl.Next(nextVal)
|
|
|
|
if next == rt.NilValue {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
nextVal = next
|
|
|
|
|
|
|
|
_, 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
|
|
|
|
itemDescriptions := make(map[string]string)
|
|
|
|
nxVal := rt.NilValue
|
|
|
|
for {
|
|
|
|
nx, vl, _ := compItems.Next(nxVal)
|
|
|
|
if nx == rt.NilValue {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
nxVal = nx
|
|
|
|
|
|
|
|
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
|
2021-11-22 16:24:31 +00:00
|
|
|
}
|
2022-04-04 10:40:02 +00:00
|
|
|
items = append(items, nxStr)
|
|
|
|
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
|
|
|
if !ok {
|
|
|
|
// TODO: error
|
|
|
|
continue
|
2021-11-22 16:24:31 +00:00
|
|
|
}
|
2022-04-04 10:40:02 +00:00
|
|
|
itemDescriptions[nxStr] = itemDescription
|
|
|
|
} else if tstr == rt.IntType {
|
|
|
|
vlStr, okk := vl.TryString()
|
|
|
|
if !okk {
|
|
|
|
// TODO: error
|
|
|
|
continue
|
2021-11-22 16:24:31 +00:00
|
|
|
}
|
2022-04-04 10:40:02 +00:00
|
|
|
items = append(items, vlStr)
|
|
|
|
} else {
|
|
|
|
// TODO: error
|
|
|
|
continue
|
2021-11-22 16:24:31 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-04 10:40:02 +00:00
|
|
|
|
|
|
|
var dispType readline.TabDisplayType
|
|
|
|
switch compType {
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
2021-11-22 16:24:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-05 19:59:00 +00:00
|
|
|
if len(compGroup) == 0 {
|
2022-02-27 23:21:21 +00:00
|
|
|
completions = fileComplete(query, ctx, fields)
|
2022-03-05 19:59:00 +00:00
|
|
|
compGroup = append(compGroup, &readline.CompletionGroup{
|
|
|
|
TrimSlash: false,
|
|
|
|
NoSpace: true,
|
|
|
|
Suggestions: completions,
|
|
|
|
})
|
2021-11-22 16:24:31 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-27 23:21:21 +00:00
|
|
|
return "", compGroup
|
2021-11-22 16:24:31 +00:00
|
|
|
}
|
2021-05-17 23:03:56 +00:00
|
|
|
|
2021-12-06 21:21:31 +00:00
|
|
|
return &lineReader{
|
2022-02-27 23:21:21 +00:00
|
|
|
rl,
|
2021-05-17 23:03:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-06 21:21:31 +00:00
|
|
|
func (lr *lineReader) Read() (string, error) {
|
2022-02-27 23:27:18 +00:00
|
|
|
hooks.Em.Emit("command.precmd", nil)
|
2022-02-27 23:21:21 +00:00
|
|
|
s, err := lr.rl.Readline()
|
|
|
|
// this is so dumb
|
|
|
|
if err == readline.EOF {
|
2022-03-02 02:00:46 +00:00
|
|
|
fmt.Println("")
|
2022-02-27 23:21:21 +00:00
|
|
|
return "", io.EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
return s, err // might get another error
|
2021-05-17 23:03:56 +00:00
|
|
|
}
|
|
|
|
|
2022-03-06 21:20:41 +00:00
|
|
|
func (lr *lineReader) SetPrompt(p string) {
|
|
|
|
halfPrompt := strings.Split(p, "\n")
|
2022-02-27 23:21:21 +00:00
|
|
|
if len(halfPrompt) > 1 {
|
2022-03-05 01:16:45 +00:00
|
|
|
lr.rl.Multiline = true
|
2022-02-27 23:21:21 +00:00
|
|
|
lr.rl.SetPrompt(strings.Join(halfPrompt[:len(halfPrompt) - 1], "\n"))
|
|
|
|
lr.rl.MultilinePrompt = halfPrompt[len(halfPrompt) - 1:][0]
|
|
|
|
} else {
|
2022-03-05 01:16:45 +00:00
|
|
|
lr.rl.Multiline = false
|
2022-03-13 20:42:14 +00:00
|
|
|
lr.rl.MultilinePrompt = ""
|
2022-03-06 21:20:41 +00:00
|
|
|
lr.rl.SetPrompt(p)
|
2022-02-27 23:21:21 +00:00
|
|
|
}
|
2022-03-06 21:20:41 +00:00
|
|
|
if initialized && !running {
|
2022-03-02 02:00:46 +00:00
|
|
|
lr.rl.RefreshPromptInPlace("")
|
|
|
|
}
|
2021-05-17 23:03:56 +00:00
|
|
|
}
|
|
|
|
|
2022-04-13 14:13:46 +00:00
|
|
|
func (lr *lineReader) SetRightPrompt(p string) {
|
|
|
|
lr.rl.SetRightPrompt(p)
|
|
|
|
if initialized && !running {
|
|
|
|
lr.rl.RefreshPromptInPlace("")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-06 21:21:31 +00:00
|
|
|
func (lr *lineReader) AddHistory(cmd string) {
|
2022-03-05 19:57:46 +00:00
|
|
|
fileHist.Write(cmd)
|
2021-05-17 23:03:56 +00:00
|
|
|
}
|
|
|
|
|
2021-12-06 21:21:31 +00:00
|
|
|
func (lr *lineReader) ClearInput() {
|
2022-02-27 23:21:21 +00:00
|
|
|
return
|
2021-06-09 01:00:01 +00:00
|
|
|
}
|
|
|
|
|
2021-12-06 21:21:31 +00:00
|
|
|
func (lr *lineReader) Resize() {
|
2022-02-27 23:21:21 +00:00
|
|
|
return
|
2021-11-22 15:41:27 +00:00
|
|
|
}
|
2022-01-27 21:02:21 +00:00
|
|
|
|
|
|
|
// lua module
|
2022-04-04 10:40:02 +00:00
|
|
|
func (lr *lineReader) Loader(rtm *rt.Runtime) *rt.Table {
|
|
|
|
lrLua := map[string]util.LuaExport{
|
|
|
|
"add": {lr.luaAddHistory, 1, false},
|
|
|
|
"all": {lr.luaAllHistory, 0, false},
|
|
|
|
"clear": {lr.luaClearHistory, 0, false},
|
|
|
|
"get": {lr.luaGetHistory, 1, false},
|
|
|
|
"size": {lr.luaSize, 0, false},
|
2022-01-27 21:02:21 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
mod := rt.NewTable()
|
|
|
|
util.SetExports(rtm, mod, lrLua)
|
2022-01-27 21:02:21 +00:00
|
|
|
|
|
|
|
return mod
|
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (lr *lineReader) luaAddHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cmd, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-01-27 21:02:21 +00:00
|
|
|
lr.AddHistory(cmd)
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-27 21:02:21 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (lr *lineReader) luaSize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
return c.PushingNext1(t.Runtime, rt.IntValue(int64(fileHist.Len()))), nil
|
2022-01-27 21:02:21 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (lr *lineReader) luaGetHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
idx, err := c.IntArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-05 19:57:46 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
cmd, _ := fileHist.GetLine(int(idx))
|
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
|
2022-01-27 21:02:21 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (lr *lineReader) luaAllHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
tbl := rt.NewTable()
|
2022-03-05 19:57:46 +00:00
|
|
|
size := fileHist.Len()
|
|
|
|
|
|
|
|
for i := 1; i < size; i++ {
|
|
|
|
cmd, _ := fileHist.GetLine(i)
|
2022-04-04 10:40:02 +00:00
|
|
|
tbl.Set(rt.IntValue(int64(i)), rt.StringValue(cmd))
|
2022-03-05 19:57:46 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.PushingNext1(t.Runtime, rt.TableValue(tbl)), nil
|
2022-01-27 21:02:21 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
func (lr *lineReader) luaClearHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-03-22 23:01:29 +00:00
|
|
|
fileHist.clear()
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), nil
|
2022-01-27 21:02:21 +00:00
|
|
|
}
|