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,19 +32,20 @@ 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
@ -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
@ -161,9 +187,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
@ -176,7 +202,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
@ -215,7 +241,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`)
@ -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 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
@ -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
@ -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.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,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,
@ -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.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")
@ -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 {
@ -385,7 +441,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,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 + "*")
@ -54,6 +103,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,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
} }
@ -111,7 +113,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-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

@ -19,26 +19,25 @@ 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_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
} }
fh := &fileHistory{ fh := &fileHistory{
items: itms, items: itms,
f: f, f: f,
} }
return fh, nil return fh, nil
} }
@ -52,7 +51,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
} }
@ -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

@ -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,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()

237
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 := ""
@ -38,13 +39,8 @@ 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
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 {
@ -58,62 +54,28 @@ 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 {
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 if len(fields) == 1 {
for _, dir := range filepath.SplitList(os.Getenv("PATH")) { completions, prefix := binaryComplete(query, ctx, fields)
// print dir to stderr for debugging
// search for an executable which matches our query string compGroup = append(compGroup, &readline.CompletionGroup{
if matches, err := filepath.Glob(filepath.Join(dir, query + "*")); err == nil { TrimSlash: false,
// get basename from matches NoSpace: true,
for _, match := range matches { Suggestions: completions,
// check if we have execute permissions for our match })
if info, err := os.Stat(match); err == nil && info.Mode().Perm() & 0100 == 0 {
continue return prefix, compGroup
}
// 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
@ -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 value.Type() == lua.LTTable {
if strings.HasPrefix(value.String(), fields[1]) { luaCmpGroup := value.(*lua.LTable)
completions = append(completions, value.String()) 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 compItems.Type() != lua.LTTable {
} else if key.Type() == lua.LTString { l.RaiseError("bad items for completion (expected table, got %v)", compItems.Type().String())
if len(fields) == 2 {
if strings.HasPrefix(key.String(), fields[1]) {
completions = append(completions, key.String())
} }
} else { var items []string
// if we have more than 2 fields, we need to check if the key matches itemDescriptions := make(map[string]string)
// the current field and if it does, we need to check if the value is a string compItems.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) {
// or table (nested sub completions) if k.Type() == lua.LTString {
if key.String() == fields[1] { // ['--flag'] = {'description', '--flag-alias'}
// if value is a table, we need to iterate over it itm := v.(*lua.LTable)
// and add each value to completions items = append(items, k.String())
// check if value is either a table or function itemDescriptions[k.String()] = itm.RawGet(lua.LNumber(1)).String()
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)
}
})
}
} else { } else {
// throw lua error items = append(items, v.String())
// complete.cmdname: error message...
l.RaiseError("complete." + fields[0] + ": completion value is not a table or function")
} }
})
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) 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 = "> "
) )