fix: bring in latest changes

insensitive-tab
TorchedSammy 2022-05-07 08:11:32 -04:00
commit 0e076f391a
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
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,11 +132,19 @@ func runInput(input string, priv bool) {
input = inp input = inp
} }
if runErr != rt.NilValue { if errStr, ok := runErr.TryString(); ok {
fmt.Fprintln(os.Stderr, runErr) err = fmt.Errorf("%s", errStr)
}
}
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) {
@ -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 = ""
) )