Compare commits

..

No commits in common. "f3f49fc3988251a05363d091d7bbe2c47c9a28a0" and "59963add14c7d0134795d4f928efb75b0af1251e" have entirely different histories.

12 changed files with 163 additions and 281 deletions

View File

@ -1,6 +1,6 @@
# 🎀 Changelog
## [1.0.0] - 2021-03-04
## Unreleased
### Added
- MacOS is now officialy supported, default compile time vars have been added
for it
@ -18,7 +18,6 @@ it finds the path to `binName` in $PATH
(like it always was) or Vim via `hilbish.inputMode()`
- Changing Vim mode throws a `hilbish.vimMode` hook
- The current Vim mode is also accessible with the `hilbish.vimMode` property
- Print errors in `hilbish.timeout()` and `hilbish.goro()` callbacks
### Fixed
- Tab completion for executables
@ -32,7 +31,6 @@ it finds the path to `binName` in $PATH
- Alias expansion with quotes
- Add full command to history in the case of incomplete input
- `hilbish.exec()` now has a windows substitute
- Fixed case of successful command after prompted for more input not writing to history
### Changed
- The minimal config is truly minimal now
@ -55,30 +53,6 @@ as it functions the same but is OS agnostic
- `hilbish.flag()` has been removed
- `~/.hprofile.lua` has been removed, instead check in your config if `hilbish.login`
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
### Fixed
@ -367,7 +341,6 @@ This input for example will prompt for more input to complete:
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.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

62
api.go
View File

@ -23,7 +23,6 @@ import (
var exports = map[string]lua.LGFunction {
"alias": hlalias,
"appendPath": hlappendPath,
"complete": hlcomplete,
"cwd": hlcwd,
"exec": hlexec,
"goro": hlgoro,
@ -75,7 +74,6 @@ The nice lil shell for {blue}Lua{reset} fanatics!
util.Document(L, hshuser, "User directories to store configs and/or modules.")
L.SetField(mod, "userDir", hshuser)
// hilbish.os table
hshos := L.NewTable()
info, _ := osinfo.GetOSInfo()
@ -96,63 +94,11 @@ The nice lil shell for {blue}Lua{reset} fanatics!
util.Document(L, historyModule, "History interface for Hilbish.")
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)
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) {
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)")
@ -228,7 +174,7 @@ These will be formatted and replaced with the appropriate values.
--- @param str string
*/
func hlprompt(L *lua.LState) int {
prompt = L.CheckString(1)
prompt := L.CheckString(1)
lr.SetPrompt(fmtPrompt(prompt))
return 0
@ -400,10 +346,8 @@ func hlinterval(L *lua.LState) int {
// Registers a completion handler for `scope`.
// A `scope` is currently only expected to be `command.<cmd>`,
// replacing <cmd> with the name of the command (for example `command.git`).
// `cb` must be a function that returns a table of "completion groups."
// 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)
// `cb` must be a function that returns a table of the entries to complete.
// Nested tables will be used as sub-completions.
// --- @param scope string
// --- @param cb function
func hlcomplete(L *lua.LState) int {

View File

@ -23,55 +23,6 @@ func fileComplete(query, ctx string, fields []string) []string {
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) {
var entries []string
matches, err := filepath.Glob(path + "*")

View File

@ -5,10 +5,8 @@ appendPath(dir) > Appends `dir` to $PATH
complete(scope, cb) > Registers a completion handler for `scope`.
A `scope` is currently only expected to be `command.<cmd>`,
replacing <cmd> with the name of the command (for example `command.git`).
`cb` must be a function that returns a table of "completion groups."
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)
`cb` must be a function that returns a table of the entries to complete.
Nested tables will be used as sub-completions.
cwd() > Returns the current directory of the shell

View File

@ -14,10 +14,8 @@ function hilbish.appendPath(dir) end
--- Registers a completion handler for `scope`.
--- A `scope` is currently only expected to be `command.<cmd>`,
--- replacing <cmd> with the name of the command (for example `command.git`).
--- `cb` must be a function that returns a table of "completion groups."
--- 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)
--- `cb` must be a function that returns a table of the entries to complete.
--- Nested tables will be used as sub-completions.
--- @param scope string
--- @param cb function
function hilbish.complete(scope, cb) end

View File

@ -61,8 +61,6 @@ func runInput(input, origInput string) {
} else if err != nil {
fmt.Fprintln(os.Stderr, err)
cmdFinish(1, cmdString, origInput)
} else {
cmdFinish(0, cmdString, origInput)
}
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 github.com/maxlandon/readline => github.com/Rosettea/readline-1 v0.0.0-20220305123014-31d4d4214c93
replace github.com/maxlandon/readline => github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10

2
go.sum
View File

@ -2,8 +2,6 @@ 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-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-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/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
github.com/Rosettea/readline-1 v0.1.0-beta.0.20220228022904-61f5e4493011 h1:+a61iNamZiO3Xru+l/1qtpKqqltVfWEm2r/rxH9hXxY=

View File

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

View File

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

231
rl.go
View File

@ -3,7 +3,9 @@ package main
import (
"fmt"
"io"
"path/filepath"
"strings"
"os"
"github.com/maxlandon/readline"
"github.com/yuin/gopher-lua"
@ -12,18 +14,15 @@ import (
type lineReader struct {
rl *readline.Instance
}
var fileHist *fileHistory
// other gophers might hate this naming but this is local, shut up
func newLineReader(prompt string) *lineReader {
rl := readline.NewInstance()
fh, err := newFileHistory()
fileHist = fh // go stupid
fileHist, err := newFileHistory()
if err != nil {
panic(err)
}
rl.SetHistoryCtrlR("file", fileHist)
rl.HistoryAutoWrite = false
rl.ShowVimMode = false
rl.ViModeCallback = func(mode readline.ViMode) {
modeStr := ""
@ -40,7 +39,12 @@ func newLineReader(prompt string) *lineReader {
ctx := string(line)
var completions []string
var compGroup []*readline.CompletionGroup
compGroup := []*readline.CompletionGroup{
&readline.CompletionGroup{
TrimSlash: false,
NoSpace: true,
},
}
ctx = strings.TrimLeft(ctx, " ")
if len(ctx) == 0 {
@ -56,26 +60,60 @@ func newLineReader(prompt string) *lineReader {
ctx = aliases.Resolve(ctx)
if len(fields) == 1 {
completions, prefix := binaryComplete(query, ctx, fields)
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)
}
compGroup[0].Suggestions = completions
}
return "", compGroup
}
}
compGroup = append(compGroup, &readline.CompletionGroup{
TrimSlash: false,
NoSpace: true,
Suggestions: 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)
// print name to stderr for debugging
// add basename to completions
completions = append(completions, name)
}
}
}
return prefix, compGroup
// 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 {
luaFields := l.NewTable()
for _, f := range fields {
luaFields.Append(lua.LString(f))
}
err := l.CallByParam(lua.P{
Fn: completecb,
NRet: 1,
Protect: true,
}, lua.LString(query), lua.LString(ctx), luaFields)
})
if err != nil {
return "", compGroup
@ -84,86 +122,88 @@ func newLineReader(prompt string) *lineReader {
luacompleteTable := l.Get(-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 {
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 {
// completion group
if value.Type() == lua.LTTable {
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 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())
}
if compItems.Type() != lua.LTTable {
l.RaiseError("bad items for completion (expected table, got %v)", compItems.Type().String())
}
} else if key.Type() == lua.LTString {
if len(fields) == 2 {
if strings.HasPrefix(key.String(), fields[1]) {
completions = append(completions, key.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 {
items = append(items, v.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))
var dispType readline.TabDisplayType
switch compType.String() {
case "grid": dispType = readline.TabDisplayGrid
case "list": dispType = readline.TabDisplayList
// need special cases, will implement later
//case "map": dispType = readline.TabDisplayMap
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")
}
}
compGroup = append(compGroup, &readline.CompletionGroup{
DisplayType: dispType,
Descriptions: itemDescriptions,
Suggestions: items,
TrimSlash: false,
NoSpace: true,
})
}
}
})
}
}
if len(compGroup) == 0 {
if len(completions) == 0 {
completions = fileComplete(query, ctx, fields)
compGroup = append(compGroup, &readline.CompletionGroup{
TrimSlash: false,
NoSpace: true,
Suggestions: completions,
})
}
}
compGroup[0].Suggestions = completions
return "", compGroup
}
@ -200,7 +240,7 @@ func (lr *lineReader) SetPrompt(prompt string) {
}
func (lr *lineReader) AddHistory(cmd string) {
fileHist.Write(cmd)
return
}
func (lr *lineReader) ClearInput() {
@ -233,35 +273,18 @@ func (lr *lineReader) luaAddHistory(l *lua.LState) int {
return 0
}
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))
func (lr *lineReader) luaSize(l *lua.LState) int {
return 0
}
func (lr *lineReader) luaAllHistory(L *lua.LState) int {
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) luaGetHistory(l *lua.LState) int {
return 0
}
func (lr *lineReader) luaAllHistory(l *lua.LState) int {
return 0
}
func (lr *lineReader) luaClearHistory(l *lua.LState) int {
return 0
}

View File

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