mirror of https://github.com/Hilbis/Hilbish
fix: bring in latest changes
commit
0e076f391a
|
@ -19,7 +19,9 @@ jobs:
|
||||||
goos: windows
|
goos: windows
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
|
@ -29,10 +31,28 @@ jobs:
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: matrix.goos == 'windows'
|
if: matrix.goos == 'windows'
|
||||||
with:
|
with:
|
||||||
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}.exe
|
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
path: hilbish.exe
|
path: |
|
||||||
|
hilbish.exe
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
CHANGELOG.md
|
||||||
|
.hilbishrc.lua
|
||||||
|
nature
|
||||||
|
libs
|
||||||
|
docs
|
||||||
|
emmyLuaDocs
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: matrix.goos != 'windows'
|
if: matrix.goos != 'windows'
|
||||||
with:
|
with:
|
||||||
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
|
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
path: hilbish
|
path: |
|
||||||
|
hilbish
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
CHANGELOG.md
|
||||||
|
.hilbishrc.lua
|
||||||
|
nature
|
||||||
|
libs
|
||||||
|
docs
|
||||||
|
emmyLuaDocs
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
name: CodeQL
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '38 2 * * 4'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
||||||
|
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||||
|
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||||
|
|
||||||
|
# - run: |
|
||||||
|
# echo "Run, Build Application using script"
|
||||||
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
|
@ -30,13 +30,13 @@ jobs:
|
||||||
- goarch: arm64
|
- goarch: arm64
|
||||||
goos: windows
|
goos: windows
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: true
|
||||||
- uses: wangyoucao577/go-release-action@v1.25
|
- uses: wangyoucao577/go-release-action@v1.25
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
goos: ${{ matrix.goos }}
|
goos: ${{ matrix.goos }}
|
||||||
goarch: ${{ matrix.goarch }}
|
goarch: ${{ matrix.goarch }}
|
||||||
binary_name: hilbish
|
binary_name: hilbish
|
||||||
extra_files: LICENSE README.md CHANGELOG.md .hilbishrc.lua prelude libs docs emmyLuaDocs
|
extra_files: LICENSE README.md CHANGELOG.md .hilbishrc.lua nature libs docs emmyLuaDocs
|
||||||
|
|
58
CHANGELOG.md
58
CHANGELOG.md
|
@ -1,5 +1,63 @@
|
||||||
# 🎀 Changelog
|
# 🎀 Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
### Added
|
||||||
|
- Inline hints, akin to fish and the others.
|
||||||
|
To make a handler for hint text, you can set the `hilbish.hinter` function.
|
||||||
|
For more info, look at its docs with the `doc hilbish` command.
|
||||||
|
- Syntax highlighting function. To make a handler for it, set
|
||||||
|
`hilbish.highlighter`. Same thing as the hinter, check `doc hilbish` for
|
||||||
|
more info/docs.
|
||||||
|
- Ctrl+K deletes from the cursor to the end of the line. ([#128](https://github.com/Rosettea/Hilbish/pull/128))
|
||||||
|
- Alt+Backspace as an alternative of Ctrl+W to delete a word. ([#132](https://github.com/Rosettea/Hilbish/pull/132))
|
||||||
|
- Enhanced timer API (`doc timers`)
|
||||||
|
- Don't exit until intervals are stopped/finished when running a non interactive script.
|
||||||
|
- Ctrl+D deletes character below cursor if line isn't empty instead of exiting.
|
||||||
|
- Ctrl+Delete to forward delete a word.
|
||||||
|
- Right prompt ([#140](https://github.com/Rosettea/Hilbish/pull/140))
|
||||||
|
- Ctrl+_ to undo in Emacs input mode.
|
||||||
|
- Emacs style forward/backward word keybinds ([#139](https://github.com/Rosettea/Hilbish/pull/139))
|
||||||
|
- `hilbish.completion.call` to call a completion handler (`doc completions`)
|
||||||
|
- `hilbish.completion.handler` to set a custom handler for completions. This
|
||||||
|
is for everything/anything as opposed to just adding a single command completion.
|
||||||
|
[#122](https://github.com/Rosettea/Hilbish/issues/122)
|
||||||
|
- `fs.abs(path)` to get absolute path.
|
||||||
|
- Nature module (`doc nature`)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Breaking Change:** Upgraded to Lua 5.4.
|
||||||
|
This is probably one of (if not the) biggest things in this release.
|
||||||
|
- **Breaking Change:** MacOS config paths now match Linux.
|
||||||
|
- Overrides on the `hilbish` table are no longer permitted.
|
||||||
|
- **Breaking Change:** Runner functions are now required to return 3 values:
|
||||||
|
user input, exit code, and error. User input has been added to the return to
|
||||||
|
account for runners wanting to prompt for continued input, and to add it
|
||||||
|
properly to history.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- If in Vim replace mode, input at the end of the line inserts instead of
|
||||||
|
replacing the last character.
|
||||||
|
- Make forward delete work how its supposed to.
|
||||||
|
- Prompt refresh not working properly.
|
||||||
|
- Crashing on input in xterm. ([#131](https://github.com/Rosettea/Hilbish/pull/131))
|
||||||
|
- Make delete key work on st ([#131](https://github.com/Rosettea/Hilbish/pull/131))
|
||||||
|
- `hilbish.login` being the wrong value.
|
||||||
|
- Put full input in history if prompted for continued input
|
||||||
|
- Don't put alias expanded command in history (sound familiar?)
|
||||||
|
- Handle cases of stdin being nonblocking (in the case of [#130](https://github.com/Rosettea/Hilbish/issues/130))
|
||||||
|
- Don't prompt for continued input if non interactive
|
||||||
|
- Don't insert unhandled control keys.
|
||||||
|
- Handle sh syntax error in alias
|
||||||
|
- Use invert for completion menu selection highlight instead of specific
|
||||||
|
colors. Brings an improvement on light themes, or themes that don't follow
|
||||||
|
certain color rules.
|
||||||
|
- Home/End keys now go to the actual start/end of the input.
|
||||||
|
- Input getting cut off on enter in certain cases.
|
||||||
|
- Go to the next line properly if input reaches end of terminal width.
|
||||||
|
- Cursor position with CJK characters. ([#145](https://github.com/Rosettea/Hilbish/pull/145))
|
||||||
|
- Files with same name as parent folder in completions getting cut off [#136](https://github.com/Rosettea/Hilbish/issues/136))
|
||||||
|
- `hilbish.which` now works with commanders and aliases.
|
||||||
|
|
||||||
## [1.2.0] - 2022-03-17
|
## [1.2.0] - 2022-03-17
|
||||||
### Added
|
### Added
|
||||||
- Job Management additions
|
- Job Management additions
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -15,7 +15,7 @@ build:
|
||||||
install:
|
install:
|
||||||
install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v hilbish "$(DESTDIR)$(BINDIR)/hilbish"
|
install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v hilbish "$(DESTDIR)$(BINDIR)/hilbish"
|
||||||
mkdir -p "$(DESTDIR)$(LIBDIR)"
|
mkdir -p "$(DESTDIR)$(LIBDIR)"
|
||||||
cp -r libs docs emmyLuaDocs prelude .hilbishrc.lua "$(DESTDIR)$(LIBDIR)"
|
cp -r libs docs emmyLuaDocs nature .hilbishrc.lua "$(DESTDIR)$(LIBDIR)"
|
||||||
grep -qxF "$(DESTDIR)$(BINDIR)/hilbish" /etc/shells || echo "$(DESTDIR)$(BINDIR)/hilbish" >> /etc/shells
|
grep -qxF "$(DESTDIR)$(BINDIR)/hilbish" /etc/shells || echo "$(DESTDIR)$(BINDIR)/hilbish" >> /etc/shells
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
|
|
14
aliases.go
14
aliases.go
|
@ -72,6 +72,7 @@ func (a *aliasHandler) Loader(rtm *rt.Runtime) *rt.Table {
|
||||||
"add": util.LuaExport{hlalias, 2, false},
|
"add": util.LuaExport{hlalias, 2, false},
|
||||||
"list": util.LuaExport{a.luaList, 0, false},
|
"list": util.LuaExport{a.luaList, 0, false},
|
||||||
"del": util.LuaExport{a.luaDelete, 1, false},
|
"del": util.LuaExport{a.luaDelete, 1, false},
|
||||||
|
"resolve": util.LuaExport{a.luaResolve, 1, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
|
@ -101,3 +102,16 @@ func (a *aliasHandler) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *aliasHandler) luaResolve(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
alias, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resolved := a.Resolve(alias)
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, rt.StringValue(resolved)), nil
|
||||||
|
}
|
||||||
|
|
233
api.go
233
api.go
|
@ -52,7 +52,47 @@ var hilbishLoader = packagelib.Loader{
|
||||||
}
|
}
|
||||||
|
|
||||||
func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
|
fakeMod := rt.NewTable()
|
||||||
|
modmt := rt.NewTable()
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
|
|
||||||
|
modIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
arg := c.Arg(1)
|
||||||
|
val := mod.Get(arg)
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, val), nil
|
||||||
|
}
|
||||||
|
modNewIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
k, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := c.Arg(2)
|
||||||
|
if k == "highlighter" {
|
||||||
|
var err error
|
||||||
|
// fine to assign, since itll be either nil or a closure
|
||||||
|
highlighter, err = c.ClosureArg(2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("hilbish.highlighter has to be a function")
|
||||||
|
}
|
||||||
|
} else if k == "hinter" {
|
||||||
|
var err error
|
||||||
|
hinter, err = c.ClosureArg(2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("hilbish.hinter has to be a function")
|
||||||
|
}
|
||||||
|
} else if modVal := mod.Get(rt.StringValue(k)); modVal != rt.NilValue {
|
||||||
|
return nil, errors.New("not allowed to override in hilbish table")
|
||||||
|
}
|
||||||
|
mod.Set(rt.StringValue(k), v)
|
||||||
|
|
||||||
|
return c.Next(), nil
|
||||||
|
}
|
||||||
|
modmt.Set(rt.StringValue("__newindex"), rt.FunctionValue(rt.NewGoFunction(modNewIndex, "__newindex", 3, false)))
|
||||||
|
modmt.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(modIndex, "__index", 2, false)))
|
||||||
|
fakeMod.SetMetatable(modmt)
|
||||||
|
|
||||||
util.SetExports(rtm, mod, exports)
|
util.SetExports(rtm, mod, exports)
|
||||||
hshMod = mod
|
hshMod = mod
|
||||||
|
|
||||||
|
@ -67,17 +107,17 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
The nice lil shell for {blue}Lua{reset} fanatics!
|
The nice lil shell for {blue}Lua{reset} fanatics!
|
||||||
Check out the {blue}{bold}guide{reset} command to get started.
|
Check out the {blue}{bold}guide{reset} command to get started.
|
||||||
`
|
`
|
||||||
util.SetField(rtm, mod, "ver", rt.StringValue(version), "Hilbish version")
|
util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(version), "Hilbish version")
|
||||||
util.SetField(rtm, mod, "user", rt.StringValue(username), "Username of user")
|
util.SetFieldProtected(fakeMod, mod, "user", rt.StringValue(username), "Username of user")
|
||||||
util.SetField(rtm, mod, "host", rt.StringValue(host), "Host name of the machine")
|
util.SetFieldProtected(fakeMod, mod, "host", rt.StringValue(host), "Host name of the machine")
|
||||||
util.SetField(rtm, mod, "home", rt.StringValue(curuser.HomeDir), "Home directory of the user")
|
util.SetFieldProtected(fakeMod, mod, "home", rt.StringValue(curuser.HomeDir), "Home directory of the user")
|
||||||
util.SetField(rtm, mod, "dataDir", rt.StringValue(dataDir), "Directory for Hilbish's data files")
|
util.SetFieldProtected(fakeMod, mod, "dataDir", rt.StringValue(dataDir), "Directory for Hilbish's data files")
|
||||||
util.SetField(rtm, mod, "interactive", rt.BoolValue(interactive), "If this is an interactive shell")
|
util.SetFieldProtected(fakeMod, mod, "interactive", rt.BoolValue(interactive), "If this is an interactive shell")
|
||||||
util.SetField(rtm, mod, "login", rt.BoolValue(login), "Whether this is a login shell")
|
util.SetFieldProtected(fakeMod, mod, "login", rt.BoolValue(login), "Whether this is a login shell")
|
||||||
util.SetField(rtm, mod, "greeting", rt.StringValue(greeting), "Hilbish's welcome message for interactive shells. It has Lunacolors formatting.")
|
util.SetFieldProtected(fakeMod, mod, "greeting", rt.StringValue(greeting), "Hilbish's welcome message for interactive shells. It has Lunacolors formatting.")
|
||||||
util.SetField(rtm, mod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
util.SetFieldProtected(fakeMod, mod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
||||||
util.SetField(rtm, hshMod, "exitCode", rt.IntValue(0), "Exit code of last exected command")
|
util.SetFieldProtected(fakeMod, mod, "exitCode", rt.IntValue(0), "Exit code of last exected command")
|
||||||
util.Document(mod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
|
util.Document(fakeMod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
|
||||||
|
|
||||||
// hilbish.userDir table
|
// hilbish.userDir table
|
||||||
hshuser := rt.NewTable()
|
hshuser := rt.NewTable()
|
||||||
|
@ -109,15 +149,7 @@ Check out the {blue}{bold}guide{reset} command to get started.
|
||||||
util.Document(historyModule, "History interface for Hilbish.")
|
util.Document(historyModule, "History interface for Hilbish.")
|
||||||
|
|
||||||
// hilbish.completion table
|
// hilbish.completion table
|
||||||
hshcomp := rt.NewTable()
|
hshcomp := completionLoader(rtm)
|
||||||
util.SetField(rtm, hshcomp, "files",
|
|
||||||
rt.FunctionValue(rt.NewGoFunction(luaFileComplete, "files", 3, false)),
|
|
||||||
"Completer for files")
|
|
||||||
|
|
||||||
util.SetField(rtm, hshcomp, "bins",
|
|
||||||
rt.FunctionValue(rt.NewGoFunction(luaBinaryComplete, "bins", 3, false)),
|
|
||||||
"Completer for executables/binaries")
|
|
||||||
|
|
||||||
util.Document(hshcomp, "Completions interface for Hilbish.")
|
util.Document(hshcomp, "Completions interface for Hilbish.")
|
||||||
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))
|
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))
|
||||||
|
|
||||||
|
@ -137,7 +169,7 @@ Check out the {blue}{bold}guide{reset} command to get started.
|
||||||
util.Document(timerModule, "Timer interface, for control of all intervals and timeouts.")
|
util.Document(timerModule, "Timer interface, for control of all intervals and timeouts.")
|
||||||
mod.Set(rt.StringValue("timers"), rt.TableValue(timerModule))
|
mod.Set(rt.StringValue("timers"), rt.TableValue(timerModule))
|
||||||
|
|
||||||
return rt.TableValue(mod), nil
|
return rt.TableValue(fakeMod), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getenv(key, fallback string) string {
|
func getenv(key, fallback string) string {
|
||||||
|
@ -148,75 +180,6 @@ func getenv(key, fallback string) string {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
query, ctx, fds, err := getCompleteParams(t, c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
completions, _ := fileComplete(query, ctx, fds)
|
|
||||||
luaComps := rt.NewTable()
|
|
||||||
|
|
||||||
for i, comp := range completions {
|
|
||||||
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.TableValue(luaComps)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
query, ctx, fds, err := getCompleteParams(t, c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
completions, _ := binaryComplete(query, ctx, fds)
|
|
||||||
luaComps := rt.NewTable()
|
|
||||||
|
|
||||||
for i, comp := range completions {
|
|
||||||
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.TableValue(luaComps)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, error) {
|
|
||||||
if err := c.CheckNArgs(3); err != nil {
|
|
||||||
return "", "", []string{}, err
|
|
||||||
}
|
|
||||||
query, err := c.StringArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", []string{}, err
|
|
||||||
}
|
|
||||||
ctx, err := c.StringArg(1)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", []string{}, err
|
|
||||||
}
|
|
||||||
fields, err := c.TableArg(2)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", []string{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var fds []string
|
|
||||||
nextVal := rt.NilValue
|
|
||||||
for {
|
|
||||||
next, val, ok := fields.Next(nextVal)
|
|
||||||
if next == rt.NilValue {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nextVal = next
|
|
||||||
|
|
||||||
valStr, ok := val.TryString()
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fds = append(fds, valStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return query, ctx, fds, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func setVimMode(mode string) {
|
func setVimMode(mode string) {
|
||||||
util.SetField(l, hshMod, "vimMode", rt.StringValue(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
util.SetField(l, hshMod, "vimMode", rt.StringValue(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
||||||
hooks.Em.Emit("hilbish.vimMode", mode)
|
hooks.Em.Emit("hilbish.vimMode", mode)
|
||||||
|
@ -395,21 +358,11 @@ func hlappendPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
|
||||||
// check if dir is a table or a string
|
// check if dir is a table or a string
|
||||||
if arg.Type() == rt.TableType {
|
if arg.Type() == rt.TableType {
|
||||||
nextVal := rt.NilValue
|
util.ForEach(arg.AsTable(), func(k rt.Value, v rt.Value) {
|
||||||
for {
|
if v.Type() == rt.StringType {
|
||||||
next, val, ok := arg.AsTable().Next(nextVal)
|
appendPath(v.AsString())
|
||||||
if next == rt.NilValue {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
nextVal = next
|
})
|
||||||
|
|
||||||
valStr, ok := val.TryString()
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
appendPath(valStr)
|
|
||||||
}
|
|
||||||
} else if arg.Type() == rt.StringType {
|
} else if arg.Type() == rt.StringType {
|
||||||
appendPath(arg.AsString())
|
appendPath(arg.AsString())
|
||||||
} else {
|
} else {
|
||||||
|
@ -544,9 +497,7 @@ func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
// 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 "completion groups."
|
||||||
// A completion group is a table with the keys `items` and `type`.
|
// Check `doc completions` for more information.
|
||||||
// `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(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func hlcomplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
@ -581,18 +532,27 @@ func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// which(binName)
|
// which(name)
|
||||||
// Searches for an executable called `binName` in the directories of $PATH
|
// Checks if `name` is a valid command
|
||||||
// --- @param binName string
|
// --- @param binName string
|
||||||
func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
binName, err := c.StringArg(0)
|
name, err := c.StringArg(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
path, err := exec.LookPath(binName)
|
|
||||||
|
cmd := aliases.Resolve(name)
|
||||||
|
|
||||||
|
// check for commander
|
||||||
|
if commands[cmd] != nil {
|
||||||
|
// they dont resolve to a path, so just send the cmd
|
||||||
|
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := exec.LookPath(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
@ -601,7 +561,7 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputMode(mode)
|
// inputMode(mode)
|
||||||
// Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
// Sets the input mode for Hilbish's line reader. Accepts either emacs or vim
|
||||||
// --- @param mode string
|
// --- @param mode string
|
||||||
func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
@ -642,7 +602,6 @@ func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
switch mode.Type() {
|
switch mode.Type() {
|
||||||
case rt.StringType:
|
case rt.StringType:
|
||||||
switch mode.AsString() {
|
switch mode.AsString() {
|
||||||
// no fallthrough doesnt work so eh
|
|
||||||
case "hybrid", "hybridRev", "lua", "sh": runnerMode = mode
|
case "hybrid", "hybridRev", "lua", "sh": runnerMode = mode
|
||||||
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.AsString())
|
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.AsString())
|
||||||
}
|
}
|
||||||
|
@ -653,40 +612,24 @@ func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hinter(cb)
|
// hinter(line, pos)
|
||||||
// Sets the hinter function. This will be called on every key insert to determine
|
// The command line hint handler. It gets called on every key insert to
|
||||||
// what text to use as an inline hint. The callback is passed 2 arguments:
|
// determine what text to use as an inline hint. It is passed the current
|
||||||
// the current line and the position. It is expected to return a string
|
// line and cursor position. It is expected to return a string which is used
|
||||||
// which will be used for the hint.
|
// as the text for the hint. This is by default a shim. To set hints,
|
||||||
// --- @param cb function
|
// override this function with your custom handler.
|
||||||
|
// --- @param line string
|
||||||
|
// --- @param pos int
|
||||||
func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
return c.Next(), nil
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hinterCb, err := c.ClosureArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hinter = hinterCb
|
|
||||||
|
|
||||||
return c.Next(), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// highlighter(cb)
|
// highlighter(line)
|
||||||
// Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
// Line highlighter handler. This is mainly for syntax highlighting, but in
|
||||||
// reality could set the input of the prompt to display anything. The callback
|
// reality could set the input of the prompt to *display* anything. The
|
||||||
// is passed the current line as typed and is expected to return a line that will
|
// callback is passed the current line and is expected to return a line that
|
||||||
// be used to display in the line.
|
// will be used as the input display.
|
||||||
// --- @param cb function
|
// --- @param line string
|
||||||
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
return c.Next(), nil
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
highlighterCb, err := c.ClosureArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
highlighter = highlighterCb
|
|
||||||
|
|
||||||
return c.Next(), err
|
|
||||||
}
|
}
|
||||||
|
|
125
complete.go
125
complete.go
|
@ -1,9 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"hilbish/util"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func fileComplete(query, ctx string, fields []string) ([]string, string) {
|
func fileComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
|
@ -19,7 +24,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
fileCompletions, filePref := matchPath(query)
|
fileCompletions, filePref := matchPath(query)
|
||||||
if len(fileCompletions) != 0 {
|
if len(fileCompletions) != 0 {
|
||||||
for _, f := range fileCompletions {
|
for _, f := range fileCompletions {
|
||||||
fullPath, _ := filepath.Abs(expandHome(query + strings.TrimPrefix(f, filePref)))
|
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
|
||||||
if err := findExecutable(fullPath, false, true); err != nil {
|
if err := findExecutable(fullPath, false, true); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -66,7 +71,7 @@ func matchPath(query string) ([]string, string) {
|
||||||
var entries []string
|
var entries []string
|
||||||
var baseName string
|
var baseName string
|
||||||
|
|
||||||
path, _ := filepath.Abs(expandHome(filepath.Dir(query)))
|
path, _ := filepath.Abs(util.ExpandHome(filepath.Dir(query)))
|
||||||
if string(query) == "" {
|
if string(query) == "" {
|
||||||
// filepath base below would give us "."
|
// filepath base below would give us "."
|
||||||
// which would cause a match of only dotfiles
|
// which would cause a match of only dotfiles
|
||||||
|
@ -112,3 +117,119 @@ func escapeFilename(fname string) string {
|
||||||
return r.Replace(fname)
|
return r.Replace(fname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func completionLoader(rtm *rt.Runtime) *rt.Table {
|
||||||
|
exports := map[string]util.LuaExport{
|
||||||
|
"files": {luaFileComplete, 3, false},
|
||||||
|
"bins": {luaBinaryComplete, 3, false},
|
||||||
|
"call": {callLuaCompleter, 4, false},
|
||||||
|
"handler": {completionHandler, 2, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := rt.NewTable()
|
||||||
|
util.SetExports(rtm, mod, exports)
|
||||||
|
|
||||||
|
return mod
|
||||||
|
}
|
||||||
|
|
||||||
|
// left as a shim, might doc in the same way as hilbish functions
|
||||||
|
func completionHandler(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
return c.Next(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.CheckNArgs(4); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
completer, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
query, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx, err := c.StringArg(2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fields, err := c.TableArg(3)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var completecb *rt.Closure
|
||||||
|
var ok bool
|
||||||
|
if completecb, ok = luaCompletions[completer]; !ok {
|
||||||
|
return nil, errors.New("completer " + completer + " does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// we must keep the holy 80 cols
|
||||||
|
completerReturn, err := rt.Call1(l.MainThread(),
|
||||||
|
rt.FunctionValue(completecb), rt.StringValue(query),
|
||||||
|
rt.StringValue(ctx), rt.TableValue(fields))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, completerReturn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
query, ctx, fds, err := getCompleteParams(t, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
completions, pfx := fileComplete(query, ctx, fds)
|
||||||
|
luaComps := rt.NewTable()
|
||||||
|
|
||||||
|
for i, comp := range completions {
|
||||||
|
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
query, ctx, fds, err := getCompleteParams(t, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
completions, pfx := binaryComplete(query, ctx, fds)
|
||||||
|
luaComps := rt.NewTable()
|
||||||
|
|
||||||
|
for i, comp := range completions {
|
||||||
|
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.PushingNext(t.Runtime, rt.TableValue(luaComps), rt.StringValue(pfx)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, error) {
|
||||||
|
if err := c.CheckNArgs(3); err != nil {
|
||||||
|
return "", "", []string{}, err
|
||||||
|
}
|
||||||
|
query, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", []string{}, err
|
||||||
|
}
|
||||||
|
ctx, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", []string{}, err
|
||||||
|
}
|
||||||
|
fields, err := c.TableArg(2)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", []string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fds []string
|
||||||
|
util.ForEach(fields, func(k rt.Value, v rt.Value) {
|
||||||
|
if v.Type() == rt.StringType {
|
||||||
|
fds = append(fds, v.AsString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return query, ctx, fds, err
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
Hilbish has a pretty good completion system. It has a nice looking menu,
|
||||||
|
with 2 types of menus: grid (like file completions) or list.
|
||||||
|
|
||||||
|
Like most parts of Hilbish, it's made to be extensible and customizable.
|
||||||
|
The default handler for completions in general can be overwritten to provide
|
||||||
|
more advanced completions if needed.
|
||||||
|
|
||||||
|
# Completion Handler
|
||||||
|
By default, it provides 3 things: for the first argument, binaries (with a
|
||||||
|
plain name requested to complete, those in $PATH), files, or command
|
||||||
|
completions. With the default completion handler, it will try to run a
|
||||||
|
handler for the command or fallback to file completions.
|
||||||
|
|
||||||
|
To overwrite it, just assign a function to `hilbish.completion.handler`
|
||||||
|
like so:
|
||||||
|
function hilbish.completion.handler(line, pos)
|
||||||
|
-- do things
|
||||||
|
end
|
||||||
|
It is passed 2 arguments, the entire line, and the current cursor position.
|
||||||
|
The functions in the completion interface take 3 arguments: query, ctx,
|
||||||
|
and fields. The `query`, which what the user is currently trying to complete,
|
||||||
|
`ctx`, being just the entire line, and `fields` being a table of arguments.
|
||||||
|
It's just `ctx` split up, delimited by spaces.
|
||||||
|
It's expected to return 2 things: a table of completion groups, and a prefix.
|
||||||
|
A completion group is defined as a table with 2 keys: `items` and `type`.
|
||||||
|
The `items` field is just a table of items to use for completions.
|
||||||
|
The `type` is for the completion menu type, being either `grid` or `list`.
|
||||||
|
The prefix is what all the completions start with. It should be empty
|
||||||
|
if the user doesn't have a query. If the beginning of the completion
|
||||||
|
item does not match the prefix, it will be replaced and fixed properly
|
||||||
|
in the line. It is case sensitive.
|
||||||
|
|
||||||
|
If you want to overwrite the functionality of the general completion handler,
|
||||||
|
or make your command completion have files as well (and filter them),
|
||||||
|
then there is the `files` function, which is mentioned below.
|
||||||
|
|
||||||
|
# Completion Interface
|
||||||
|
## Functions
|
||||||
|
- `files(query, ctx, fields)` -> table, prefix: get file completions, based
|
||||||
|
on the user's query.
|
||||||
|
- `bins(query, ctx, fields)` -> table, prefix: get binary/executable
|
||||||
|
completions, based on user query.
|
||||||
|
- `call(scope, query, ctx, fields)` -> table, prefix: call a completion handler
|
||||||
|
with `scope`, usually being in the form of `command.<name>`
|
|
@ -1,3 +1,5 @@
|
||||||
|
abs(path) > Gives an absolute version of `path`.
|
||||||
|
|
||||||
cd(dir) > Changes directory to `dir`
|
cd(dir) > Changes directory to `dir`
|
||||||
|
|
||||||
mkdir(name, recursive) > Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
|
mkdir(name, recursive) > Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
|
||||||
|
|
|
@ -6,9 +6,7 @@ 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 "completion groups."
|
||||||
A completion group is a table with the keys `items` and `type`.
|
Check `doc completions` for more information.
|
||||||
`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
|
||||||
|
|
||||||
|
@ -16,17 +14,18 @@ exec(cmd) > Replaces running hilbish with `cmd`
|
||||||
|
|
||||||
goro(fn) > Puts `fn` in a goroutine
|
goro(fn) > Puts `fn` in a goroutine
|
||||||
|
|
||||||
highlighter(cb) > Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
highlighter(line) > Line highlighter handler. This is mainly for syntax highlighting, but in
|
||||||
reality could set the input of the prompt to display anything. The callback
|
reality could set the input of the prompt to *display* anything. The
|
||||||
is passed the current line as typed and is expected to return a line that will
|
callback is passed the current line and is expected to return a line that
|
||||||
be used to display in the line.
|
will be used as the input display.
|
||||||
|
|
||||||
hinter(cb) > Sets the hinter function. This will be called on every key insert to determine
|
hinter(line, pos) > The command line hint handler. It gets called on every key insert to
|
||||||
what text to use as an inline hint. The callback is passed 2 arguments:
|
determine what text to use as an inline hint. It is passed the current
|
||||||
the current line and the position. It is expected to return a string
|
line and cursor position. It is expected to return a string which is used
|
||||||
which will be used for the hint.
|
as the text for the hint. This is by default a shim. To set hints,
|
||||||
|
override this function with your custom handler.
|
||||||
|
|
||||||
inputMode(mode) > Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
inputMode(mode) > Sets the input mode for Hilbish's line reader. Accepts either emacs or vim
|
||||||
|
|
||||||
interval(cb, time) > Runs the `cb` function every `time` milliseconds.
|
interval(cb, time) > Runs the `cb` function every `time` milliseconds.
|
||||||
Returns a `timer` object (see `doc timers`).
|
Returns a `timer` object (see `doc timers`).
|
||||||
|
@ -59,5 +58,5 @@ will call it to execute user input instead.
|
||||||
timeout(cb, time) > Runs the `cb` function after `time` in milliseconds
|
timeout(cb, time) > Runs the `cb` function after `time` in milliseconds
|
||||||
Returns a `timer` object (see `doc timers`).
|
Returns a `timer` object (see `doc timers`).
|
||||||
|
|
||||||
which(binName) > Searches for an executable called `binName` in the directories of $PATH
|
which(name) > Checks if `name` is a valid command
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
`modeName` is the name of the mode changed to (can be `insert`, `normal`, `delete` or `replace`).
|
`modeName` is the name of the mode changed to (can be `insert`, `normal`, `delete` or `replace`).
|
||||||
|
|
||||||
+ `hilbish.vimAction` -> actionName, args > Sent when the user does a "vim action," being something
|
+ `hilbish.vimAction` -> actionName, args > Sent when the user does a "vim action," being something
|
||||||
like yanking or pasting text. See `doc vimMode actions` for more info.
|
like yanking or pasting text. See `doc vim-mode actions` for more info.
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
A bit after creation, we have the outside nature. Little plants, seeds,
|
||||||
|
growing to their final phase: a full plant. A lot of Hilbish itself is
|
||||||
|
written in Go, but there are parts made in Lua, being the `doc` command,
|
||||||
|
command not executable/found hooks to print a message in the shell,
|
||||||
|
and other things.
|
||||||
|
|
||||||
|
Hilbish's Lua core module is called `nature`. It's handled after everything
|
||||||
|
on the Go side initializes, which is what that first sentence was from.
|
||||||
|
|
||||||
|
# Nature Modules
|
||||||
|
Currently, `nature` provides 1 intended public module: `nature.dirs`.
|
||||||
|
It is a simple API for managing recent directories and old
|
||||||
|
current working directory.
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
local fs = {}
|
local fs = {}
|
||||||
|
|
||||||
|
--- Gives an absolute version of `path`.
|
||||||
|
--- @param path string
|
||||||
|
function fs.abs(path) end
|
||||||
|
|
||||||
--- Changes directory to `dir`
|
--- Changes directory to `dir`
|
||||||
--- @param dir string
|
--- @param dir string
|
||||||
function fs.cd(dir) end
|
function fs.cd(dir) end
|
||||||
|
|
|
@ -15,9 +15,7 @@ function hilbish.appendPath(dir) end
|
||||||
--- 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 "completion groups."
|
||||||
--- A completion group is a table with the keys `items` and `type`.
|
--- Check `doc completions` for more information.
|
||||||
--- `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
|
||||||
|
@ -33,21 +31,23 @@ function hilbish.exec(cmd) end
|
||||||
--- @param fn function
|
--- @param fn function
|
||||||
function hilbish.goro(fn) end
|
function hilbish.goro(fn) end
|
||||||
|
|
||||||
--- Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
--- Line highlighter handler. This is mainly for syntax highlighting, but in
|
||||||
--- reality could set the input of the prompt to display anything. The callback
|
--- reality could set the input of the prompt to *display* anything. The
|
||||||
--- is passed the current line as typed and is expected to return a line that will
|
--- callback is passed the current line and is expected to return a line that
|
||||||
--- be used to display in the line.
|
--- will be used as the input display.
|
||||||
--- @param cb function
|
--- @param line string
|
||||||
function hilbish.highlighter(cb) end
|
function hilbish.highlighter(line) end
|
||||||
|
|
||||||
--- Sets the hinter function. This will be called on every key insert to determine
|
--- The command line hint handler. It gets called on every key insert to
|
||||||
--- what text to use as an inline hint. The callback is passed 2 arguments:
|
--- determine what text to use as an inline hint. It is passed the current
|
||||||
--- the current line and the position. It is expected to return a string
|
--- line and cursor position. It is expected to return a string which is used
|
||||||
--- which will be used for the hint.
|
--- as the text for the hint. This is by default a shim. To set hints,
|
||||||
--- @param cb function
|
--- override this function with your custom handler.
|
||||||
function hilbish.hinter(cb) end
|
--- @param line string
|
||||||
|
--- @param pos int
|
||||||
|
function hilbish.hinter(line, pos) end
|
||||||
|
|
||||||
--- Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
--- Sets the input mode for Hilbish's line reader. Accepts either emacs or vim
|
||||||
--- @param mode string
|
--- @param mode string
|
||||||
function hilbish.inputMode(mode) end
|
function hilbish.inputMode(mode) end
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ function hilbish.runnerMode(mode) end
|
||||||
--- @return table
|
--- @return table
|
||||||
function hilbish.timeout(cb, time) end
|
function hilbish.timeout(cb, time) end
|
||||||
|
|
||||||
--- Searches for an executable called `binName` in the directories of $PATH
|
--- Checks if `name` is a valid command
|
||||||
--- @param binName string
|
--- @param binName string
|
||||||
function hilbish.which(binName) end
|
function hilbish.which(binName) end
|
||||||
|
|
||||||
|
|
114
exec.go
114
exec.go
|
@ -25,8 +25,63 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNotExec = errors.New("not executable")
|
var errNotExec = errors.New("not executable")
|
||||||
|
var errNotFound = errors.New("not found")
|
||||||
var runnerMode rt.Value = rt.StringValue("hybrid")
|
var runnerMode rt.Value = rt.StringValue("hybrid")
|
||||||
|
|
||||||
|
type execError struct{
|
||||||
|
typ string
|
||||||
|
cmd string
|
||||||
|
code int
|
||||||
|
colon bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e execError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.cmd, e.typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e execError) sprint() error {
|
||||||
|
sep := " "
|
||||||
|
if e.colon {
|
||||||
|
sep = ": "
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("hilbish: %s%s%s", e.cmd, sep, e.err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExecError(err error) (execError, bool) {
|
||||||
|
if exErr, ok := err.(execError); ok {
|
||||||
|
return exErr, true
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Split(err.Error(), ": ")
|
||||||
|
knownTypes := []string{
|
||||||
|
"not-found",
|
||||||
|
"not-executable",
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fields) > 1 && contains(knownTypes, fields[1]) {
|
||||||
|
var colon bool
|
||||||
|
var e error
|
||||||
|
switch fields[1] {
|
||||||
|
case "not-found":
|
||||||
|
e = errNotFound
|
||||||
|
case "not-executable":
|
||||||
|
colon = true
|
||||||
|
e = errNotExec
|
||||||
|
}
|
||||||
|
|
||||||
|
return execError{
|
||||||
|
cmd: fields[0],
|
||||||
|
typ: fields[1],
|
||||||
|
colon: colon,
|
||||||
|
err: e,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return execError{}, false
|
||||||
|
}
|
||||||
|
|
||||||
func runInput(input string, priv bool) {
|
func runInput(input string, priv bool) {
|
||||||
running = true
|
running = true
|
||||||
cmdString := aliases.Resolve(input)
|
cmdString := aliases.Resolve(input)
|
||||||
|
@ -43,10 +98,6 @@ func runInput(input string, priv bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
input, exitCode, err = handleSh(input)
|
input, exitCode, err = handleSh(input)
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
}
|
|
||||||
cmdFinish(exitCode, input, priv)
|
|
||||||
case "hybridRev":
|
case "hybridRev":
|
||||||
_, _, err = handleSh(input)
|
_, _, err = handleSh(input)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -54,38 +105,25 @@ func runInput(input string, priv bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
input, exitCode, err = handleLua(cmdString)
|
input, exitCode, err = handleLua(cmdString)
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
}
|
|
||||||
cmdFinish(exitCode, input, priv)
|
|
||||||
case "lua":
|
case "lua":
|
||||||
input, exitCode, err = handleLua(cmdString)
|
input, exitCode, err = handleLua(cmdString)
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
}
|
|
||||||
cmdFinish(exitCode, input, priv)
|
|
||||||
case "sh":
|
case "sh":
|
||||||
input, exitCode, err = handleSh(input)
|
input, exitCode, err = handleSh(input)
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
}
|
|
||||||
cmdFinish(exitCode, input, priv)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// can only be a string or function so
|
// can only be a string or function so
|
||||||
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 2, false)
|
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false)
|
||||||
err := rt.Call(l.MainThread(), runnerMode, []rt.Value{rt.StringValue(cmdString)}, term)
|
err = rt.Call(l.MainThread(), runnerMode, []rt.Value{rt.StringValue(cmdString)}, term)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
cmdFinish(124, input, priv)
|
cmdFinish(124, input, priv)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
luaexitcode := term.Get(0)
|
luaInput := term.Get(0)
|
||||||
runErr := term.Get(1)
|
luaexitcode := term.Get(1)
|
||||||
luaInput := term.Get(1)
|
runErr := term.Get(2)
|
||||||
|
|
||||||
var exitCode uint8
|
|
||||||
if code, ok := luaexitcode.TryInt(); ok {
|
if code, ok := luaexitcode.TryInt(); ok {
|
||||||
exitCode = uint8(code)
|
exitCode = uint8(code)
|
||||||
}
|
}
|
||||||
|
@ -94,11 +132,19 @@ func runInput(input string, priv bool) {
|
||||||
input = inp
|
input = inp
|
||||||
}
|
}
|
||||||
|
|
||||||
if runErr != rt.NilValue {
|
if errStr, ok := runErr.TryString(); ok {
|
||||||
fmt.Fprintln(os.Stderr, runErr)
|
err = fmt.Errorf("%s", errStr)
|
||||||
}
|
}
|
||||||
cmdFinish(exitCode, input, priv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if exErr, ok := isExecError(err); ok {
|
||||||
|
hooks.Em.Emit("command." + exErr.typ, exErr.cmd)
|
||||||
|
err = exErr.sprint()
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
cmdFinish(exitCode, input, priv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLua(cmdString string) (string, uint8, error) {
|
func handleLua(cmdString string) (string, uint8, error) {
|
||||||
|
@ -255,12 +301,20 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
|
||||||
|
|
||||||
err := lookpath(args[0])
|
err := lookpath(args[0])
|
||||||
if err == errNotExec {
|
if err == errNotExec {
|
||||||
hooks.Em.Emit("command.no-perm", args[0])
|
return execError{
|
||||||
hooks.Em.Emit("command.not-executable", args[0])
|
typ: "not-executable",
|
||||||
return interp.NewExitStatus(126)
|
cmd: args[0],
|
||||||
|
code: 126,
|
||||||
|
colon: true,
|
||||||
|
err: errNotExec,
|
||||||
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
hooks.Em.Emit("command.not-found", args[0])
|
return execError{
|
||||||
return interp.NewExitStatus(127)
|
typ: "not-found",
|
||||||
|
cmd: args[0],
|
||||||
|
code: 127,
|
||||||
|
err: errNotFound,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
killTimeout := 2 * time.Second
|
killTimeout := 2 * time.Second
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -22,13 +23,14 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
"mkdir": util.LuaExport{fmkdir, 2, false},
|
"mkdir": util.LuaExport{fmkdir, 2, false},
|
||||||
"stat": util.LuaExport{fstat, 1, false},
|
"stat": util.LuaExport{fstat, 1, false},
|
||||||
"readdir": util.LuaExport{freaddir, 1, false},
|
"readdir": util.LuaExport{freaddir, 1, false},
|
||||||
|
"abs": util.LuaExport{fabs, 1, false},
|
||||||
}
|
}
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
util.SetExports(rtm, mod, exports)
|
util.SetExports(rtm, mod, exports)
|
||||||
|
|
||||||
util.Document(mod, `The fs module provides easy and simple access to
|
util.Document(mod, `The fs module provides easy and simple access to
|
||||||
filesystem functions and other things, and acts an
|
filesystem functions and other things, and acts an
|
||||||
addition to the Lua standard library's I/O and fs functions.`)
|
addition to the Lua standard library's I/O and filesystem functions.`)
|
||||||
|
|
||||||
return rt.TableValue(mod), nil
|
return rt.TableValue(mod), nil
|
||||||
}
|
}
|
||||||
|
@ -44,8 +46,9 @@ func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
path = util.ExpandHome(strings.TrimSpace(path))
|
||||||
|
|
||||||
err = os.Chdir(strings.TrimSpace(path))
|
err = os.Chdir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -61,7 +64,7 @@ func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.CheckNArgs(2); err != nil {
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dirname, err := c.StringArg(0)
|
path, err := c.StringArg(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -69,7 +72,7 @@ func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
path := strings.TrimSpace(dirname)
|
path = util.ExpandHome(strings.TrimSpace(path))
|
||||||
|
|
||||||
if recursive {
|
if recursive {
|
||||||
err = os.MkdirAll(path, 0744)
|
err = os.MkdirAll(path, 0744)
|
||||||
|
@ -94,6 +97,7 @@ func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
path = util.ExpandHome(path)
|
||||||
|
|
||||||
pathinfo, err := os.Stat(path)
|
pathinfo, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -120,6 +124,7 @@ func freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
dir = util.ExpandHome(dir)
|
||||||
names := rt.NewTable()
|
names := rt.NewTable()
|
||||||
|
|
||||||
dirEntries, err := os.ReadDir(dir)
|
dirEntries, err := os.ReadDir(dir)
|
||||||
|
@ -132,3 +137,21 @@ func freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.TableValue(names)), nil
|
return c.PushingNext1(t.Runtime, rt.TableValue(names)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// abs(path)
|
||||||
|
// Gives an absolute version of `path`.
|
||||||
|
// --- @param path string
|
||||||
|
func fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
path, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
path = util.ExpandHome(path)
|
||||||
|
|
||||||
|
abspath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, rt.StringValue(abspath)), nil
|
||||||
|
}
|
||||||
|
|
14
lua.go
14
lua.go
|
@ -51,14 +51,16 @@ func luaInit() {
|
||||||
// Add more paths that Lua can require from
|
// Add more paths that Lua can require from
|
||||||
err := util.DoString(l, "package.path = package.path .. " + requirePaths)
|
err := util.DoString(l, "package.path = package.path .. " + requirePaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Could not add preload paths! Libraries will be missing. This shouldn't happen.")
|
fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = util.DoFile(l, "prelude/init.lua")
|
err1 := util.DoFile(l, "nature/init.lua")
|
||||||
if err != nil {
|
if err1 != nil {
|
||||||
err = util.DoFile(l, preloadPath)
|
err2 := util.DoFile(l, preloadPath)
|
||||||
if err != nil {
|
if err2 != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Missing preload file, builtins may be missing.")
|
fmt.Fprintln(os.Stderr, "Missing nature module, some functionality and builtins will be missing.")
|
||||||
|
fmt.Fprintln(os.Stderr, "local error:", err1)
|
||||||
|
fmt.Fprintln(os.Stderr, "global install error:", err2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
main.go
9
main.go
|
@ -56,13 +56,13 @@ func main() {
|
||||||
defaultConfDir = filepath.Join(confDir, "hilbish")
|
defaultConfDir = filepath.Join(confDir, "hilbish")
|
||||||
} else {
|
} else {
|
||||||
// else do ~ substitution
|
// else do ~ substitution
|
||||||
defaultConfDir = filepath.Join(expandHome(defaultConfDir), "hilbish")
|
defaultConfDir = filepath.Join(util.ExpandHome(defaultConfDir), "hilbish")
|
||||||
}
|
}
|
||||||
defaultConfPath = filepath.Join(defaultConfDir, "init.lua")
|
defaultConfPath = filepath.Join(defaultConfDir, "init.lua")
|
||||||
if defaultHistDir == "" {
|
if defaultHistDir == "" {
|
||||||
defaultHistDir = filepath.Join(userDataDir, "hilbish")
|
defaultHistDir = filepath.Join(userDataDir, "hilbish")
|
||||||
} else {
|
} else {
|
||||||
defaultHistDir = expandHome(defaultHistDir)
|
defaultHistDir = filepath.Join(util.ExpandHome(defaultHistDir), "hilbish")
|
||||||
}
|
}
|
||||||
defaultHistPath = filepath.Join(defaultHistDir, ".hilbish-history")
|
defaultHistPath = filepath.Join(defaultHistDir, ".hilbish-history")
|
||||||
helpflag := getopt.BoolLong("help", 'h', "Prints Hilbish flags")
|
helpflag := getopt.BoolLong("help", 'h', "Prints Hilbish flags")
|
||||||
|
@ -273,11 +273,6 @@ func handleHistory(cmd string) {
|
||||||
// TODO: load history again (history shared between sessions like this ye)
|
// TODO: load history again (history shared between sessions like this ye)
|
||||||
}
|
}
|
||||||
|
|
||||||
func expandHome(path string) string {
|
|
||||||
homedir := curuser.HomeDir
|
|
||||||
return strings.Replace(path, "~", homedir, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDupes(slice []string) []string {
|
func removeDupes(slice []string) []string {
|
||||||
all := make(map[string]bool)
|
all := make(map[string]bool)
|
||||||
newSlice := []string{}
|
newSlice := []string{}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
local bait = require 'bait'
|
||||||
|
local commander = require 'commander'
|
||||||
|
local fs = require 'fs'
|
||||||
|
local dirs = require 'nature.dirs'
|
||||||
|
|
||||||
|
dirs.old = hilbish.cwd()
|
||||||
|
commander.register('cd', function (args)
|
||||||
|
if #args > 1 then
|
||||||
|
print("cd: too many arguments")
|
||||||
|
return 1
|
||||||
|
elseif #args > 0 then
|
||||||
|
local path = args[1]:gsub('$%$','\0'):gsub('${([%w_]+)}', os.getenv)
|
||||||
|
:gsub('$([%w_]+)', os.getenv):gsub('%z','$'):gsub('^%s*(.-)%s*$', '%1')
|
||||||
|
|
||||||
|
if path == '-' then
|
||||||
|
path = dirs.old
|
||||||
|
print(path)
|
||||||
|
end
|
||||||
|
dirs.setOld(hilbish.cwd())
|
||||||
|
dirs.push(path)
|
||||||
|
|
||||||
|
local ok, err = pcall(function() fs.cd(path) end)
|
||||||
|
if not ok then
|
||||||
|
print(err)
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
bait.throw('cd', path)
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
fs.cd(hilbish.home)
|
||||||
|
bait.throw('cd', hilbish.home)
|
||||||
|
|
||||||
|
dirs.push(hilbish.home)
|
||||||
|
end)
|
|
@ -0,0 +1,39 @@
|
||||||
|
local commander = require 'commander'
|
||||||
|
local fs = require 'fs'
|
||||||
|
local lunacolors = require 'lunacolors'
|
||||||
|
local dirs = require 'nature.dirs'
|
||||||
|
|
||||||
|
commander.register('cdr', function(args)
|
||||||
|
if not args[1] then
|
||||||
|
print(lunacolors.format [[
|
||||||
|
cdr: change directory to one which has been recently visied
|
||||||
|
|
||||||
|
usage: cdr <index>
|
||||||
|
|
||||||
|
to get a list of recent directories, use {green}{underline}cdr list{reset}]])
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if args[1] == 'list' then
|
||||||
|
local recentDirs = dirs.recentDirs
|
||||||
|
if #recentDirs == 0 then
|
||||||
|
print 'No directories have been visited.'
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
print(table.concat(recentDirs, '\n'))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local index = tonumber(args[1])
|
||||||
|
if not index then
|
||||||
|
print(string.format('Received %s as index, which isn\'t a number.', index))
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if not dirs.recent(index) then
|
||||||
|
print(string.format('No recent directory found at index %s.', index))
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
fs.cd(dirs.recent(index))
|
||||||
|
end)
|
|
@ -0,0 +1,93 @@
|
||||||
|
local commander = require 'commander'
|
||||||
|
local fs = require 'fs'
|
||||||
|
local lunacolors = require 'lunacolors'
|
||||||
|
|
||||||
|
commander.register('doc', function(args)
|
||||||
|
local moddocPath = hilbish.dataDir .. '/docs/'
|
||||||
|
local modDocFormat = [[
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
# Functions
|
||||||
|
]]
|
||||||
|
|
||||||
|
if #args > 0 then
|
||||||
|
local mod = args[1]
|
||||||
|
|
||||||
|
local f = io.open(moddocPath .. mod .. '.txt', 'rb')
|
||||||
|
local funcdocs = nil
|
||||||
|
if not f then
|
||||||
|
-- assume subdir
|
||||||
|
-- dataDir/docs/<mod>/<mod>.txt
|
||||||
|
moddocPath = moddocPath .. mod .. '/'
|
||||||
|
local subdocName = args[2]
|
||||||
|
if not subdocName then
|
||||||
|
subdocName = 'index'
|
||||||
|
end
|
||||||
|
f = io.open(moddocPath .. subdocName .. '.txt', 'rb')
|
||||||
|
if not f then
|
||||||
|
print('No documentation found for ' .. mod .. '.')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
funcdocs = f:read '*a'
|
||||||
|
local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= 'index.txt' end)
|
||||||
|
local subdocs = table.map(moddocs, function(fname)
|
||||||
|
return lunacolors.underline(lunacolors.blue(string.gsub(fname, '.txt', '')))
|
||||||
|
end)
|
||||||
|
if subdocName == 'index' then
|
||||||
|
funcdocs = funcdocs .. '\nSubdocs: ' .. table.concat(subdocs, ', ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not funcdocs then
|
||||||
|
funcdocs = f:read '*a'
|
||||||
|
end
|
||||||
|
local desc = ''
|
||||||
|
local ok = pcall(require, mod)
|
||||||
|
local backtickOccurence = 0
|
||||||
|
local formattedFuncs = lunacolors.format(funcdocs:sub(1, #funcdocs - 1):gsub('`', function()
|
||||||
|
backtickOccurence = backtickOccurence + 1
|
||||||
|
if backtickOccurence % 2 == 0 then
|
||||||
|
return '{reset}'
|
||||||
|
else
|
||||||
|
return '{underline}{green}'
|
||||||
|
end
|
||||||
|
end))
|
||||||
|
|
||||||
|
if ok then
|
||||||
|
local props = {}
|
||||||
|
local propstr = ''
|
||||||
|
local modDesc = ''
|
||||||
|
local modmt = getmetatable(require(mod))
|
||||||
|
modDesc = modmt.__doc
|
||||||
|
if modmt.__docProp then
|
||||||
|
-- not all modules have docs for properties
|
||||||
|
props = table.map(modmt.__docProp, function(v, k)
|
||||||
|
return lunacolors.underline(lunacolors.blue(k)) .. ' > ' .. v
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
if #props > 0 then
|
||||||
|
propstr = '\n# Properties\n' .. table.concat(props, '\n') .. '\n'
|
||||||
|
end
|
||||||
|
desc = string.format(modDocFormat, modDesc, propstr)
|
||||||
|
end
|
||||||
|
print(desc .. formattedFuncs)
|
||||||
|
f:close()
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local modules = table.map(fs.readdir(moddocPath), function(f)
|
||||||
|
return lunacolors.underline(lunacolors.blue(string.gsub(f, '.txt', '')))
|
||||||
|
end)
|
||||||
|
|
||||||
|
io.write [[
|
||||||
|
Welcome to Hilbish's doc tool! Here you can find documentation for builtin
|
||||||
|
functions and other things.
|
||||||
|
|
||||||
|
Usage: doc <section> [subdoc]
|
||||||
|
A section is a module or a literal section and a subdoc is a subsection for it.
|
||||||
|
|
||||||
|
Available sections: ]]
|
||||||
|
io.flush()
|
||||||
|
|
||||||
|
print(table.concat(modules, ', '))
|
||||||
|
end)
|
|
@ -0,0 +1,7 @@
|
||||||
|
local bait = require 'bait'
|
||||||
|
local commander = require 'commander'
|
||||||
|
|
||||||
|
commander.register('exit', function()
|
||||||
|
bait.throw('hilbish.exit')
|
||||||
|
os.exit(0)
|
||||||
|
end)
|
|
@ -0,0 +1,54 @@
|
||||||
|
local ansikit = require 'ansikit'
|
||||||
|
local commander = require 'commander'
|
||||||
|
|
||||||
|
local helpTexts = {
|
||||||
|
[[
|
||||||
|
Hello there! Welcome to Hilbish, the comfy and nice little shell for
|
||||||
|
Lua users and fans. Hilbish is configured with Lua, and its
|
||||||
|
scripts are also in Lua. It also runs both Lua and shell script when
|
||||||
|
interactive (aka normal usage).
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
What does that mean for you, the user? It means that if you prefer to
|
||||||
|
use Lua for scripting instead of shell script but still have ordinary
|
||||||
|
shell usage for interactive use.
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
If this is your first time using Hilbish and Lua, check out the
|
||||||
|
Programming in Lua book here: https://www.lua.org/pil
|
||||||
|
After (or if you already know Lua) check out the doc command.
|
||||||
|
It is an in shell tool for documentation about Hilbish provided
|
||||||
|
functions and modules.
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
If you've updated from a pre-1.0 version (0.7.1 as an example)
|
||||||
|
you'll want to move your config from ~/.hilbishrc.lua to
|
||||||
|
]] ..
|
||||||
|
hilbish.userDir.config .. '/hilbish/init.lua' ..
|
||||||
|
[[
|
||||||
|
|
||||||
|
and also change all global functions (prompt, alias) to be
|
||||||
|
in the hilbish module (hilbish.prompt, hilbish.alias as examples).
|
||||||
|
|
||||||
|
And if this is your first time (most likely), you can copy a config
|
||||||
|
from ]] .. hilbish.dataDir,
|
||||||
|
[[
|
||||||
|
Since 1.0 is a big release, you'll want to check the changelog
|
||||||
|
at https://github.com/Rosettea/Hilbish/releases/tag/v1.0.0
|
||||||
|
to find more breaking changes.
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
commander.register('guide', function()
|
||||||
|
ansikit.clear()
|
||||||
|
ansikit.cursorTo(0, 0)
|
||||||
|
for _, text in ipairs(helpTexts) do
|
||||||
|
print(text)
|
||||||
|
local out = hilbish.read('Hit enter to continue ')
|
||||||
|
ansikit.clear()
|
||||||
|
ansikit.cursorTo(0, 0)
|
||||||
|
if not out then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print 'Hope you enjoy using Hilbish!'
|
||||||
|
end)
|
|
@ -0,0 +1,6 @@
|
||||||
|
-- Add command builtins
|
||||||
|
require 'nature.commands.cd'
|
||||||
|
require 'nature.commands.cdr'
|
||||||
|
require 'nature.commands.doc'
|
||||||
|
require 'nature.commands.exit'
|
||||||
|
require 'nature.commands.guide'
|
|
@ -0,0 +1,40 @@
|
||||||
|
function hilbish.completion.handler(line, pos)
|
||||||
|
if type(line) ~= 'string' then error '#1 must be a string' end
|
||||||
|
if type(pos) ~= 'number' then error '#2 must be a number' end
|
||||||
|
|
||||||
|
-- trim leading whitespace
|
||||||
|
local ctx = line:gsub('^%s*(.-)$', '%1')
|
||||||
|
if ctx:len() == 0 then return {}, '' end
|
||||||
|
|
||||||
|
local res = hilbish.aliases.resolve(ctx)
|
||||||
|
local resFields = string.split(res, ' ')
|
||||||
|
local fields = string.split(ctx, ' ')
|
||||||
|
if #fields > 1 and #resFields > 1 then
|
||||||
|
fields = resFields
|
||||||
|
end
|
||||||
|
local query = fields[#fields]
|
||||||
|
|
||||||
|
if #fields == 1 then
|
||||||
|
local comps, pfx = hilbish.completion.bins(query, ctx, fields)
|
||||||
|
local compGroup = {
|
||||||
|
items = comps,
|
||||||
|
type = 'grid'
|
||||||
|
}
|
||||||
|
|
||||||
|
return {compGroup}, pfx
|
||||||
|
else
|
||||||
|
local ok, compGroups, pfx = pcall(hilbish.completion.call,
|
||||||
|
'command.' .. #fields[1], query, ctx, fields)
|
||||||
|
if ok then
|
||||||
|
return compGroups, pfx
|
||||||
|
end
|
||||||
|
|
||||||
|
local comps, pfx = hilbish.completion.files(query, ctx, fields)
|
||||||
|
local compGroup = {
|
||||||
|
items = comps,
|
||||||
|
type = 'grid'
|
||||||
|
}
|
||||||
|
|
||||||
|
return {compGroup}, pfx
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,75 @@
|
||||||
|
local fs = require 'fs'
|
||||||
|
|
||||||
|
local dirs = {}
|
||||||
|
|
||||||
|
--- Last (current working) directory. Separate from recentDirs mainly for
|
||||||
|
--- easier use.
|
||||||
|
dirs.old = ''
|
||||||
|
--- Table of recent directories. For use, look at public functions.
|
||||||
|
dirs.recentDirs = {}
|
||||||
|
--- Size of the recentDirs table.
|
||||||
|
dirs.recentSize = 10
|
||||||
|
|
||||||
|
--- Get (and remove) a `num` of entries from recent directories.
|
||||||
|
--- @param num number
|
||||||
|
--- @param remove boolean Whether to remove items
|
||||||
|
function dirRecents(num, remove)
|
||||||
|
num = num or 1
|
||||||
|
local entries = {}
|
||||||
|
|
||||||
|
if #dirs.recentDirs ~= 0 then
|
||||||
|
for i = 1, num do
|
||||||
|
local idx = remove and 1 or i
|
||||||
|
if not dirs.recentDirs[idx] then break end
|
||||||
|
table.insert(entries, dirs.recentDirs[idx])
|
||||||
|
if remove then table.remove(dirs.recentDirs, 1) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #entries == 1 then
|
||||||
|
return entries[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Look at `num` amount of recent directories, starting from the latest.
|
||||||
|
--- @param num? number
|
||||||
|
function dirs.peak(num)
|
||||||
|
return dirRecents(num)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add `d` to the recent directories.
|
||||||
|
function dirs.push(d)
|
||||||
|
dirs.recentDirs[dirs.recentSize + 1] = nil
|
||||||
|
if dirs.recentDirs[#dirs.recentDirs - 1] ~= d then
|
||||||
|
ok, d = pcall(fs.abs, d)
|
||||||
|
assert(ok, 'could not turn "' .. d .. '"into an absolute path')
|
||||||
|
|
||||||
|
table.insert(dirs.recentDirs, 1, d)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Remove `num` amount of dirs from the recent directories.
|
||||||
|
--- @param num number
|
||||||
|
function dirs.pop(num)
|
||||||
|
return dirRecents(num, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get entry from recent directories.
|
||||||
|
--- @param idx number
|
||||||
|
function dirs.recent(idx)
|
||||||
|
return dirs.recentDirs[idx]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Sets the old directory.
|
||||||
|
--- @param d string
|
||||||
|
function dirs.setOld(d)
|
||||||
|
ok, d = pcall(fs.abs, d)
|
||||||
|
assert(ok, 'could not turn "' .. d .. '"into an absolute path')
|
||||||
|
|
||||||
|
os.setenv('OLDPWD', d)
|
||||||
|
dirs.old = d
|
||||||
|
end
|
||||||
|
|
||||||
|
return dirs
|
|
@ -0,0 +1,45 @@
|
||||||
|
-- Prelude initializes everything else for our shell
|
||||||
|
local _ = require 'succulent' -- Function additions
|
||||||
|
|
||||||
|
package.path = package.path .. ';' .. hilbish.dataDir .. '/?/init.lua'
|
||||||
|
.. ';' .. hilbish.dataDir .. '/?/?.lua'
|
||||||
|
|
||||||
|
require 'nature.commands'
|
||||||
|
require 'nature.completions'
|
||||||
|
require 'nature.opts'
|
||||||
|
|
||||||
|
local shlvl = tonumber(os.getenv 'SHLVL')
|
||||||
|
if shlvl ~= nil then
|
||||||
|
os.setenv('SHLVL', tostring(shlvl + 1))
|
||||||
|
else
|
||||||
|
os.setenv('SHLVL', '0')
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local virt_G = { }
|
||||||
|
|
||||||
|
setmetatable(_G, {
|
||||||
|
__index = function (_, key)
|
||||||
|
local got_virt = virt_G[key]
|
||||||
|
if got_virt ~= nil then
|
||||||
|
return got_virt
|
||||||
|
end
|
||||||
|
|
||||||
|
virt_G[key] = os.getenv(key)
|
||||||
|
return virt_G[key]
|
||||||
|
end,
|
||||||
|
|
||||||
|
__newindex = function (_, key, value)
|
||||||
|
if type(value) == 'string' then
|
||||||
|
os.setenv(key, value)
|
||||||
|
virt_G[key] = value
|
||||||
|
else
|
||||||
|
if type(virt_G[key]) == 'string' then
|
||||||
|
os.setenv(key, '')
|
||||||
|
end
|
||||||
|
virt_G[key] = value
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
local fs = require 'fs'
|
||||||
|
|
||||||
|
function cdHandle(inp)
|
||||||
|
local input, exit, err = hilbish.runner.lua(inp)
|
||||||
|
|
||||||
|
if not err then
|
||||||
|
return input, exit, err
|
||||||
|
end
|
||||||
|
|
||||||
|
input, exit, err = hilbish.runner.sh(inp)
|
||||||
|
|
||||||
|
if exit ~= 0 and hilbish.opts.autocd then
|
||||||
|
local ok, stat = pcall(fs.stat, input)
|
||||||
|
if ok and stat.isDir then
|
||||||
|
-- discard here to not append the cd, which will be in history
|
||||||
|
_, exit, err = hilbish.runner.sh('cd ' .. input)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return input, exit, err
|
||||||
|
end
|
||||||
|
|
||||||
|
hilbish.runner.setMode(cdHandle)
|
|
@ -0,0 +1,28 @@
|
||||||
|
local opts = {}
|
||||||
|
hilbish.opts = {}
|
||||||
|
|
||||||
|
setmetatable(hilbish.opts, {
|
||||||
|
__newindex = function(_, k, v)
|
||||||
|
if opts[k] == nil then
|
||||||
|
error(string.format('opt %s does not exist', k))
|
||||||
|
end
|
||||||
|
|
||||||
|
opts[k] = v
|
||||||
|
end,
|
||||||
|
__index = function(_, k)
|
||||||
|
return opts[k]
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
local function setupOpt(name, default)
|
||||||
|
opts[name] = default
|
||||||
|
require('nature.opts.' .. name)
|
||||||
|
end
|
||||||
|
|
||||||
|
local defaultOpts = {
|
||||||
|
autocd = false
|
||||||
|
}
|
||||||
|
|
||||||
|
for optsName, default in pairs(defaultOpts) do
|
||||||
|
setupOpt(optsName, default)
|
||||||
|
end
|
264
prelude/init.lua
264
prelude/init.lua
|
@ -1,264 +0,0 @@
|
||||||
-- The preload file initializes everything else for our shell
|
|
||||||
local ansikit = require 'ansikit'
|
|
||||||
local bait = require 'bait'
|
|
||||||
local commander = require 'commander'
|
|
||||||
local fs = require 'fs'
|
|
||||||
local lunacolors = require 'lunacolors'
|
|
||||||
local _ = require 'succulent' -- Function additions
|
|
||||||
local oldDir = hilbish.cwd()
|
|
||||||
|
|
||||||
local shlvl = tonumber(os.getenv 'SHLVL')
|
|
||||||
if shlvl ~= nil then os.setenv('SHLVL', tostring(shlvl + 1)) else os.setenv('SHLVL', '0') end
|
|
||||||
|
|
||||||
-- Builtins
|
|
||||||
local recentDirs = {}
|
|
||||||
commander.register('cd', function (args)
|
|
||||||
if #args > 0 then
|
|
||||||
local path = table.concat(args, ' '):gsub('$%$','\0'):gsub('${([%w_]+)}', os.getenv)
|
|
||||||
:gsub('$([%w_]+)', os.getenv):gsub('%z','$'):gsub('^%s*(.-)%s*$', '%1')
|
|
||||||
|
|
||||||
if path == '-' then
|
|
||||||
path = oldDir
|
|
||||||
print(path)
|
|
||||||
end
|
|
||||||
oldDir = hilbish.cwd()
|
|
||||||
|
|
||||||
local ok, err = pcall(function() fs.cd(path) end)
|
|
||||||
if not ok then
|
|
||||||
print(err:sub(17))
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
bait.throw('cd', path)
|
|
||||||
|
|
||||||
-- add to table of recent dirs
|
|
||||||
recentDirs[11] = nil
|
|
||||||
if recentDirs[#recentDirs - 1] ~= path then
|
|
||||||
table.insert(recentDirs, 1, path)
|
|
||||||
end
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
fs.cd(hilbish.home)
|
|
||||||
bait.throw('cd', hilbish.home)
|
|
||||||
|
|
||||||
table.insert(recentDirs, 1, hilbish.home)
|
|
||||||
recentDirs[11] = nil
|
|
||||||
end)
|
|
||||||
|
|
||||||
commander.register('exit', function()
|
|
||||||
bait.throw('hilbish.exit')
|
|
||||||
os.exit(0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
commander.register('doc', function(args)
|
|
||||||
local moddocPath = hilbish.dataDir .. '/docs/'
|
|
||||||
local modDocFormat = [[
|
|
||||||
%s
|
|
||||||
%s
|
|
||||||
# Functions
|
|
||||||
]]
|
|
||||||
|
|
||||||
if #args > 0 then
|
|
||||||
local mod = args[1]
|
|
||||||
|
|
||||||
local f = io.open(moddocPath .. mod .. '.txt', 'rb')
|
|
||||||
local funcdocs = nil
|
|
||||||
if not f then
|
|
||||||
-- assume subdir
|
|
||||||
-- dataDir/docs/<mod>/<mod>.txt
|
|
||||||
moddocPath = moddocPath .. mod .. '/'
|
|
||||||
local subdocName = args[2]
|
|
||||||
if not subdocName then
|
|
||||||
subdocName = 'index'
|
|
||||||
end
|
|
||||||
f = io.open(moddocPath .. subdocName .. '.txt', 'rb')
|
|
||||||
if not f then
|
|
||||||
print('No documentation found for ' .. mod .. '.')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
funcdocs = f:read '*a'
|
|
||||||
local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= 'index.txt' end)
|
|
||||||
local subdocs = table.map(moddocs, function(fname)
|
|
||||||
return lunacolors.underline(lunacolors.blue(string.gsub(fname, '.txt', '')))
|
|
||||||
end)
|
|
||||||
if subdocName == 'index' then
|
|
||||||
funcdocs = funcdocs .. '\nSubdocs: ' .. table.concat(subdocs, ', ')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not funcdocs then
|
|
||||||
funcdocs = f:read '*a'
|
|
||||||
end
|
|
||||||
local desc = ''
|
|
||||||
local ok = pcall(require, mod)
|
|
||||||
local backtickOccurence = 0
|
|
||||||
local formattedFuncs = lunacolors.format(funcdocs:sub(1, #funcdocs - 1):gsub('`', function()
|
|
||||||
backtickOccurence = backtickOccurence + 1
|
|
||||||
if backtickOccurence % 2 == 0 then
|
|
||||||
return '{reset}'
|
|
||||||
else
|
|
||||||
return '{underline}{green}'
|
|
||||||
end
|
|
||||||
end))
|
|
||||||
|
|
||||||
if ok then
|
|
||||||
local props = {}
|
|
||||||
local propstr = ''
|
|
||||||
local modDesc = ''
|
|
||||||
local modmt = getmetatable(require(mod))
|
|
||||||
modDesc = modmt.__doc
|
|
||||||
if modmt.__docProp then
|
|
||||||
-- not all modules have docs for properties
|
|
||||||
props = table.map(modmt.__docProp, function(v, k)
|
|
||||||
return lunacolors.underline(lunacolors.blue(k)) .. ' > ' .. v
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
if #props > 0 then
|
|
||||||
propstr = '\n# Properties\n' .. table.concat(props, '\n') .. '\n'
|
|
||||||
end
|
|
||||||
desc = string.format(modDocFormat, modDesc, propstr)
|
|
||||||
end
|
|
||||||
print(desc .. formattedFuncs)
|
|
||||||
f:close()
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local modules = table.map(fs.readdir(moddocPath), function(f)
|
|
||||||
return lunacolors.underline(lunacolors.blue(string.gsub(f, '.txt', '')))
|
|
||||||
end)
|
|
||||||
|
|
||||||
io.write [[
|
|
||||||
Welcome to Hilbish's doc tool! Here you can find documentation for builtin
|
|
||||||
functions and other things.
|
|
||||||
|
|
||||||
Usage: doc <section> [subdoc]
|
|
||||||
A section is a module or a literal section and a subdoc is a subsection for it.
|
|
||||||
|
|
||||||
Available sections: ]]
|
|
||||||
|
|
||||||
print(table.concat(modules, ', '))
|
|
||||||
end)
|
|
||||||
|
|
||||||
local helpTexts = {
|
|
||||||
[[
|
|
||||||
Hello there! Welcome to Hilbish, the comfy and nice little shell for
|
|
||||||
Lua users and fans. Hilbish is configured with Lua, and its
|
|
||||||
scripts are also in Lua. It also runs both Lua and shell script when
|
|
||||||
interactive (aka normal usage).
|
|
||||||
]],
|
|
||||||
[[
|
|
||||||
What does that mean for you, the user? It means that if you prefer to
|
|
||||||
use Lua for scripting instead of shell script but still have ordinary
|
|
||||||
shell usage for interactive use.
|
|
||||||
]],
|
|
||||||
[[
|
|
||||||
If this is your first time using Hilbish and Lua, check out the
|
|
||||||
Programming in Lua book here: https://www.lua.org/pil
|
|
||||||
After (or if you already know Lua) check out the doc command.
|
|
||||||
It is an in shell tool for documentation about Hilbish provided
|
|
||||||
functions and modules.
|
|
||||||
]],
|
|
||||||
[[
|
|
||||||
If you've updated from a pre-1.0 version (0.7.1 as an example)
|
|
||||||
you'll want to move your config from ~/.hilbishrc.lua to
|
|
||||||
]] ..
|
|
||||||
hilbish.userDir.config .. '/hilbish/init.lua' ..
|
|
||||||
[[
|
|
||||||
|
|
||||||
and also change all global functions (prompt, alias) to be
|
|
||||||
in the hilbish module (hilbish.prompt, hilbish.alias as examples).
|
|
||||||
|
|
||||||
And if this is your first time (most likely), you can copy a config
|
|
||||||
from ]] .. hilbish.dataDir,
|
|
||||||
[[
|
|
||||||
Since 1.0 is a big release, you'll want to check the changelog
|
|
||||||
at https://github.com/Rosettea/Hilbish/releases/tag/v1.0.0
|
|
||||||
to find more breaking changes.
|
|
||||||
]]
|
|
||||||
}
|
|
||||||
commander.register('guide', function()
|
|
||||||
ansikit.clear()
|
|
||||||
ansikit.cursorTo(0, 0)
|
|
||||||
for _, text in ipairs(helpTexts) do
|
|
||||||
print(text)
|
|
||||||
local out = hilbish.read('Hit enter to continue ')
|
|
||||||
ansikit.clear()
|
|
||||||
ansikit.cursorTo(0, 0)
|
|
||||||
if not out then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
print 'Hope you enjoy using Hilbish!'
|
|
||||||
end)
|
|
||||||
|
|
||||||
do
|
|
||||||
local virt_G = { }
|
|
||||||
|
|
||||||
setmetatable(_G, {
|
|
||||||
__index = function (_, key)
|
|
||||||
local got_virt = virt_G[key]
|
|
||||||
if got_virt ~= nil then
|
|
||||||
return got_virt
|
|
||||||
end
|
|
||||||
|
|
||||||
virt_G[key] = os.getenv(key)
|
|
||||||
return virt_G[key]
|
|
||||||
end,
|
|
||||||
|
|
||||||
__newindex = function (_, key, value)
|
|
||||||
if type(value) == 'string' then
|
|
||||||
os.setenv(key, value)
|
|
||||||
virt_G[key] = value
|
|
||||||
else
|
|
||||||
if type(virt_G[key]) == 'string' then
|
|
||||||
os.setenv(key, '')
|
|
||||||
end
|
|
||||||
virt_G[key] = value
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
commander.register('cdr', function(args)
|
|
||||||
if not args[1] then
|
|
||||||
print(lunacolors.format [[
|
|
||||||
cdr: change directory to one which has been recently visied
|
|
||||||
|
|
||||||
usage: cdr <index>
|
|
||||||
|
|
||||||
to get a list of recent directories, use {green}{underline}cdr list{reset}]])
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if args[1] == 'list' then
|
|
||||||
if #recentDirs == 0 then
|
|
||||||
print 'No directories have been visited.'
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
print(table.concat(recentDirs, '\n'))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local index = tonumber(args[1])
|
|
||||||
if not index then
|
|
||||||
print(string.format('received %s as index, which isn\'t a number', index))
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if not recentDirs[index] then
|
|
||||||
print(string.format('no recent directory found at index %s', index))
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
fs.cd(recentDirs[index])
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Hook handles
|
|
||||||
bait.catch('command.not-found', function(cmd)
|
|
||||||
print(string.format('hilbish: %s not found', cmd))
|
|
||||||
end)
|
|
||||||
|
|
||||||
bait.catch('command.not-executable', function(cmd)
|
|
||||||
print(string.format('hilbish: %s: not executable', cmd))
|
|
||||||
end)
|
|
||||||
|
|
226
rl.go
226
rl.go
|
@ -34,8 +34,7 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||||
case readline.VimKeys: modeStr = "normal"
|
case readline.VimKeys: modeStr = "normal"
|
||||||
case readline.VimInsert: modeStr = "insert"
|
case readline.VimInsert: modeStr = "insert"
|
||||||
case readline.VimDelete: modeStr = "delete"
|
case readline.VimDelete: modeStr = "delete"
|
||||||
case readline.VimReplaceOnce:
|
case readline.VimReplaceOnce, readline.VimReplaceMany: modeStr = "replace"
|
||||||
case readline.VimReplaceMany: modeStr = "replace"
|
|
||||||
}
|
}
|
||||||
setVimMode(modeStr)
|
setVimMode(modeStr)
|
||||||
}
|
}
|
||||||
|
@ -85,168 +84,91 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||||
return highlighted
|
return highlighted
|
||||||
}
|
}
|
||||||
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)
|
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 2, false)
|
||||||
|
compHandle := hshMod.Get(rt.StringValue("completion")).AsTable().Get(rt.StringValue("handler"))
|
||||||
|
err := rt.Call(l.MainThread(), compHandle, []rt.Value{rt.StringValue(string(line)),
|
||||||
|
rt.IntValue(int64(pos))}, term)
|
||||||
|
|
||||||
var compGroup []*readline.CompletionGroup
|
var compGroups []*readline.CompletionGroup
|
||||||
|
if err != nil {
|
||||||
ctx = strings.TrimLeft(ctx, " ")
|
return "", compGroups
|
||||||
if len(ctx) == 0 {
|
|
||||||
return "", compGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.Split(ctx, " ")
|
luaCompGroups := term.Get(0)
|
||||||
if len(fields) == 0 {
|
luaPrefix := term.Get(1)
|
||||||
return "", compGroup
|
|
||||||
|
if luaCompGroups.Type() != rt.TableType {
|
||||||
|
return "", compGroups
|
||||||
}
|
}
|
||||||
query := fields[len(fields) - 1]
|
|
||||||
|
|
||||||
ctx = aliases.Resolve(ctx)
|
groups := luaCompGroups.AsTable()
|
||||||
|
// prefix is optional
|
||||||
|
pfx, _ := luaPrefix.TryString()
|
||||||
|
|
||||||
if len(fields) == 1 {
|
util.ForEach(groups, func(key rt.Value, val rt.Value) {
|
||||||
completions, prefix := binaryComplete(query, ctx, fields)
|
if key.Type() != rt.IntType || val.Type() != rt.TableType {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
valTbl := val.AsTable()
|
||||||
TrimSlash: false,
|
luaCompType := valTbl.Get(rt.StringValue("type"))
|
||||||
NoSpace: true,
|
luaCompItems := valTbl.Get(rt.StringValue("items"))
|
||||||
Suggestions: completions,
|
|
||||||
|
if luaCompType.Type() != rt.StringType || luaCompItems.Type() != rt.TableType {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items := []string{}
|
||||||
|
itemDescriptions := make(map[string]string)
|
||||||
|
|
||||||
|
util.ForEach(luaCompItems.AsTable(), func(lkey rt.Value, lval rt.Value) {
|
||||||
|
if keytyp := lkey.Type(); keytyp == rt.StringType {
|
||||||
|
// ['--flag'] = {'description', '--flag-alias'}
|
||||||
|
itemName, ok := lkey.TryString()
|
||||||
|
vlTbl, okk := lval.TryTable()
|
||||||
|
if !ok && !okk {
|
||||||
|
// TODO: error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, itemName)
|
||||||
|
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
||||||
|
if !ok {
|
||||||
|
// TODO: error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
itemDescriptions[itemName] = itemDescription
|
||||||
|
} else if keytyp == rt.IntType {
|
||||||
|
vlStr, ok := lval.TryString()
|
||||||
|
if !ok {
|
||||||
|
// TODO: error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
items = append(items, vlStr)
|
||||||
|
} else {
|
||||||
|
// TODO: error
|
||||||
|
return
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return prefix, compGroup
|
var dispType readline.TabDisplayType
|
||||||
} else {
|
switch luaCompType.AsString() {
|
||||||
if completecb, ok := luaCompletions["command." + fields[0]]; ok {
|
case "grid": dispType = readline.TabDisplayGrid
|
||||||
luaFields := rt.NewTable()
|
case "list": dispType = readline.TabDisplayList
|
||||||
for i, f := range fields {
|
// need special cases, will implement later
|
||||||
luaFields.Set(rt.IntValue(int64(i + 1)), rt.StringValue(f))
|
//case "map": dispType = readline.TabDisplayMap
|
||||||
}
|
|
||||||
|
|
||||||
// we must keep the holy 80 cols
|
|
||||||
luacompleteTable, err := rt.Call1(l.MainThread(),
|
|
||||||
rt.FunctionValue(completecb), rt.StringValue(query),
|
|
||||||
rt.StringValue(ctx), rt.TableValue(luaFields))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", compGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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.TryTable(); ok {
|
|
||||||
nextVal := rt.NilValue
|
|
||||||
for {
|
|
||||||
next, val, ok := cmpTbl.Next(nextVal)
|
|
||||||
if next == rt.NilValue {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nextVal = next
|
|
||||||
|
|
||||||
_, ok = next.TryInt()
|
|
||||||
valTbl, okk := val.TryTable()
|
|
||||||
if !ok || !okk {
|
|
||||||
// TODO: error?
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
luaCompType := valTbl.Get(rt.StringValue("type"))
|
|
||||||
luaCompItems := valTbl.Get(rt.StringValue("items"))
|
|
||||||
|
|
||||||
compType, ok := luaCompType.TryString()
|
|
||||||
compItems, okk := luaCompItems.TryTable()
|
|
||||||
if !ok || !okk {
|
|
||||||
// TODO: error
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
var items []string
|
|
||||||
itemDescriptions := make(map[string]string)
|
|
||||||
nxVal := rt.NilValue
|
|
||||||
for {
|
|
||||||
nx, vl, _ := compItems.Next(nxVal)
|
|
||||||
if nx == rt.NilValue {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nxVal = nx
|
|
||||||
|
|
||||||
if tstr := nx.Type(); tstr == rt.StringType {
|
|
||||||
// ['--flag'] = {'description', '--flag-alias'}
|
|
||||||
nxStr, ok := nx.TryString()
|
|
||||||
vlTbl, okk := vl.TryTable()
|
|
||||||
if !ok || !okk {
|
|
||||||
// TODO: error
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
items = append(items, nxStr)
|
|
||||||
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
|
||||||
if !ok {
|
|
||||||
// TODO: error
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
itemDescriptions[nxStr] = itemDescription
|
|
||||||
} else if tstr == rt.IntType {
|
|
||||||
vlStr, okk := vl.TryString()
|
|
||||||
if !okk {
|
|
||||||
// TODO: error
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
items = append(items, vlStr)
|
|
||||||
} else {
|
|
||||||
// TODO: error
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var dispType readline.TabDisplayType
|
|
||||||
switch compType {
|
|
||||||
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(compGroup) == 0 {
|
compGroups = append(compGroups, &readline.CompletionGroup{
|
||||||
completions, p := fileComplete(query, ctx, fields)
|
DisplayType: dispType,
|
||||||
fcompGroup := []*readline.CompletionGroup{{
|
Descriptions: itemDescriptions,
|
||||||
TrimSlash: false,
|
Suggestions: items,
|
||||||
NoSpace: true,
|
TrimSlash: false,
|
||||||
Suggestions: completions,
|
NoSpace: true,
|
||||||
}}
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return p, fcompGroup
|
return pfx, compGroups
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", compGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &lineReader{
|
return &lineReader{
|
||||||
|
|
56
util/util.go
56
util/util.go
|
@ -3,7 +3,9 @@ package util
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"os"
|
"os"
|
||||||
|
"os/user"
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
)
|
)
|
||||||
|
@ -25,19 +27,37 @@ func Document(module *rt.Table, doc string) {
|
||||||
// It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
// It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
||||||
func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value, doc string) {
|
func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value, doc string) {
|
||||||
// TODO: ^ rtm isnt needed, i should remove it
|
// TODO: ^ rtm isnt needed, i should remove it
|
||||||
|
SetFieldDoc(module, field, doc)
|
||||||
|
module.Set(rt.StringValue(field), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFieldDoc sets the __docProp metatable for a field on the
|
||||||
|
// module.
|
||||||
|
func SetFieldDoc(module *rt.Table, field, doc string) {
|
||||||
mt := module.Metatable()
|
mt := module.Metatable()
|
||||||
|
|
||||||
if mt == nil {
|
if mt == nil {
|
||||||
mt = rt.NewTable()
|
mt = rt.NewTable()
|
||||||
docProp := rt.NewTable()
|
|
||||||
mt.Set(rt.StringValue("__docProp"), rt.TableValue(docProp))
|
|
||||||
|
|
||||||
module.SetMetatable(mt)
|
module.SetMetatable(mt)
|
||||||
}
|
}
|
||||||
|
|
||||||
docProp := mt.Get(rt.StringValue("__docProp"))
|
docProp := mt.Get(rt.StringValue("__docProp"))
|
||||||
|
if docProp == rt.NilValue {
|
||||||
|
docPropTbl := rt.NewTable()
|
||||||
|
mt.Set(rt.StringValue("__docProp"), rt.TableValue(docPropTbl))
|
||||||
|
docProp = mt.Get(rt.StringValue("__docProp"))
|
||||||
|
}
|
||||||
|
|
||||||
docProp.AsTable().Set(rt.StringValue(field), rt.StringValue(doc))
|
docProp.AsTable().Set(rt.StringValue(field), rt.StringValue(doc))
|
||||||
module.Set(rt.StringValue(field), value)
|
}
|
||||||
|
|
||||||
|
// SetFieldProtected sets a field in a protected table. A protected table
|
||||||
|
// is one which has a metatable proxy to ensure no overrides happen to it.
|
||||||
|
// It sets the field in the table and sets the __docProp metatable on the
|
||||||
|
// user facing table.
|
||||||
|
func SetFieldProtected(module, realModule *rt.Table, field string, value rt.Value, doc string) {
|
||||||
|
SetFieldDoc(module, field, doc)
|
||||||
|
realModule.Set(rt.StringValue(field), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoString runs the code string in the Lua runtime.
|
// DoString runs the code string in the Lua runtime.
|
||||||
|
@ -118,3 +138,31 @@ func HandleStrCallback(t *rt.Thread, c *rt.GoCont) (string, *rt.Closure, error)
|
||||||
|
|
||||||
return name, cb, err
|
return name, cb, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForEach loops through a Lua table.
|
||||||
|
func ForEach(tbl *rt.Table, cb func(key rt.Value, val rt.Value)) {
|
||||||
|
nextVal := rt.NilValue
|
||||||
|
for {
|
||||||
|
key, val, _ := tbl.Next(nextVal)
|
||||||
|
if key == rt.NilValue {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nextVal = key
|
||||||
|
|
||||||
|
cb(key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandHome expands ~ (tilde) in the path, changing it to the user home
|
||||||
|
// directory.
|
||||||
|
func ExpandHome(path string) string {
|
||||||
|
if strings.HasPrefix(path, "~") {
|
||||||
|
curuser, _ := user.Current()
|
||||||
|
homedir := curuser.HomeDir
|
||||||
|
|
||||||
|
return strings.Replace(path, "~", homedir, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ var (
|
||||||
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
|
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
|
||||||
.. hilbish.userDir.config .. '/hilbish/?.lua'`
|
.. hilbish.userDir.config .. '/hilbish/?.lua'`
|
||||||
dataDir = "/usr/local/share/hilbish"
|
dataDir = "/usr/local/share/hilbish"
|
||||||
preloadPath = dataDir + "/prelude/init.lua"
|
preloadPath = dataDir + "/nature/init.lua"
|
||||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
||||||
defaultConfDir = getenv("XDG_CONFIG_HOME", "~/.config")
|
defaultConfDir = getenv("XDG_CONFIG_HOME", "~/.config")
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,7 +15,7 @@ var (
|
||||||
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
|
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
|
||||||
.. hilbish.userDir.config .. '/hilbish/?.lua'`
|
.. hilbish.userDir.config .. '/hilbish/?.lua'`
|
||||||
dataDir = "/usr/share/hilbish"
|
dataDir = "/usr/share/hilbish"
|
||||||
preloadPath = dataDir + "/prelude/init.lua"
|
preloadPath = dataDir + "/nature/init.lua"
|
||||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
||||||
defaultConfDir = ""
|
defaultConfDir = ""
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,7 +9,7 @@ var (
|
||||||
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\?.lua;'
|
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\?.lua;'
|
||||||
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?.lua;'`
|
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?.lua;'`
|
||||||
dataDir = "~\\Appdata\\Roaming\\Hilbish" // ~ and \ gonna cry?
|
dataDir = "~\\Appdata\\Roaming\\Hilbish" // ~ and \ gonna cry?
|
||||||
preloadPath = dataDir + "\\prelude\\init.lua"
|
preloadPath = dataDir + "\\nature\\init.lua"
|
||||||
sampleConfPath = dataDir + "\\hilbishrc.lua" // Path to default/sample config
|
sampleConfPath = dataDir + "\\hilbishrc.lua" // Path to default/sample config
|
||||||
defaultConfDir = ""
|
defaultConfDir = ""
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue