Compare commits

...

46 Commits

Author SHA1 Message Date
TorchedSammy 0e076f391a
fix: bring in latest changes 2022-05-07 08:11:32 -04:00
TorchedSammy 0a01ed862c
feat: print error if nature is missing 2022-05-06 19:21:32 -04:00
TorchedSammy 200babf831
chore: merge 2022-05-06 19:06:57 -04:00
TorchedSammy d6bc8b51de
ci: fix missing files on either oses 2022-05-06 19:06:06 -04:00
sammyette 86700c0a7b
ci: add codeql code scanning 2022-05-06 12:20:41 -04:00
TorchedSammy 30b07bc98b
fix: check if path has tilde prefix when trying to expand home 2022-05-01 07:20:40 -04:00
TorchedSammy db437905e0
fix: implicitly expand tilde on args to all fs functions 2022-05-01 00:49:59 -04:00
TorchedSammy c890b86e08
feat: add hilbish.opts and autocd opt
this adds `hilbish.opts`, a table to set
simple options akin to shopt or setopt on
other shells. this commit specifically also
includes the autocd opt, which functions the
way you expect it to

to set opts, simply do `hilbish.opts.name = val`,
where `name` is the opt you want to set and `val`
being the opt setting.

ie: `hilbish.opts.autocd = true` to turn on autocd
2022-04-30 21:22:37 -04:00
TorchedSammy a8406657f9
fix: remove nature hook handles
since it was only for command exec errors,
and theyre handled runner side now, we dont
have to handle it via hooks anymore
2022-04-30 21:05:21 -04:00
TorchedSammy 4178b78341
fix: return other errors from sh runner
makes it so that the sh runner function will return
command not found and not executable errors,
which makes them able to be handled via lua properly
2022-04-30 21:02:36 -04:00
TorchedSammy c4eb3ad368
chore: change lua init errors to make more sense 2022-04-30 20:31:37 -04:00
TorchedSammy 0642ddda36
fix: push home dir to recent dirs with correct function 2022-04-30 20:28:47 -04:00
TorchedSammy b210378380
ci: remove .exe from windows artifact names 2022-04-30 12:08:54 -04:00
TorchedSammy 03cb9c06f3
ci: fix release builds; make them include lua libs and nature lib 2022-04-30 12:08:39 -04:00
TorchedSammy c929c7602f
ci: checkout submodules 2022-04-30 12:00:41 -04:00
TorchedSammy e678ef66d8
docS: update changelogs for master
800th commit!
2022-04-30 12:00:01 -04:00
TorchedSammy d053b58204
ci: indent list of files 2022-04-30 10:50:06 -04:00
TorchedSammy eb3123405a
ci: fix list of files for artifact build 2022-04-30 10:48:32 -04:00
TorchedSammy f12f88ed00
ci: include all required files in build artifacts 2022-04-30 10:45:55 -04:00
TorchedSammy 3bad452f95
fix: load completions by default 2022-04-24 09:00:06 -04:00
TorchedSammy 2790982ad1
fix: no command completions if query/line is an alias
basically, i have a `c` alias which is `git commit`,
this would resolve to `git commit` literally to try
and complete `commit`, which wouldnt match.
this fixes that, and instead itll suggest commands
that start with `c`. if there is a space after and
completion is requested, itll use the alias properly
2022-04-24 00:06:19 -04:00
TorchedSammy 4e5f8b5c80
docs: add docs for nature module 2022-04-24 00:00:18 -04:00
TorchedSammy 4a4c4d8c74
refactor: rewrite completion handler in lua 2022-04-23 23:59:03 -04:00
TorchedSammy bcf02a6b0e
fix: make custom hist dir match default by adding hilbish to the path 2022-04-23 23:55:04 -04:00
TorchedSammy 984b8a4308
feat: add hilbish.aliases.resolve to resolve an alias 2022-04-23 12:28:28 -04:00
TorchedSammy 3db6334445
docs: [ci] generate new docs 2022-04-23 00:03:50 -04:00
TorchedSammy 9e596dc271
refactor: (re)organize and change prelude path and structure
prelude is no longer. it is now nature.
organized the single file prelude into multiple
source files and renamed it to nature. this is coming
after thought that it can turn into a general hilbish
lua core, with user facing modules as well.

this introduces the `nature.dirs` module, to interact
and get recently changed to directories and last/old
cwd.
2022-04-23 00:03:50 -04:00
TorchedSammy f003249632
docs: update doc reference for vim mode actions 2022-04-23 00:03:49 -04:00
TorchedSammy 1714aeac36
feat: add fs.abs 2022-04-23 00:03:49 -04:00
TorchedSammy bca89197ad
fix: add io flush after doc help message 2022-04-23 00:03:49 -04:00
TorchedSammy 27ac60b856
fix: remove unused var 2022-04-23 00:03:48 -04:00
TorchedSammy dbb27f7739
docs: [ci] generate new docs 2022-04-23 00:03:48 -04:00
TorchedSammy 03a57fce5b
docs: add more documentation for completions 2022-04-23 00:03:48 -04:00
TorchedSammy abfbeb5f84
feat: allow overwrite of completion handler (closes #122)
this also makes the completion functions `bins`
and `files` also return the prefix to pass
to the completion handler.

this is an overhaul to the completion system,
which gets the completion handler from lua
instead of being made to only have lua provided
*command* completions.

it does not have any performance deficit, even
though it calls in to golua for completions.
2022-04-23 00:03:47 -04:00
TorchedSammy 3194add3dc
fix: restore doc related metafields on hilbish table 2022-04-23 00:03:47 -04:00
TorchedSammy 1ba88fea88
fix: correct custom runner mode handling with recent changes 2022-04-23 00:03:47 -04:00
TorchedSammy 1274811739
docs: [ci] generate new docs 2022-04-23 00:03:46 -04:00
TorchedSammy 0af36db6ff
fix!: change the way highlighter and hinter are set
with the change of blocking changes to the
hilbish table, i took an opportunity
to make the highlighter and hinter callbacks
set in a more natural way. instead of being
a function which takes a callback, you set
the function itself.
2022-04-23 00:03:38 -04:00
TorchedSammy 1a4008fcfb docs: [ci] generate new docs 2022-04-22 02:17:00 +00:00
TorchedSammy 57d7527356
fix: make hilbish.which work with aliases and commanders 2022-04-21 22:16:04 -04:00
TorchedSammy 3d525aa7da
fix: dont allow overrides on hilbish table 2022-04-21 20:39:38 -04:00
TorchedSammy 37e1b12b81
feat: add hilbish.completion.call to call a completer 2022-04-21 20:39:06 -04:00
TorchedSammy abfd4e5196
fix(util): SetField on a table with a metatable causing panic 2022-04-21 20:33:32 -04:00
TorchedSammy bd35e3b871
refactor: use foreach function to loop over lua tables 2022-04-21 14:01:59 -04:00
TorchedSammy 86aa40af64 docs: [ci] generate new docs 2022-04-21 16:19:31 +00:00
TorchedSammy a89d3e59d6
docs: fix typo in hilbish.inputMode doc (closes #147) 2022-04-21 12:19:11 -04:00
36 changed files with 1160 additions and 657 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

13
docs/nature.txt 100644
View File

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

View File

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

View File

@ -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
View File

@ -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,12 +132,20 @@ 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)
}
}
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) cmdFinish(exitCode, input, priv)
} }
}
func handleLua(cmdString string) (string, uint8, error) { func handleLua(cmdString string) (string, uint8, error) {
// First try to load input, essentially compiling to bytecode // First try to load input, essentially compiling to bytecode
@ -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

View File

@ -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
View File

@ -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)
} }
} }
} }

View File

@ -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{}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
local bait = require 'bait'
local commander = require 'commander'
commander.register('exit', function()
bait.throw('hilbish.exit')
os.exit(0)
end)

View File

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

View File

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

View File

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

75
nature/dirs.lua 100644
View File

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

45
nature/init.lua 100644
View File

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

View File

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

View File

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

View File

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

166
rl.go
View File

@ -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"))
var compGroup []*readline.CompletionGroup err := rt.Call(l.MainThread(), compHandle, []rt.Value{rt.StringValue(string(line)),
rt.IntValue(int64(pos))}, term)
ctx = strings.TrimLeft(ctx, " ")
if len(ctx) == 0 {
return "", compGroup
}
fields := strings.Split(ctx, " ")
if len(fields) == 0 {
return "", compGroup
}
query := fields[len(fields) - 1]
ctx = aliases.Resolve(ctx)
if len(fields) == 1 {
completions, prefix := binaryComplete(query, ctx, fields)
compGroup = append(compGroup, &readline.CompletionGroup{
TrimSlash: false,
NoSpace: true,
Suggestions: completions,
})
return prefix, compGroup
} else {
if completecb, ok := luaCompletions["command." + fields[0]]; ok {
luaFields := rt.NewTable()
for i, f := range fields {
luaFields.Set(rt.IntValue(int64(i + 1)), rt.StringValue(f))
}
// 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))
var compGroups []*readline.CompletionGroup
if err != nil { if err != nil {
return "", compGroup return "", compGroups
} }
/* luaCompGroups := term.Get(0)
as an example with git, luaPrefix := term.Get(1)
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() if luaCompGroups.Type() != rt.TableType {
valTbl, okk := val.TryTable() return "", compGroups
if !ok || !okk {
// TODO: error?
break
} }
groups := luaCompGroups.AsTable()
// prefix is optional
pfx, _ := luaPrefix.TryString()
util.ForEach(groups, func(key rt.Value, val rt.Value) {
if key.Type() != rt.IntType || val.Type() != rt.TableType {
return
}
valTbl := val.AsTable()
luaCompType := valTbl.Get(rt.StringValue("type")) luaCompType := valTbl.Get(rt.StringValue("type"))
luaCompItems := valTbl.Get(rt.StringValue("items")) luaCompItems := valTbl.Get(rt.StringValue("items"))
compType, ok := luaCompType.TryString() if luaCompType.Type() != rt.StringType || luaCompItems.Type() != rt.TableType {
compItems, okk := luaCompItems.TryTable() return
if !ok || !okk {
// TODO: error
break
} }
var items []string items := []string{}
itemDescriptions := make(map[string]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 { util.ForEach(luaCompItems.AsTable(), func(lkey rt.Value, lval rt.Value) {
if keytyp := lkey.Type(); keytyp == rt.StringType {
// ['--flag'] = {'description', '--flag-alias'} // ['--flag'] = {'description', '--flag-alias'}
nxStr, ok := nx.TryString() itemName, ok := lkey.TryString()
vlTbl, okk := vl.TryTable() vlTbl, okk := lval.TryTable()
if !ok || !okk { if !ok && !okk {
// TODO: error // TODO: error
continue return
} }
items = append(items, nxStr)
items = append(items, itemName)
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString() itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
if !ok { if !ok {
// TODO: error // TODO: error
continue return
} }
itemDescriptions[nxStr] = itemDescription itemDescriptions[itemName] = itemDescription
} else if tstr == rt.IntType { } else if keytyp == rt.IntType {
vlStr, okk := vl.TryString() vlStr, ok := lval.TryString()
if !okk { if !ok {
// TODO: error // TODO: error
continue return
} }
items = append(items, vlStr) items = append(items, vlStr)
} else { } else {
// TODO: error // TODO: error
continue return
}
} }
})
var dispType readline.TabDisplayType var dispType readline.TabDisplayType
switch compType { switch luaCompType.AsString() {
case "grid": dispType = readline.TabDisplayGrid case "grid": dispType = readline.TabDisplayGrid
case "list": dispType = readline.TabDisplayList case "list": dispType = readline.TabDisplayList
// need special cases, will implement later // need special cases, will implement later
//case "map": dispType = readline.TabDisplayMap //case "map": dispType = readline.TabDisplayMap
} }
compGroup = append(compGroup, &readline.CompletionGroup{ compGroups = append(compGroups, &readline.CompletionGroup{
DisplayType: dispType, DisplayType: dispType,
Descriptions: itemDescriptions, Descriptions: itemDescriptions,
Suggestions: items, Suggestions: items,
TrimSlash: false, TrimSlash: false,
NoSpace: true, NoSpace: true,
}) })
} })
}
}
if len(compGroup) == 0 { return pfx, compGroups
completions, p := fileComplete(query, ctx, fields)
fcompGroup := []*readline.CompletionGroup{{
TrimSlash: false,
NoSpace: true,
Suggestions: completions,
}}
return p, fcompGroup
}
}
return "", compGroup
} }
return &lineReader{ return &lineReader{

View File

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

View File

@ -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")
) )

View File

@ -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 = ""
) )

View File

@ -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 = ""
) )