Compare commits

...

13 Commits

Author SHA1 Message Date
TorchedSammy f3f49fc398
docs: make changelog up to date 2022-03-05 21:34:59 -04:00
TorchedSammy 893d72a236
chore: prepare for 1.0 release 2022-03-05 21:29:38 -04:00
TorchedSammy 0637f2763b
fix: dont write history automatically with rl library 2022-03-05 21:29:13 -04:00
TorchedSammy 1cb536b1ac
fix: write to bottom of history file instead of at top 2022-03-05 21:26:03 -04:00
TorchedSammy 6740e012a5
fix: finish properly when command exit is successful after contine prompt 2022-03-05 21:25:32 -04:00
TorchedSammy b1ad90443e docs: [ci] generate new docs 2022-03-05 20:13:15 +00:00
TorchedSammy 76c94bfcce
docs: fix docs for hilbish.complete 2022-03-05 16:12:46 -04:00
TorchedSammy 0ed365170c
refactor!: completion api, add hilbish.completion interface
this is a pretty big commit which mainly contains a refactor
and breaking change to how command completions are done.

before that, a hilbish.completion interface has been added
which for now just has 2 functions (`files` and `bins`)
for completions of normal files and executables.

hilbish.complete is now expected to return a table of
"completions groups," which are as the name suggests a group
for a completion. a completion group is a table which has
the fields `type`, which can be either `list` or `grid`,
and `items`, being an array (or string keyed table) of items

if an item is string keyed the item itself is the key name
and the value is a table with the first value in it being the
description for the item. this description is only applied
with the list type.

this is probably the longest commit message ive written
2022-03-05 15:59:00 -04:00
TorchedSammy 70724ec015
feat: make hilbish.history functional for go readline 2022-03-05 15:57:46 -04:00
TorchedSammy f1dfd59c4c
fix: add back prompt global var
fixes an issue with multiline prompt overriding user's prompt
permanently
2022-03-05 15:46:38 -04:00
TorchedSammy a0dff5babf
fix: remove print in history handler 2022-03-05 15:23:17 -04:00
TorchedSammy 058d6ac456
chore: update readline 2022-03-05 14:40:38 -04:00
TorchedSammy 9f206ebed0
fix: export hilbish.complete 2022-03-05 11:38:33 -04:00
12 changed files with 281 additions and 163 deletions

View File

@ -1,6 +1,6 @@
# 🎀 Changelog # 🎀 Changelog
## Unreleased ## [1.0.0] - 2021-03-04
### Added ### Added
- MacOS is now officialy supported, default compile time vars have been added - MacOS is now officialy supported, default compile time vars have been added
for it for it
@ -18,6 +18,7 @@ it finds the path to `binName` in $PATH
(like it always was) or Vim via `hilbish.inputMode()` (like it always was) or Vim via `hilbish.inputMode()`
- Changing Vim mode throws a `hilbish.vimMode` hook - Changing Vim mode throws a `hilbish.vimMode` hook
- The current Vim mode is also accessible with the `hilbish.vimMode` property - The current Vim mode is also accessible with the `hilbish.vimMode` property
- Print errors in `hilbish.timeout()` and `hilbish.goro()` callbacks
### Fixed ### Fixed
- Tab completion for executables - Tab completion for executables
@ -31,6 +32,7 @@ it finds the path to `binName` in $PATH
- Alias expansion with quotes - Alias expansion with quotes
- Add full command to history in the case of incomplete input - Add full command to history in the case of incomplete input
- `hilbish.exec()` now has a windows substitute - `hilbish.exec()` now has a windows substitute
- Fixed case of successful command after prompted for more input not writing to history
### Changed ### Changed
- The minimal config is truly minimal now - The minimal config is truly minimal now
@ -53,6 +55,30 @@ as it functions the same but is OS agnostic
- `hilbish.flag()` has been removed - `hilbish.flag()` has been removed
- `~/.hprofile.lua` has been removed, instead check in your config if `hilbish.login` - `~/.hprofile.lua` has been removed, instead check in your config if `hilbish.login`
is true is true
- `hilbish.complete()` has had a slight refactor to fit with the new readline library.
It now expects a table of "completion groups" which are just tables with the
`type` and `items` keys. Here is a (more or less) complete example of how it works now:
```lua
hilbish.complete('command.git', function()
return {
{
items = {
'add',
'clone'
},
type = 'grid'
},
{
items = {
['--git-dir'] = {'Description of flag'},
'-c'
},
type = 'list'
}
}
end)
```
Completer functions are now also expected to handle subcommands/subcompletions
## [0.7.1] - 2021-11-22 ## [0.7.1] - 2021-11-22
### Fixed ### Fixed
@ -341,6 +367,7 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish. First "stable" release of Hilbish.
[0.7.1]: https://github.com/Rosettea/Hilbish/compare/v0.7.1...v1.0.0
[0.7.1]: https://github.com/Rosettea/Hilbish/compare/v0.7.0...v0.7.1 [0.7.1]: https://github.com/Rosettea/Hilbish/compare/v0.7.0...v0.7.1
[0.7.0]: https://github.com/Rosettea/Hilbish/compare/v0.6.1...v0.7.0 [0.7.0]: https://github.com/Rosettea/Hilbish/compare/v0.6.1...v0.7.0
[0.6.1]: https://github.com/Rosettea/Hilbish/compare/v0.6.0...v0.6.1 [0.6.1]: https://github.com/Rosettea/Hilbish/compare/v0.6.0...v0.6.1

62
api.go
View File

@ -23,6 +23,7 @@ import (
var exports = map[string]lua.LGFunction { var exports = map[string]lua.LGFunction {
"alias": hlalias, "alias": hlalias,
"appendPath": hlappendPath, "appendPath": hlappendPath,
"complete": hlcomplete,
"cwd": hlcwd, "cwd": hlcwd,
"exec": hlexec, "exec": hlexec,
"goro": hlgoro, "goro": hlgoro,
@ -74,6 +75,7 @@ The nice lil shell for {blue}Lua{reset} fanatics!
util.Document(L, hshuser, "User directories to store configs and/or modules.") util.Document(L, hshuser, "User directories to store configs and/or modules.")
L.SetField(mod, "userDir", hshuser) L.SetField(mod, "userDir", hshuser)
// hilbish.os table
hshos := L.NewTable() hshos := L.NewTable()
info, _ := osinfo.GetOSInfo() info, _ := osinfo.GetOSInfo()
@ -94,11 +96,63 @@ The nice lil shell for {blue}Lua{reset} fanatics!
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
hshcomp := L.NewTable()
util.SetField(L, hshcomp, "files", L.NewFunction(luaFileComplete), "Completer for files")
util.SetField(L, hshcomp, "bins", L.NewFunction(luaBinaryComplete), "Completer for executables/binaries")
util.Document(L, hshcomp, "Completions interface for Hilbish.")
L.SetField(mod, "completion", hshcomp)
L.Push(mod) L.Push(mod)
return 1 return 1
} }
func luaFileComplete(L *lua.LState) int {
query := L.CheckString(1)
ctx := L.CheckString(2)
fields := L.CheckTable(3)
var fds []string
fields.ForEach(func(k lua.LValue, v lua.LValue) {
fds = append(fds, v.String())
})
completions := fileComplete(query, ctx, fds)
luaComps := L.NewTable()
for _, comp := range completions {
luaComps.Append(lua.LString(comp))
}
L.Push(luaComps)
return 1
}
func luaBinaryComplete(L *lua.LState) int {
query := L.CheckString(1)
ctx := L.CheckString(2)
fields := L.CheckTable(3)
var fds []string
fields.ForEach(func(k lua.LValue, v lua.LValue) {
fds = append(fds, v.String())
})
completions, _ := binaryComplete(query, ctx, fds)
luaComps := L.NewTable()
for _, comp := range completions {
luaComps.Append(lua.LString(comp))
}
L.Push(luaComps)
return 1
}
func setVimMode(mode string) { func setVimMode(mode string) {
hooks.Em.Emit("hilbish.vimMode", mode) hooks.Em.Emit("hilbish.vimMode", mode)
util.SetField(l, hshMod, "vimMode", lua.LString(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)") util.SetField(l, hshMod, "vimMode", lua.LString(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)")
@ -174,7 +228,7 @@ These will be formatted and replaced with the appropriate values.
--- @param str string --- @param str string
*/ */
func hlprompt(L *lua.LState) int { func hlprompt(L *lua.LState) int {
prompt := L.CheckString(1) prompt = L.CheckString(1)
lr.SetPrompt(fmtPrompt(prompt)) lr.SetPrompt(fmtPrompt(prompt))
return 0 return 0
@ -346,8 +400,10 @@ func hlinterval(L *lua.LState) int {
// Registers a completion handler for `scope`. // Registers a completion handler for `scope`.
// A `scope` is currently only expected to be `command.<cmd>`, // A `scope` is currently only expected to be `command.<cmd>`,
// replacing <cmd> with the name of the command (for example `command.git`). // replacing <cmd> with the name of the command (for example `command.git`).
// `cb` must be a function that returns a table of the entries to complete. // `cb` must be a function that returns a table of "completion groups."
// Nested tables will be used as sub-completions. // A completion group is a table with the keys `items` and `type`.
// `items` being a table of items and `type` being the display type of
// `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(L *lua.LState) int {

View File

@ -23,6 +23,55 @@ func fileComplete(query, ctx string, fields []string) []string {
return completions return completions
} }
func binaryComplete(query, ctx string, fields []string) ([]string, string) {
var completions []string
prefixes := []string{"./", "../", "/", "~/"}
for _, prefix := range prefixes {
if strings.HasPrefix(query, prefix) {
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)
}
}
return completions, ""
}
}
// 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)
// 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)
}
}
return completions, query
}
func matchPath(path, pref string) ([]string, error) { func matchPath(path, pref string) ([]string, error) {
var entries []string var entries []string
matches, err := filepath.Glob(path + "*") matches, err := filepath.Glob(path + "*")

View File

@ -5,8 +5,10 @@ appendPath(dir) > Appends `dir` to $PATH
complete(scope, cb) > Registers a completion handler for `scope`. complete(scope, cb) > Registers a completion handler for `scope`.
A `scope` is currently only expected to be `command.<cmd>`, A `scope` is currently only expected to be `command.<cmd>`,
replacing <cmd> with the name of the command (for example `command.git`). replacing <cmd> with the name of the command (for example `command.git`).
`cb` must be a function that returns a table of the entries to complete. `cb` must be a function that returns a table of "completion groups."
Nested tables will be used as sub-completions. A completion group is a table with the keys `items` and `type`.
`items` being a table of items and `type` being the display type of
`grid` (the normal file completion display) or `list` (with a description)
cwd() > Returns the current directory of the shell cwd() > Returns the current directory of the shell

View File

@ -14,8 +14,10 @@ function hilbish.appendPath(dir) end
--- Registers a completion handler for `scope`. --- Registers a completion handler for `scope`.
--- A `scope` is currently only expected to be `command.<cmd>`, --- A `scope` is currently only expected to be `command.<cmd>`,
--- replacing <cmd> with the name of the command (for example `command.git`). --- replacing <cmd> with the name of the command (for example `command.git`).
--- `cb` must be a function that returns a table of the entries to complete. --- `cb` must be a function that returns a table of "completion groups."
--- Nested tables will be used as sub-completions. --- A completion group is a table with the keys `items` and `type`.
--- `items` being a table of items and `type` being the display type of
--- `grid` (the normal file completion display) or `list` (with a description)
--- @param scope string --- @param scope string
--- @param cb function --- @param cb function
function hilbish.complete(scope, cb) end function hilbish.complete(scope, cb) end

View File

@ -61,6 +61,8 @@ func runInput(input, origInput string) {
} else if err != nil { } else if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
cmdFinish(1, cmdString, origInput) cmdFinish(1, cmdString, origInput)
} else {
cmdFinish(0, cmdString, origInput)
} }
break break
} }

2
go.mod
View File

@ -16,6 +16,6 @@ require (
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20211022004519-f67a49cb50f5 replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20211022004519-f67a49cb50f5
replace github.com/maxlandon/readline => github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119 replace github.com/maxlandon/readline => github.com/Rosettea/readline-1 v0.0.0-20220305123014-31d4d4214c93
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10 replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10

2
go.sum
View File

@ -2,6 +2,8 @@ github.com/Rosettea/readline-1 v0.0.0-20220302012429-9ce5d23760f7 h1:LoY+kBKqMQq
github.com/Rosettea/readline-1 v0.0.0-20220302012429-9ce5d23760f7/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs= github.com/Rosettea/readline-1 v0.0.0-20220302012429-9ce5d23760f7/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119 h1:rGsc30WTD5hk+oiXrAKsAIwZn5qBeTAdr29y3HhJh9E= github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119 h1:rGsc30WTD5hk+oiXrAKsAIwZn5qBeTAdr29y3HhJh9E=
github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs= github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/readline-1 v0.0.0-20220305123014-31d4d4214c93 h1:SmOkAEm3O7si8CURZSsSN0ZxCQ8IGiiulw8LMZ1V1Yc=
github.com/Rosettea/readline-1 v0.0.0-20220305123014-31d4d4214c93/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/readline-1 v0.1.0-beta.0.20211207003625-341c7985ad7d h1:KBttN41h/tPahmpaZavviwQ8q4rCkt5CD0HdVmfgPVA= github.com/Rosettea/readline-1 v0.1.0-beta.0.20211207003625-341c7985ad7d h1:KBttN41h/tPahmpaZavviwQ8q4rCkt5CD0HdVmfgPVA=
github.com/Rosettea/readline-1 v0.1.0-beta.0.20211207003625-341c7985ad7d/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs= github.com/Rosettea/readline-1 v0.1.0-beta.0.20211207003625-341c7985ad7d/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/readline-1 v0.1.0-beta.0.20220228022904-61f5e4493011 h1:+a61iNamZiO3Xru+l/1qtpKqqltVfWEm2r/rxH9hXxY= github.com/Rosettea/readline-1 v0.1.0-beta.0.20220228022904-61f5e4493011 h1:+a61iNamZiO3Xru+l/1qtpKqqltVfWEm2r/rxH9hXxY=

View File

@ -24,12 +24,11 @@ func newFileHistory() (*fileHistory, error) {
lines := strings.Split(string(data), "\n") lines := strings.Split(string(data), "\n")
for i, l := range lines { for i, l := range lines {
if i == len(lines) - 1 { if i == len(lines) - 1 {
println(i, l)
continue continue
} }
itms = append(itms, l) itms = append(itms, l)
} }
f, err := os.OpenFile(defaultHistPath, os.O_RDWR | os.O_CREATE, 0755) f, err := os.OpenFile(defaultHistPath, os.O_APPEND | os.O_WRONLY | os.O_CREATE, 0755)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -72,5 +71,5 @@ func (h *fileHistory) Len() int {
} }
func (h *fileHistory) Dump() interface{} { func (h *fileHistory) Dump() interface{} {
return nil return h.items
} }

View File

@ -165,6 +165,7 @@ func main() {
input: input:
for interactive { for interactive {
lr.SetPrompt(fmtPrompt(prompt))
running = false running = false
input, err := lr.Read() input, err := lr.Read()

225
rl.go
View File

@ -3,9 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"path/filepath"
"strings" "strings"
"os"
"github.com/maxlandon/readline" "github.com/maxlandon/readline"
"github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua"
@ -14,15 +12,18 @@ import (
type lineReader struct { type lineReader struct {
rl *readline.Instance rl *readline.Instance
} }
var fileHist *fileHistory
// other gophers might hate this naming but this is local, shut up // other gophers might hate this naming but this is local, shut up
func newLineReader(prompt string) *lineReader { func newLineReader(prompt string) *lineReader {
rl := readline.NewInstance() rl := readline.NewInstance()
fileHist, err := newFileHistory() fh, err := newFileHistory()
fileHist = fh // go stupid
if err != nil { if err != nil {
panic(err) panic(err)
} }
rl.SetHistoryCtrlR("file", fileHist) rl.SetHistoryCtrlR("file", fileHist)
rl.HistoryAutoWrite = false
rl.ShowVimMode = false rl.ShowVimMode = false
rl.ViModeCallback = func(mode readline.ViMode) { rl.ViModeCallback = func(mode readline.ViMode) {
modeStr := "" modeStr := ""
@ -39,12 +40,7 @@ func newLineReader(prompt string) *lineReader {
ctx := string(line) ctx := string(line)
var completions []string var completions []string
compGroup := []*readline.CompletionGroup{ var compGroup []*readline.CompletionGroup
&readline.CompletionGroup{
TrimSlash: false,
NoSpace: true,
},
}
ctx = strings.TrimLeft(ctx, " ") ctx = strings.TrimLeft(ctx, " ")
if len(ctx) == 0 { if len(ctx) == 0 {
@ -60,60 +56,26 @@ func newLineReader(prompt string) *lineReader {
ctx = aliases.Resolve(ctx) ctx = aliases.Resolve(ctx)
if len(fields) == 1 { if len(fields) == 1 {
prefixes := []string{"./", "../", "/", "~/"} completions, prefix := binaryComplete(query, ctx, fields)
for _, prefix := range prefixes {
if strings.HasPrefix(query, prefix) {
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 compGroup = append(compGroup, &readline.CompletionGroup{
for _, dir := range filepath.SplitList(os.Getenv("PATH")) { TrimSlash: false,
// print dir to stderr for debugging NoSpace: true,
// search for an executable which matches our query string Suggestions: completions,
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 return prefix, compGroup
for cmdName := range commands {
if strings.HasPrefix(cmdName, query) {
completions = append(completions, cmdName)
}
}
compGroup[0].Suggestions = completions
return query, compGroup
} else { } else {
if completecb, ok := luaCompletions["command." + fields[0]]; ok { if completecb, ok := luaCompletions["command." + fields[0]]; ok {
luaFields := l.NewTable()
for _, f := range fields {
luaFields.Append(lua.LString(f))
}
err := l.CallByParam(lua.P{ err := l.CallByParam(lua.P{
Fn: completecb, Fn: completecb,
NRet: 1, NRet: 1,
Protect: true, Protect: true,
}) }, lua.LString(query), lua.LString(ctx), luaFields)
if err != nil { if err != nil {
return "", compGroup return "", compGroup
@ -122,88 +84,86 @@ func newLineReader(prompt string) *lineReader {
luacompleteTable := l.Get(-1) luacompleteTable := l.Get(-1)
l.Pop(1) l.Pop(1)
/*
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
*/
if cmpTbl, ok := luacompleteTable.(*lua.LTable); ok { if cmpTbl, ok := luacompleteTable.(*lua.LTable); ok {
cmpTbl.ForEach(func(key lua.LValue, value lua.LValue) { 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 key.Type() == lua.LTNumber {
// if we have only 2 fields then this is fine // completion group
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 { if value.Type() == lua.LTTable {
valueTbl := value.(*lua.LTable) luaCmpGroup := value.(*lua.LTable)
valueTbl.ForEach(func(key lua.LValue, value lua.LValue) { compType := luaCmpGroup.RawGet(lua.LString("type"))
val := value.String() compItems := luaCmpGroup.RawGet(lua.LString("items"))
if val == "<file>" { if compType.Type() != lua.LTString {
// complete files l.RaiseError("bad type name for completion (expected string, got %v)", compType.Type().String())
completions = append(completions, fileComplete(query, ctx, fields)...) }
if compItems.Type() != lua.LTTable {
l.RaiseError("bad items for completion (expected table, got %v)", compItems.Type().String())
}
var items []string
itemDescriptions := make(map[string]string)
compItems.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) {
if k.Type() == lua.LTString {
// ['--flag'] = {'description', '--flag-alias'}
itm := v.(*lua.LTable)
items = append(items, k.String())
itemDescriptions[k.String()] = itm.RawGet(lua.LNumber(1)).String()
} else { } else {
if strings.HasPrefix(val, query) { items = append(items, v.String())
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 { var dispType readline.TabDisplayType
return switch compType.String() {
} case "grid": dispType = readline.TabDisplayGrid
case "list": dispType = readline.TabDisplayList
luacompleteTable := l.Get(-1) // need special cases, will implement later
l.Pop(1) //case "map": dispType = readline.TabDisplayMap
// 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)
} }
compGroup = append(compGroup, &readline.CompletionGroup{
DisplayType: dispType,
Descriptions: itemDescriptions,
Suggestions: items,
TrimSlash: false,
NoSpace: true,
}) })
} }
} 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 { if len(compGroup) == 0 {
completions = fileComplete(query, ctx, fields) completions = fileComplete(query, ctx, fields)
compGroup = append(compGroup, &readline.CompletionGroup{
TrimSlash: false,
NoSpace: true,
Suggestions: completions,
})
} }
} }
compGroup[0].Suggestions = completions
return "", compGroup return "", compGroup
} }
@ -240,7 +200,7 @@ func (lr *lineReader) SetPrompt(prompt string) {
} }
func (lr *lineReader) AddHistory(cmd string) { func (lr *lineReader) AddHistory(cmd string) {
return fileHist.Write(cmd)
} }
func (lr *lineReader) ClearInput() { func (lr *lineReader) ClearInput() {
@ -273,18 +233,35 @@ func (lr *lineReader) luaAddHistory(l *lua.LState) int {
return 0 return 0
} }
func (lr *lineReader) luaSize(l *lua.LState) int { func (lr *lineReader) luaSize(L *lua.LState) int {
L.Push(lua.LNumber(fileHist.Len()))
return 1
}
func (lr *lineReader) luaGetHistory(L *lua.LState) int {
idx := L.CheckInt(1)
cmd, _ := fileHist.GetLine(idx)
L.Push(lua.LString(cmd))
return 0 return 0
} }
func (lr *lineReader) luaGetHistory(l *lua.LState) int { func (lr *lineReader) luaAllHistory(L *lua.LState) int {
return 0 tbl := L.NewTable()
} size := fileHist.Len()
for i := 1; i < size; i++ {
cmd, _ := fileHist.GetLine(i)
tbl.Append(lua.LString(cmd))
}
L.Push(tbl)
func (lr *lineReader) luaAllHistory(l *lua.LState) int {
return 0 return 0
} }
func (lr *lineReader) luaClearHistory(l *lua.LState) int { func (lr *lineReader) luaClearHistory(l *lua.LState) int {
return 0 return 0
} }

View File

@ -2,11 +2,12 @@ package main
// String vars that are free to be changed at compile time // String vars that are free to be changed at compile time
var ( var (
version = "v0.7.1" version = "v1.0.0"
defaultConfDir = "" // ~ will be substituted for home, path for user's default config defaultConfDir = "" // ~ will be substituted for home, path for user's default config
defaultHistDir = "" defaultHistDir = ""
commonRequirePaths = "';./libs/?/init.lua;./?/init.lua;./?/?.lua'" commonRequirePaths = "';./libs/?/init.lua;./?/init.lua;./?/?.lua'"
prompt string
multilinePrompt = "> " multilinePrompt = "> "
) )