mirror of https://github.com/Hilbis/Hilbish
fix: bring in latest changes
commit
0e076f391a
|
@ -19,7 +19,9 @@ jobs:
|
|||
goos: windows
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
|
@ -29,10 +31,28 @@ jobs:
|
|||
- uses: actions/upload-artifact@v2
|
||||
if: matrix.goos == 'windows'
|
||||
with:
|
||||
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}.exe
|
||||
path: hilbish.exe
|
||||
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: |
|
||||
hilbish.exe
|
||||
LICENSE
|
||||
README.md
|
||||
CHANGELOG.md
|
||||
.hilbishrc.lua
|
||||
nature
|
||||
libs
|
||||
docs
|
||||
emmyLuaDocs
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: matrix.goos != 'windows'
|
||||
with:
|
||||
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
|
||||
goos: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
submodules: true
|
||||
- uses: wangyoucao577/go-release-action@v1.25
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
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
|
||||
|
||||
## 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
|
||||
### Added
|
||||
- Job Management additions
|
||||
|
|
2
Makefile
2
Makefile
|
@ -15,7 +15,7 @@ build:
|
|||
install:
|
||||
install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v hilbish "$(DESTDIR)$(BINDIR)/hilbish"
|
||||
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
|
||||
|
||||
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},
|
||||
"list": util.LuaExport{a.luaList, 0, false},
|
||||
"del": util.LuaExport{a.luaDelete, 1, false},
|
||||
"resolve": util.LuaExport{a.luaResolve, 1, false},
|
||||
}
|
||||
|
||||
mod := rt.NewTable()
|
||||
|
@ -101,3 +102,16 @@ func (a *aliasHandler) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
|
||||
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()) {
|
||||
fakeMod := rt.NewTable()
|
||||
modmt := 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)
|
||||
hshMod = mod
|
||||
|
||||
|
@ -67,17 +107,17 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
|||
The nice lil shell for {blue}Lua{reset} fanatics!
|
||||
Check out the {blue}{bold}guide{reset} command to get started.
|
||||
`
|
||||
util.SetField(rtm, mod, "ver", rt.StringValue(version), "Hilbish version")
|
||||
util.SetField(rtm, mod, "user", rt.StringValue(username), "Username of user")
|
||||
util.SetField(rtm, 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.SetField(rtm, 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.SetField(rtm, 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.SetField(rtm, 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.Document(mod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
|
||||
util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(version), "Hilbish version")
|
||||
util.SetFieldProtected(fakeMod, mod, "user", rt.StringValue(username), "Username of user")
|
||||
util.SetFieldProtected(fakeMod, mod, "host", rt.StringValue(host), "Host name of the machine")
|
||||
util.SetFieldProtected(fakeMod, mod, "home", rt.StringValue(curuser.HomeDir), "Home directory of the user")
|
||||
util.SetFieldProtected(fakeMod, mod, "dataDir", rt.StringValue(dataDir), "Directory for Hilbish's data files")
|
||||
util.SetFieldProtected(fakeMod, mod, "interactive", rt.BoolValue(interactive), "If this is an interactive shell")
|
||||
util.SetFieldProtected(fakeMod, mod, "login", rt.BoolValue(login), "Whether this is a login shell")
|
||||
util.SetFieldProtected(fakeMod, mod, "greeting", rt.StringValue(greeting), "Hilbish's welcome message for interactive shells. It has Lunacolors formatting.")
|
||||
util.SetFieldProtected(fakeMod, mod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
||||
util.SetFieldProtected(fakeMod, mod, "exitCode", rt.IntValue(0), "Exit code of last exected command")
|
||||
util.Document(fakeMod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
|
||||
|
||||
// hilbish.userDir table
|
||||
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.")
|
||||
|
||||
// hilbish.completion table
|
||||
hshcomp := rt.NewTable()
|
||||
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")
|
||||
|
||||
hshcomp := completionLoader(rtm)
|
||||
util.Document(hshcomp, "Completions interface for Hilbish.")
|
||||
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.")
|
||||
mod.Set(rt.StringValue("timers"), rt.TableValue(timerModule))
|
||||
|
||||
return rt.TableValue(mod), nil
|
||||
return rt.TableValue(fakeMod), nil
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
|
@ -148,75 +180,6 @@ func getenv(key, fallback string) string {
|
|||
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) {
|
||||
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)
|
||||
|
@ -395,21 +358,11 @@ func hlappendPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
|
||||
// check if dir is a table or a string
|
||||
if arg.Type() == rt.TableType {
|
||||
nextVal := rt.NilValue
|
||||
for {
|
||||
next, val, ok := arg.AsTable().Next(nextVal)
|
||||
if next == rt.NilValue {
|
||||
break
|
||||
}
|
||||
nextVal = next
|
||||
|
||||
valStr, ok := val.TryString()
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
appendPath(valStr)
|
||||
util.ForEach(arg.AsTable(), func(k rt.Value, v rt.Value) {
|
||||
if v.Type() == rt.StringType {
|
||||
appendPath(v.AsString())
|
||||
}
|
||||
})
|
||||
} else if arg.Type() == rt.StringType {
|
||||
appendPath(arg.AsString())
|
||||
} 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>`,
|
||||
// replacing <cmd> with the name of the command (for example `command.git`).
|
||||
// `cb` must be a function that returns a table of "completion groups."
|
||||
// A completion group is a table with the keys `items` and `type`.
|
||||
// `items` being a table of items and `type` being the display type of
|
||||
// `grid` (the normal file completion display) or `list` (with a description)
|
||||
// Check `doc completions` for more information.
|
||||
// --- @param scope string
|
||||
// --- @param cb function
|
||||
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
|
||||
}
|
||||
|
||||
// which(binName)
|
||||
// Searches for an executable called `binName` in the directories of $PATH
|
||||
// which(name)
|
||||
// Checks if `name` is a valid command
|
||||
// --- @param binName string
|
||||
func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
binName, err := c.StringArg(0)
|
||||
name, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
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 {
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
@ -601,7 +561,7 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
}
|
||||
|
||||
// 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
|
||||
func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
|
@ -642,7 +602,6 @@ func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
switch mode.Type() {
|
||||
case rt.StringType:
|
||||
switch mode.AsString() {
|
||||
// no fallthrough doesnt work so eh
|
||||
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())
|
||||
}
|
||||
|
@ -653,40 +612,24 @@ func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// hinter(cb)
|
||||
// Sets the hinter function. This will be called on every key insert to determine
|
||||
// what text to use as an inline hint. The callback is passed 2 arguments:
|
||||
// the current line and the position. It is expected to return a string
|
||||
// which will be used for the hint.
|
||||
// --- @param cb function
|
||||
// hinter(line, pos)
|
||||
// The command line hint handler. It gets called on every key insert to
|
||||
// determine what text to use as an inline hint. It is passed the current
|
||||
// line and cursor position. It is expected to return a string which is used
|
||||
// as the text for the hint. This is by default a shim. To set hints,
|
||||
// override this function with your custom handler.
|
||||
// --- @param line string
|
||||
// --- @param pos int
|
||||
func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hinterCb, err := c.ClosureArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hinter = hinterCb
|
||||
|
||||
return c.Next(), err
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// highlighter(cb)
|
||||
// Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
||||
// reality could set the input of the prompt to display anything. The callback
|
||||
// is passed the current line as typed and is expected to return a line that will
|
||||
// be used to display in the line.
|
||||
// --- @param cb function
|
||||
// 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 is passed the current line and is expected to return a line that
|
||||
// will be used as the input display.
|
||||
// --- @param line string
|
||||
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
highlighterCb, err := c.ClosureArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
highlighter = highlighterCb
|
||||
|
||||
return c.Next(), err
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
|
125
complete.go
125
complete.go
|
@ -1,9 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
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)
|
||||
if len(fileCompletions) != 0 {
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
|
@ -66,7 +71,7 @@ func matchPath(query string) ([]string, string) {
|
|||
var entries []string
|
||||
var baseName string
|
||||
|
||||
path, _ := filepath.Abs(expandHome(filepath.Dir(query)))
|
||||
path, _ := filepath.Abs(util.ExpandHome(filepath.Dir(query)))
|
||||
if string(query) == "" {
|
||||
// filepath base below would give us "."
|
||||
// which would cause a match of only dotfiles
|
||||
|
@ -112,3 +117,119 @@ func escapeFilename(fname string) string {
|
|||
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`
|
||||
|
||||
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>`,
|
||||
replacing <cmd> with the name of the command (for example `command.git`).
|
||||
`cb` must be a function that returns a table of "completion groups."
|
||||
A completion group is a table with the keys `items` and `type`.
|
||||
`items` being a table of items and `type` being the display type of
|
||||
`grid` (the normal file completion display) or `list` (with a description)
|
||||
Check `doc completions` for more information.
|
||||
|
||||
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
|
||||
|
||||
highlighter(cb) > Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
||||
reality could set the input of the prompt to display anything. The callback
|
||||
is passed the current line as typed and is expected to return a line that will
|
||||
be used to display in the line.
|
||||
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 is passed the current line and is expected to return a line that
|
||||
will be used as the input display.
|
||||
|
||||
hinter(cb) > Sets the hinter function. This will be called on every key insert to determine
|
||||
what text to use as an inline hint. The callback is passed 2 arguments:
|
||||
the current line and the position. It is expected to return a string
|
||||
which will be used for the hint.
|
||||
hinter(line, pos) > The command line hint handler. It gets called on every key insert to
|
||||
determine what text to use as an inline hint. It is passed the current
|
||||
line and cursor position. It is expected to return a string which is used
|
||||
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.
|
||||
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
|
||||
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`).
|
||||
|
||||
+ `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 = {}
|
||||
|
||||
--- Gives an absolute version of `path`.
|
||||
--- @param path string
|
||||
function fs.abs(path) end
|
||||
|
||||
--- Changes directory to `dir`
|
||||
--- @param dir string
|
||||
function fs.cd(dir) end
|
||||
|
|
|
@ -15,9 +15,7 @@ function hilbish.appendPath(dir) end
|
|||
--- A `scope` is currently only expected to be `command.<cmd>`,
|
||||
--- replacing <cmd> with the name of the command (for example `command.git`).
|
||||
--- `cb` must be a function that returns a table of "completion groups."
|
||||
--- A completion group is a table with the keys `items` and `type`.
|
||||
--- `items` being a table of items and `type` being the display type of
|
||||
--- `grid` (the normal file completion display) or `list` (with a description)
|
||||
--- Check `doc completions` for more information.
|
||||
--- @param scope string
|
||||
--- @param cb function
|
||||
function hilbish.complete(scope, cb) end
|
||||
|
@ -33,21 +31,23 @@ function hilbish.exec(cmd) end
|
|||
--- @param fn function
|
||||
function hilbish.goro(fn) end
|
||||
|
||||
--- Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
||||
--- reality could set the input of the prompt to display anything. The callback
|
||||
--- is passed the current line as typed and is expected to return a line that will
|
||||
--- be used to display in the line.
|
||||
--- @param cb function
|
||||
function hilbish.highlighter(cb) end
|
||||
--- Line highlighter handler. This is mainly for syntax highlighting, but in
|
||||
--- reality could set the input of the prompt to *display* anything. The
|
||||
--- callback is passed the current line and is expected to return a line that
|
||||
--- will be used as the input display.
|
||||
--- @param line string
|
||||
function hilbish.highlighter(line) end
|
||||
|
||||
--- Sets the hinter function. This will be called on every key insert to determine
|
||||
--- what text to use as an inline hint. The callback is passed 2 arguments:
|
||||
--- the current line and the position. It is expected to return a string
|
||||
--- which will be used for the hint.
|
||||
--- @param cb function
|
||||
function hilbish.hinter(cb) end
|
||||
--- The command line hint handler. It gets called on every key insert to
|
||||
--- determine what text to use as an inline hint. It is passed the current
|
||||
--- line and cursor position. It is expected to return a string which is used
|
||||
--- as the text for the hint. This is by default a shim. To set hints,
|
||||
--- override this function with your custom handler.
|
||||
--- @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
|
||||
function hilbish.inputMode(mode) end
|
||||
|
||||
|
@ -103,7 +103,7 @@ function hilbish.runnerMode(mode) end
|
|||
--- @return table
|
||||
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
|
||||
function hilbish.which(binName) end
|
||||
|
||||
|
|
114
exec.go
114
exec.go
|
@ -25,8 +25,63 @@ import (
|
|||
)
|
||||
|
||||
var errNotExec = errors.New("not executable")
|
||||
var errNotFound = errors.New("not found")
|
||||
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) {
|
||||
running = true
|
||||
cmdString := aliases.Resolve(input)
|
||||
|
@ -43,10 +98,6 @@ func runInput(input string, priv bool) {
|
|||
return
|
||||
}
|
||||
input, exitCode, err = handleSh(input)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
case "hybridRev":
|
||||
_, _, err = handleSh(input)
|
||||
if err == nil {
|
||||
|
@ -54,38 +105,25 @@ func runInput(input string, priv bool) {
|
|||
return
|
||||
}
|
||||
input, exitCode, err = handleLua(cmdString)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
case "lua":
|
||||
input, exitCode, err = handleLua(cmdString)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
case "sh":
|
||||
input, exitCode, err = handleSh(input)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
}
|
||||
} else {
|
||||
// can only be a string or function so
|
||||
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 2, false)
|
||||
err := rt.Call(l.MainThread(), runnerMode, []rt.Value{rt.StringValue(cmdString)}, term)
|
||||
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false)
|
||||
err = rt.Call(l.MainThread(), runnerMode, []rt.Value{rt.StringValue(cmdString)}, term)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
cmdFinish(124, input, priv)
|
||||
return
|
||||
}
|
||||
|
||||
luaexitcode := term.Get(0)
|
||||
runErr := term.Get(1)
|
||||
luaInput := term.Get(1)
|
||||
luaInput := term.Get(0)
|
||||
luaexitcode := term.Get(1)
|
||||
runErr := term.Get(2)
|
||||
|
||||
var exitCode uint8
|
||||
if code, ok := luaexitcode.TryInt(); ok {
|
||||
exitCode = uint8(code)
|
||||
}
|
||||
|
@ -94,11 +132,19 @@ func runInput(input string, priv bool) {
|
|||
input = inp
|
||||
}
|
||||
|
||||
if runErr != rt.NilValue {
|
||||
fmt.Fprintln(os.Stderr, runErr)
|
||||
if errStr, ok := runErr.TryString(); ok {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func handleLua(cmdString string) (string, uint8, error) {
|
||||
|
@ -255,12 +301,20 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
|
|||
|
||||
err := lookpath(args[0])
|
||||
if err == errNotExec {
|
||||
hooks.Em.Emit("command.no-perm", args[0])
|
||||
hooks.Em.Emit("command.not-executable", args[0])
|
||||
return interp.NewExitStatus(126)
|
||||
return execError{
|
||||
typ: "not-executable",
|
||||
cmd: args[0],
|
||||
code: 126,
|
||||
colon: true,
|
||||
err: errNotExec,
|
||||
}
|
||||
} else if err != nil {
|
||||
hooks.Em.Emit("command.not-found", args[0])
|
||||
return interp.NewExitStatus(127)
|
||||
return execError{
|
||||
typ: "not-found",
|
||||
cmd: args[0],
|
||||
code: 127,
|
||||
err: errNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
killTimeout := 2 * time.Second
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -22,13 +23,14 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
|||
"mkdir": util.LuaExport{fmkdir, 2, false},
|
||||
"stat": util.LuaExport{fstat, 1, false},
|
||||
"readdir": util.LuaExport{freaddir, 1, false},
|
||||
"abs": util.LuaExport{fabs, 1, false},
|
||||
}
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
|
||||
util.Document(mod, `The fs module provides easy and simple access to
|
||||
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
|
||||
}
|
||||
|
@ -44,8 +46,9 @@ func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path = util.ExpandHome(strings.TrimSpace(path))
|
||||
|
||||
err = os.Chdir(strings.TrimSpace(path))
|
||||
err = os.Chdir(path)
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
dirname, err := c.StringArg(0)
|
||||
path, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -69,7 +72,7 @@ func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := strings.TrimSpace(dirname)
|
||||
path = util.ExpandHome(strings.TrimSpace(path))
|
||||
|
||||
if recursive {
|
||||
err = os.MkdirAll(path, 0744)
|
||||
|
@ -94,6 +97,7 @@ func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path = util.ExpandHome(path)
|
||||
|
||||
pathinfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
|
@ -120,6 +124,7 @@ func freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir = util.ExpandHome(dir)
|
||||
names := rt.NewTable()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
err := util.DoString(l, "package.path = package.path .. " + requirePaths)
|
||||
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")
|
||||
if err != nil {
|
||||
err = util.DoFile(l, preloadPath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Missing preload file, builtins may be missing.")
|
||||
err1 := util.DoFile(l, "nature/init.lua")
|
||||
if err1 != nil {
|
||||
err2 := util.DoFile(l, preloadPath)
|
||||
if err2 != nil {
|
||||
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")
|
||||
} else {
|
||||
// else do ~ substitution
|
||||
defaultConfDir = filepath.Join(expandHome(defaultConfDir), "hilbish")
|
||||
defaultConfDir = filepath.Join(util.ExpandHome(defaultConfDir), "hilbish")
|
||||
}
|
||||
defaultConfPath = filepath.Join(defaultConfDir, "init.lua")
|
||||
if defaultHistDir == "" {
|
||||
defaultHistDir = filepath.Join(userDataDir, "hilbish")
|
||||
} else {
|
||||
defaultHistDir = expandHome(defaultHistDir)
|
||||
defaultHistDir = filepath.Join(util.ExpandHome(defaultHistDir), "hilbish")
|
||||
}
|
||||
defaultHistPath = filepath.Join(defaultHistDir, ".hilbish-history")
|
||||
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)
|
||||
}
|
||||
|
||||
func expandHome(path string) string {
|
||||
homedir := curuser.HomeDir
|
||||
return strings.Replace(path, "~", homedir, 1)
|
||||
}
|
||||
|
||||
func removeDupes(slice []string) []string {
|
||||
all := make(map[string]bool)
|
||||
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)
|
||||
|
166
rl.go
166
rl.go
|
@ -34,8 +34,7 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||
case readline.VimKeys: modeStr = "normal"
|
||||
case readline.VimInsert: modeStr = "insert"
|
||||
case readline.VimDelete: modeStr = "delete"
|
||||
case readline.VimReplaceOnce:
|
||||
case readline.VimReplaceMany: modeStr = "replace"
|
||||
case readline.VimReplaceOnce, readline.VimReplaceMany: modeStr = "replace"
|
||||
}
|
||||
setVimMode(modeStr)
|
||||
}
|
||||
|
@ -85,168 +84,91 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||
return highlighted
|
||||
}
|
||||
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
|
||||
ctx := string(line)
|
||||
|
||||
var compGroup []*readline.CompletionGroup
|
||||
|
||||
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))
|
||||
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 compGroups []*readline.CompletionGroup
|
||||
if err != nil {
|
||||
return "", compGroup
|
||||
return "", compGroups
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
luaCompGroups := term.Get(0)
|
||||
luaPrefix := term.Get(1)
|
||||
|
||||
_, ok = next.TryInt()
|
||||
valTbl, okk := val.TryTable()
|
||||
if !ok || !okk {
|
||||
// TODO: error?
|
||||
break
|
||||
if luaCompGroups.Type() != rt.TableType {
|
||||
return "", compGroups
|
||||
}
|
||||
|
||||
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"))
|
||||
luaCompItems := valTbl.Get(rt.StringValue("items"))
|
||||
|
||||
compType, ok := luaCompType.TryString()
|
||||
compItems, okk := luaCompItems.TryTable()
|
||||
if !ok || !okk {
|
||||
// TODO: error
|
||||
break
|
||||
if luaCompType.Type() != rt.StringType || luaCompItems.Type() != rt.TableType {
|
||||
return
|
||||
}
|
||||
|
||||
var items []string
|
||||
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 {
|
||||
util.ForEach(luaCompItems.AsTable(), func(lkey rt.Value, lval rt.Value) {
|
||||
if keytyp := lkey.Type(); keytyp == rt.StringType {
|
||||
// ['--flag'] = {'description', '--flag-alias'}
|
||||
nxStr, ok := nx.TryString()
|
||||
vlTbl, okk := vl.TryTable()
|
||||
if !ok || !okk {
|
||||
itemName, ok := lkey.TryString()
|
||||
vlTbl, okk := lval.TryTable()
|
||||
if !ok && !okk {
|
||||
// TODO: error
|
||||
continue
|
||||
return
|
||||
}
|
||||
items = append(items, nxStr)
|
||||
|
||||
items = append(items, itemName)
|
||||
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
||||
if !ok {
|
||||
// TODO: error
|
||||
continue
|
||||
return
|
||||
}
|
||||
itemDescriptions[nxStr] = itemDescription
|
||||
} else if tstr == rt.IntType {
|
||||
vlStr, okk := vl.TryString()
|
||||
if !okk {
|
||||
itemDescriptions[itemName] = itemDescription
|
||||
} else if keytyp == rt.IntType {
|
||||
vlStr, ok := lval.TryString()
|
||||
if !ok {
|
||||
// TODO: error
|
||||
continue
|
||||
return
|
||||
}
|
||||
items = append(items, vlStr)
|
||||
} else {
|
||||
// TODO: error
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
var dispType readline.TabDisplayType
|
||||
switch compType {
|
||||
switch luaCompType.AsString() {
|
||||
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{
|
||||
compGroups = append(compGroups, &readline.CompletionGroup{
|
||||
DisplayType: dispType,
|
||||
Descriptions: itemDescriptions,
|
||||
Suggestions: items,
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if len(compGroup) == 0 {
|
||||
completions, p := fileComplete(query, ctx, fields)
|
||||
fcompGroup := []*readline.CompletionGroup{{
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
Suggestions: completions,
|
||||
}}
|
||||
|
||||
return p, fcompGroup
|
||||
}
|
||||
}
|
||||
return "", compGroup
|
||||
return pfx, compGroups
|
||||
}
|
||||
|
||||
return &lineReader{
|
||||
|
|
56
util/util.go
56
util/util.go
|
@ -3,7 +3,9 @@ package util
|
|||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
"os"
|
||||
"os/user"
|
||||
|
||||
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.
|
||||
func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value, doc string) {
|
||||
// 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()
|
||||
|
||||
if mt == nil {
|
||||
mt = rt.NewTable()
|
||||
docProp := rt.NewTable()
|
||||
mt.Set(rt.StringValue("__docProp"), rt.TableValue(docProp))
|
||||
|
||||
module.SetMetatable(mt)
|
||||
}
|
||||
|
||||
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))
|
||||
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.
|
||||
|
@ -118,3 +138,31 @@ func HandleStrCallback(t *rt.Thread, c *rt.GoCont) (string, *rt.Closure, error)
|
|||
|
||||
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'`
|
||||
dataDir = "/usr/local/share/hilbish"
|
||||
preloadPath = dataDir + "/prelude/init.lua"
|
||||
preloadPath = dataDir + "/nature/init.lua"
|
||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
||||
defaultConfDir = getenv("XDG_CONFIG_HOME", "~/.config")
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@ var (
|
|||
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
|
||||
.. hilbish.userDir.config .. '/hilbish/?.lua'`
|
||||
dataDir = "/usr/share/hilbish"
|
||||
preloadPath = dataDir + "/prelude/init.lua"
|
||||
preloadPath = dataDir + "/nature/init.lua"
|
||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
||||
defaultConfDir = ""
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@ var (
|
|||
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\?.lua;'
|
||||
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?.lua;'`
|
||||
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
|
||||
defaultConfDir = ""
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue