mirror of https://github.com/Hilbis/Hilbish
Compare commits
13 Commits
59963add14
...
f3f49fc398
Author | SHA1 | Date |
---|---|---|
TorchedSammy | f3f49fc398 | |
TorchedSammy | 893d72a236 | |
TorchedSammy | 0637f2763b | |
TorchedSammy | 1cb536b1ac | |
TorchedSammy | 6740e012a5 | |
TorchedSammy | b1ad90443e | |
TorchedSammy | 76c94bfcce | |
TorchedSammy | 0ed365170c | |
TorchedSammy | 70724ec015 | |
TorchedSammy | f1dfd59c4c | |
TorchedSammy | a0dff5babf | |
TorchedSammy | 058d6ac456 | |
TorchedSammy | 9f206ebed0 |
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -1,6 +1,6 @@
|
|||
# 🎀 Changelog
|
||||
|
||||
## Unreleased
|
||||
## [1.0.0] - 2021-03-04
|
||||
### Added
|
||||
- MacOS is now officialy supported, default compile time vars have been added
|
||||
for it
|
||||
|
@ -18,6 +18,7 @@ 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
|
||||
|
@ -31,19 +32,20 @@ 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
|
||||
- Default config is no longer copied to user's config and is instead ran its location
|
||||
#### Breaking Changes
|
||||
(there were a lot...)
|
||||
(there were a lot...)
|
||||
- Change default SHLVL to 0 instead of 1
|
||||
- ~/.hilbishrc.lua will no longer be run by default, it now
|
||||
only uses the paths mentioned below.
|
||||
- Changed Hilbish's config path to something more suited
|
||||
according to the OS (`$XDG_CONFIG_HOME/hilbish/init.lua` on Linux,
|
||||
`~/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`
|
||||
- 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
|
||||
|
@ -53,6 +55,30 @@ 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
|
||||
|
@ -161,9 +187,9 @@ An absolutely massive release. Probably the biggest yet, includes a bunch of fix
|
|||
### Added
|
||||
|
||||
- `-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
|
||||
- Commands from commander can now `return` an exit code
|
||||
- Commands from commander can now `return` an exit code
|
||||
```lua
|
||||
commander.register('false', function()
|
||||
return 1
|
||||
|
@ -176,7 +202,7 @@ When `false` is run, it will have the exit code of `1`, this is shorter/easier t
|
|||
- Recursive aliases
|
||||
- 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)
|
||||
- `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
|
||||
- `cd -` will change to the previous directory
|
||||
- `hilbish.cwd()` gets the current working directory
|
||||
|
@ -215,7 +241,7 @@ When `false` is run, it will have the exit code of `1`, this is shorter/easier t
|
|||
|
||||
### Added
|
||||
- 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
|
||||
- `-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`)
|
||||
|
@ -223,13 +249,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 `appendPath` function to append a directory to `$PATH`
|
||||
- `~` 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)`
|
||||
- Added a `_user` variable to easily get current user's name
|
||||
|
||||
### 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
|
||||
- A getopt-like library is now used for command line flag parsing
|
||||
- `cd` builtin now supports using environment variables
|
||||
|
@ -341,6 +367,7 @@ 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
|
||||
|
@ -354,5 +381,5 @@ First "stable" release of Hilbish.
|
|||
[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.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
|
||||
|
|
68
api.go
68
api.go
|
@ -23,6 +23,7 @@ import (
|
|||
var exports = map[string]lua.LGFunction {
|
||||
"alias": hlalias,
|
||||
"appendPath": hlappendPath,
|
||||
"complete": hlcomplete,
|
||||
"cwd": hlcwd,
|
||||
"exec": hlexec,
|
||||
"goro": hlgoro,
|
||||
|
@ -73,10 +74,11 @@ The nice lil shell for {blue}Lua{reset} fanatics!
|
|||
util.SetField(L, hshuser, "data", lua.LString(userDataDir), "XDG data directory")
|
||||
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()
|
||||
|
||||
|
||||
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, "version", lua.LString(info.Version), "Version of the current OS")
|
||||
|
@ -94,11 +96,63 @@ 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)")
|
||||
|
@ -174,7 +228,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
|
||||
|
@ -346,8 +400,10 @@ 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 the entries to complete.
|
||||
// Nested tables will be used as sub-completions.
|
||||
// `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)
|
||||
// --- @param scope string
|
||||
// --- @param cb function
|
||||
func hlcomplete(L *lua.LState) int {
|
||||
|
@ -385,7 +441,7 @@ func hlwhich(L *lua.LState) int {
|
|||
l.Push(lua.LNil)
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
l.Push(lua.LString(path))
|
||||
return 1
|
||||
}
|
||||
|
|
53
complete.go
53
complete.go
|
@ -15,7 +15,7 @@ func fileComplete(query, ctx string, fields []string) []string {
|
|||
completions, _ = matchPath(strings.Replace(query, "~", curuser.HomeDir, 1), query)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if len(completions) == 0 && len(fields) > 1 {
|
||||
completions, _ = matchPath("./" + query, query)
|
||||
}
|
||||
|
@ -23,6 +23,55 @@ 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 + "*")
|
||||
|
@ -54,6 +103,6 @@ func matchPath(path, pref string) ([]string, error) {
|
|||
entries = append(entries, name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return entries, err
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ 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 the entries to complete.
|
||||
Nested tables will be used as sub-completions.
|
||||
`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)
|
||||
|
||||
cwd() > Returns the current directory of the shell
|
||||
|
||||
|
|
|
@ -14,8 +14,10 @@ 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 the entries to complete.
|
||||
--- Nested tables will be used as sub-completions.
|
||||
--- `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)
|
||||
--- @param scope string
|
||||
--- @param cb function
|
||||
function hilbish.complete(scope, cb) end
|
||||
|
|
4
exec.go
4
exec.go
|
@ -61,6 +61,8 @@ func runInput(input, origInput string) {
|
|||
} else if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
cmdFinish(1, cmdString, origInput)
|
||||
} else {
|
||||
cmdFinish(0, cmdString, origInput)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -111,7 +113,7 @@ func execCommand(cmd, old string) error {
|
|||
NRet: 1,
|
||||
Protect: true,
|
||||
}, luacmdArgs)
|
||||
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr,
|
||||
"Error in command:\n\n" + err.Error())
|
||||
|
|
2
go.mod
2
go.mod
|
@ -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-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
|
||||
|
|
2
go.sum
2
go.sum
|
@ -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-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=
|
||||
|
|
13
history.go
13
history.go
|
@ -19,26 +19,25 @@ func newFileHistory() (*fileHistory, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
itms := []string{""}
|
||||
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_RDWR | os.O_CREATE, 0755)
|
||||
f, err := os.OpenFile(defaultHistPath, os.O_APPEND | os.O_WRONLY | os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
fh := &fileHistory{
|
||||
items: itms,
|
||||
f: f,
|
||||
}
|
||||
|
||||
|
||||
return fh, nil
|
||||
}
|
||||
|
||||
|
@ -52,7 +51,7 @@ func (h *fileHistory) Write(line string) (int, error) {
|
|||
return 0, err
|
||||
}
|
||||
h.f.Sync()
|
||||
|
||||
|
||||
h.items = append(h.items, line)
|
||||
return len(h.items), nil
|
||||
}
|
||||
|
@ -72,5 +71,5 @@ func (h *fileHistory) Len() int {
|
|||
}
|
||||
|
||||
func (h *fileHistory) Dump() interface{} {
|
||||
return nil
|
||||
return h.items
|
||||
}
|
||||
|
|
3
main.go
3
main.go
|
@ -40,7 +40,7 @@ func main() {
|
|||
confDir, _ = os.UserConfigDir()
|
||||
preloadPath = strings.Replace(preloadPath, "~", homedir, 1)
|
||||
sampleConfPath = strings.Replace(sampleConfPath, "~", homedir, 1)
|
||||
|
||||
|
||||
// i honestly dont know what directories to use for this
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
|
@ -165,6 +165,7 @@ func main() {
|
|||
|
||||
input:
|
||||
for interactive {
|
||||
lr.SetPrompt(fmtPrompt(prompt))
|
||||
running = false
|
||||
|
||||
input, err := lr.Read()
|
||||
|
|
237
rl.go
237
rl.go
|
@ -3,9 +3,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/maxlandon/readline"
|
||||
"github.com/yuin/gopher-lua"
|
||||
|
@ -14,15 +12,18 @@ 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()
|
||||
fileHist, err := newFileHistory()
|
||||
fh, err := newFileHistory()
|
||||
fileHist = fh // go stupid
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rl.SetHistoryCtrlR("file", fileHist)
|
||||
rl.HistoryAutoWrite = false
|
||||
rl.ShowVimMode = false
|
||||
rl.ViModeCallback = func(mode readline.ViMode) {
|
||||
modeStr := ""
|
||||
|
@ -38,13 +39,8 @@ func newLineReader(prompt string) *lineReader {
|
|||
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
|
||||
ctx := string(line)
|
||||
var completions []string
|
||||
|
||||
compGroup := []*readline.CompletionGroup{
|
||||
&readline.CompletionGroup{
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
},
|
||||
}
|
||||
|
||||
var compGroup []*readline.CompletionGroup
|
||||
|
||||
ctx = strings.TrimLeft(ctx, " ")
|
||||
if len(ctx) == 0 {
|
||||
|
@ -58,62 +54,28 @@ func newLineReader(prompt string) *lineReader {
|
|||
query := fields[len(fields) - 1]
|
||||
|
||||
ctx = aliases.Resolve(ctx)
|
||||
|
||||
if len(fields) == 1 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if len(fields) == 1 {
|
||||
completions, prefix := binaryComplete(query, ctx, fields)
|
||||
|
||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
Suggestions: completions,
|
||||
})
|
||||
|
||||
return prefix, 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
|
||||
|
@ -122,88 +84,86 @@ 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 {
|
||||
// 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())
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
} else if key.Type() == lua.LTString {
|
||||
if len(fields) == 2 {
|
||||
if strings.HasPrefix(key.String(), fields[1]) {
|
||||
completions = append(completions, key.String())
|
||||
if compItems.Type() != lua.LTTable {
|
||||
l.RaiseError("bad items for completion (expected table, got %v)", compItems.Type().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))
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
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 {
|
||||
// throw lua error
|
||||
// complete.cmdname: error message...
|
||||
l.RaiseError("complete." + fields[0] + ": completion value is not a table or function")
|
||||
items = append(items, v.String())
|
||||
}
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
||||
DisplayType: dispType,
|
||||
Descriptions: itemDescriptions,
|
||||
Suggestions: items,
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(completions) == 0 {
|
||||
if len(compGroup) == 0 {
|
||||
completions = fileComplete(query, ctx, fields)
|
||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
Suggestions: completions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
compGroup[0].Suggestions = completions
|
||||
return "", compGroup
|
||||
}
|
||||
|
||||
|
@ -240,7 +200,7 @@ func (lr *lineReader) SetPrompt(prompt string) {
|
|||
}
|
||||
|
||||
func (lr *lineReader) AddHistory(cmd string) {
|
||||
return
|
||||
fileHist.Write(cmd)
|
||||
}
|
||||
|
||||
func (lr *lineReader) ClearInput() {
|
||||
|
@ -273,18 +233,35 @@ func (lr *lineReader) luaAddHistory(l *lua.LState) int {
|
|||
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
|
||||
}
|
||||
|
||||
func (lr *lineReader) luaGetHistory(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) luaAllHistory(l *lua.LState) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (lr *lineReader) luaClearHistory(l *lua.LState) int {
|
||||
return 0
|
||||
|
||||
}
|
||||
|
|
3
vars.go
3
vars.go
|
@ -2,11 +2,12 @@ package main
|
|||
|
||||
// String vars that are free to be changed at compile time
|
||||
var (
|
||||
version = "v0.7.1"
|
||||
version = "v1.0.0"
|
||||
defaultConfDir = "" // ~ will be substituted for home, path for user's default config
|
||||
defaultHistDir = ""
|
||||
commonRequirePaths = "';./libs/?/init.lua;./?/init.lua;./?/?.lua'"
|
||||
|
||||
prompt string
|
||||
multilinePrompt = "> "
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue