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 # 🎀 Changelog
## [1.0.0] - 2021-03-04 ## Unreleased
### 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,7 +18,6 @@ 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
@ -32,20 +31,19 @@ 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
- Default config is no longer copied to user's config and is instead ran its location - Default config is no longer copied to user's config and is instead ran its location
#### Breaking Changes #### Breaking Changes
(there were a lot...) (there were a lot...)
- Change default SHLVL to 0 instead of 1 - Change default SHLVL to 0 instead of 1
- ~/.hilbishrc.lua will no longer be run by default, it now - ~/.hilbishrc.lua will no longer be run by default, it now
only uses the paths mentioned below. only uses the paths mentioned below.
- Changed Hilbish's config path to something more suited - Changed Hilbish's config path to something more suited
according to the OS (`$XDG_CONFIG_HOME/hilbish/init.lua` on Linux, according to the OS (`$XDG_CONFIG_HOME/hilbish/init.lua` on Linux,
`~/Library/Application Support/hilbish/init.lua` on MacOS and `~/Library/Application Support/hilbish/init.lua` on MacOS and
(`%APPDATA%/hilbish/init.lua` on Windows). Previously on Unix-like it was (`%APPDATA%/hilbish/init.lua` on Windows). Previously on Unix-like it was
`$XDG_CONFIG_HOME/hilbish/hilbishrc.lua` `$XDG_CONFIG_HOME/hilbish/hilbishrc.lua`
- The history path has been changed to a better suited path. - The history path has been changed to a better suited path.
On Linux, it is `$XDG_DATA_HOME/hilbish/.hilbish-history` and for others it is On Linux, it is `$XDG_DATA_HOME/hilbish/.hilbish-history` and for others it is
@ -55,30 +53,6 @@ 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
@ -187,9 +161,9 @@ An absolutely massive release. Probably the biggest yet, includes a bunch of fix
### Added ### Added
- `-n` flag, which checks Lua for syntax errors without running it - `-n` flag, which checks Lua for syntax errors without running it
- `exec(command)` function, acts like the `exec` builtin in sh - `exec(command)` function, acts like the `exec` builtin in sh
- Example: `exec 'awesome'` in an .xinitrc file with Hilbish as shebang - Example: `exec 'awesome'` in an .xinitrc file with Hilbish as shebang
- Commands from commander can now `return` an exit code - Commands from commander can now `return` an exit code
```lua ```lua
commander.register('false', function() commander.register('false', function()
return 1 return 1
@ -202,7 +176,7 @@ When `false` is run, it will have the exit code of `1`, this is shorter/easier t
- Recursive aliases - Recursive aliases
- At the moment this only works for the first argument - At the moment this only works for the first argument
- Hilbish can now be used with Hilbiline if compiled to do so (currently only for testing purposes) - Hilbish can now be used with Hilbiline if compiled to do so (currently only for testing purposes)
- `goro(func)` runs a `func`tion in a goroutine. With channels that gopher-lua also provides, one can do parallelism and concurrency in Lua (but go style). - `goro(func)` runs a `func`tion in a goroutine. With channels that gopher-lua also provides, one can do parallelism and concurrency in Lua (but go style).
- `coroutine` no those dont exist they dont matter `goro` is easier - `coroutine` no those dont exist they dont matter `goro` is easier
- `cd -` will change to the previous directory - `cd -` will change to the previous directory
- `hilbish.cwd()` gets the current working directory - `hilbish.cwd()` gets the current working directory
@ -241,7 +215,7 @@ When `false` is run, it will have the exit code of `1`, this is shorter/easier t
### Added ### Added
- Ctrl C in the prompt now cancels/clear input (I've needed this for so long also) - Ctrl C in the prompt now cancels/clear input (I've needed this for so long also)
- Made Hilbish act like a login shell on login - Made Hilbish act like a login shell on login
- If Hilbish is the login shell, or the `-l`/`--login` flags are used, Hilbish will use an additional `~/.hprofile.lua` file, you can use this to set environment variables once on login - If Hilbish is the login shell, or the `-l`/`--login` flags are used, Hilbish will use an additional `~/.hprofile.lua` file, you can use this to set environment variables once on login
- `-c` has been added to run a single command (this works exactly like being in the prompt would, so Lua works as well) - `-c` has been added to run a single command (this works exactly like being in the prompt would, so Lua works as well)
- `-i` (also `--interactive`) has been added to force Hilbish to be an interactive shell in cases where it usually wont be (like with `-c`) - `-i` (also `--interactive`) has been added to force Hilbish to be an interactive shell in cases where it usually wont be (like with `-c`)
@ -249,13 +223,13 @@ When `false` is run, it will have the exit code of `1`, this is shorter/easier t
- Added a `mulitline` hook that's thrown when in the continue/multiline prompt - Added a `mulitline` hook that's thrown when in the continue/multiline prompt
- Added `appendPath` function to append a directory to `$PATH` - Added `appendPath` function to append a directory to `$PATH`
- `~` will be expanded to `$HOME` as well - `~` will be expanded to `$HOME` as well
- A utility `string.split` function is now added - A utility `string.split` function is now added
- `string.split(str, delimiter)` - `string.split(str, delimiter)`
- Added a `_user` variable to easily get current user's name - Added a `_user` variable to easily get current user's name
### Changed ### Changed
- **BREAKING Change**: [Lunacolors](https://github.com/Hilbis/Lunacolors) has replaced ansikit for formatting colors, which means the format function has been removed from ansikit and moved to Lunacolors. - **BREAKING Change**: [Lunacolors](https://github.com/Hilbis/Lunacolors) has replaced ansikit for formatting colors, which means the format function has been removed from ansikit and moved to Lunacolors.
- Users must replace ansikit with `lunacolors` in their config files - Users must replace ansikit with `lunacolors` in their config files
- A getopt-like library is now used for command line flag parsing - A getopt-like library is now used for command line flag parsing
- `cd` builtin now supports using environment variables - `cd` builtin now supports using environment variables
@ -367,7 +341,6 @@ 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
@ -381,5 +354,5 @@ First "stable" release of Hilbish.
[0.2.0]: https://github.com/Rosettea/Hilbish/compare/v0.1.2...v0.2.0 [0.2.0]: https://github.com/Rosettea/Hilbish/compare/v0.1.2...v0.2.0
[0.1.2]: https://github.com/Rosettea/Hilbish/compare/v0.1.1...v0.1.2 [0.1.2]: https://github.com/Rosettea/Hilbish/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/Rosettea/Hilbish/compare/v0.1.0...v0.1.1 [0.1.1]: https://github.com/Rosettea/Hilbish/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/Rosettea/Hilbish/compare/v0.0.12...v0.1.0 [0.1.0]: https://github.com/Rosettea/Hilbish/compare/v0.0.12...v0.1.0
[0.0.12]: https://github.com/Rosettea/Hilbish/releases/tag/v0.0.12 [0.0.12]: https://github.com/Rosettea/Hilbish/releases/tag/v0.0.12

68
api.go
View File

@ -23,7 +23,6 @@ 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,11 +73,10 @@ The nice lil shell for {blue}Lua{reset} fanatics!
util.SetField(L, hshuser, "data", lua.LString(userDataDir), "XDG data directory") util.SetField(L, hshuser, "data", lua.LString(userDataDir), "XDG data directory")
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()
util.SetField(L, hshos, "family", lua.LString(info.Family), "Family name of the current OS") util.SetField(L, hshos, "family", lua.LString(info.Family), "Family name of the current OS")
util.SetField(L, hshos, "name", lua.LString(info.Name), "Pretty name of the current OS") util.SetField(L, hshos, "name", lua.LString(info.Name), "Pretty name of the current OS")
util.SetField(L, hshos, "version", lua.LString(info.Version), "Version of the current OS") util.SetField(L, hshos, "version", lua.LString(info.Version), "Version of the current OS")
@ -96,63 +94,11 @@ 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)")
@ -228,7 +174,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
@ -400,10 +346,8 @@ 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 "completion groups." // `cb` must be a function that returns a table of the entries to complete.
// A completion group is a table with the keys `items` and `type`. // Nested tables will be used as sub-completions.
// `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 {
@ -441,7 +385,7 @@ func hlwhich(L *lua.LState) int {
l.Push(lua.LNil) l.Push(lua.LNil)
return 1 return 1
} }
l.Push(lua.LString(path)) l.Push(lua.LString(path))
return 1 return 1
} }

View File

@ -15,7 +15,7 @@ func fileComplete(query, ctx string, fields []string) []string {
completions, _ = matchPath(strings.Replace(query, "~", curuser.HomeDir, 1), query) completions, _ = matchPath(strings.Replace(query, "~", curuser.HomeDir, 1), query)
} }
} }
if len(completions) == 0 && len(fields) > 1 { if len(completions) == 0 && len(fields) > 1 {
completions, _ = matchPath("./" + query, query) completions, _ = matchPath("./" + query, query)
} }
@ -23,55 +23,6 @@ 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 + "*")
@ -103,6 +54,6 @@ func matchPath(path, pref string) ([]string, error) {
entries = append(entries, name) entries = append(entries, name)
} }
} }
return entries, err return entries, err
} }

View File

@ -5,10 +5,8 @@ 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 "completion groups." `cb` must be a function that returns a table of the entries to complete.
A completion group is a table with the keys `items` and `type`. Nested tables will be used as sub-completions.
`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,10 +14,8 @@ 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 "completion groups." --- `cb` must be a function that returns a table of the entries to complete.
--- A completion group is a table with the keys `items` and `type`. --- Nested tables will be used as sub-completions.
--- `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,8 +61,6 @@ 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
} }
@ -113,7 +111,7 @@ func execCommand(cmd, old string) error {
NRet: 1, NRet: 1,
Protect: true, Protect: true,
}, luacmdArgs) }, luacmdArgs)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, fmt.Fprintln(os.Stderr,
"Error in command:\n\n" + err.Error()) "Error in command:\n\n" + err.Error())

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-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 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-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

@ -19,25 +19,26 @@ func newFileHistory() (*fileHistory, error) {
return nil, err return nil, err
} }
} }
itms := []string{""} itms := []string{""}
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_APPEND | os.O_WRONLY | os.O_CREATE, 0755) f, err := os.OpenFile(defaultHistPath, os.O_RDWR | os.O_CREATE, 0755)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fh := &fileHistory{ fh := &fileHistory{
items: itms, items: itms,
f: f, f: f,
} }
return fh, nil return fh, nil
} }
@ -51,7 +52,7 @@ func (h *fileHistory) Write(line string) (int, error) {
return 0, err return 0, err
} }
h.f.Sync() h.f.Sync()
h.items = append(h.items, line) h.items = append(h.items, line)
return len(h.items), nil return len(h.items), nil
} }
@ -71,5 +72,5 @@ func (h *fileHistory) Len() int {
} }
func (h *fileHistory) Dump() interface{} { func (h *fileHistory) Dump() interface{} {
return h.items return nil
} }

View File

@ -40,7 +40,7 @@ func main() {
confDir, _ = os.UserConfigDir() confDir, _ = os.UserConfigDir()
preloadPath = strings.Replace(preloadPath, "~", homedir, 1) preloadPath = strings.Replace(preloadPath, "~", homedir, 1)
sampleConfPath = strings.Replace(sampleConfPath, "~", homedir, 1) sampleConfPath = strings.Replace(sampleConfPath, "~", homedir, 1)
// i honestly dont know what directories to use for this // i honestly dont know what directories to use for this
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "linux":
@ -165,7 +165,6 @@ func main() {
input: input:
for interactive { for interactive {
lr.SetPrompt(fmtPrompt(prompt))
running = false running = false
input, err := lr.Read() input, err := lr.Read()

237
rl.go
View File

@ -3,7 +3,9 @@ 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"
@ -12,18 +14,15 @@ 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()
fh, err := newFileHistory() fileHist, 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,8 +38,13 @@ func newLineReader(prompt string) *lineReader {
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
var compGroup []*readline.CompletionGroup 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 {
@ -54,28 +58,62 @@ func newLineReader(prompt string) *lineReader {
query := fields[len(fields) - 1] query := fields[len(fields) - 1]
ctx = aliases.Resolve(ctx) ctx = aliases.Resolve(ctx)
if len(fields) == 1 { 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{ // filter out executables, but in path
TrimSlash: false, for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
NoSpace: true, // print dir to stderr for debugging
Suggestions: completions, // search for an executable which matches our query string
}) if matches, err := filepath.Glob(filepath.Join(dir, query + "*")); err == nil {
// get basename from matches
return prefix, compGroup 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
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
@ -84,86 +122,88 @@ 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 {
// completion group // if we have only 2 fields then this is fine
if value.Type() == lua.LTTable { if len(fields) == 2 {
luaCmpGroup := value.(*lua.LTable) if strings.HasPrefix(value.String(), fields[1]) {
compType := luaCmpGroup.RawGet(lua.LString("type")) completions = append(completions, value.String())
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 { }
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 } else {
itemDescriptions := make(map[string]string) // if we have more than 2 fields, we need to check if the key matches
compItems.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) { // the current field and if it does, we need to check if the value is a string
if k.Type() == lua.LTString { // or table (nested sub completions)
// ['--flag'] = {'description', '--flag-alias'} if key.String() == fields[1] {
itm := v.(*lua.LTable) // if value is a table, we need to iterate over it
items = append(items, k.String()) // and add each value to completions
itemDescriptions[k.String()] = itm.RawGet(lua.LNumber(1)).String() // check if value is either a table or function
} else { if value.Type() == lua.LTTable {
items = append(items, v.String()) 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 if err != nil {
switch compType.String() { return
case "grid": dispType = readline.TabDisplayGrid }
case "list": dispType = readline.TabDisplayList
// need special cases, will implement later luacompleteTable := l.Get(-1)
//case "map": dispType = readline.TabDisplayMap 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) completions = fileComplete(query, ctx, fields)
compGroup = append(compGroup, &readline.CompletionGroup{
TrimSlash: false,
NoSpace: true,
Suggestions: completions,
})
} }
} }
compGroup[0].Suggestions = completions
return "", compGroup return "", compGroup
} }
@ -200,7 +240,7 @@ func (lr *lineReader) SetPrompt(prompt string) {
} }
func (lr *lineReader) AddHistory(cmd string) { func (lr *lineReader) AddHistory(cmd string) {
fileHist.Write(cmd) return
} }
func (lr *lineReader) ClearInput() { func (lr *lineReader) ClearInput() {
@ -233,35 +273,18 @@ 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) luaAllHistory(L *lua.LState) int { func (lr *lineReader) luaGetHistory(l *lua.LState) int {
tbl := L.NewTable() return 0
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,12 +2,11 @@ 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 = "v1.0.0" version = "v0.7.1"
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 = "> "
) )