Compare commits

...

37 Commits

Author SHA1 Message Date
TorchedSammy 1e2c88f4a4
chore: merge from master 2022-09-02 23:29:34 -04:00
TorchedSammy 959030f70d
refactor: automatically load all nature commands 2022-09-02 23:01:39 -04:00
TorchedSammy 59cec0ffa5
fix: call hinter for hint text handler 2022-09-02 22:19:30 -04:00
TorchedSammy 1eed4cc7ee
fix: add back empty string in command line split
this fixes file completion in normal usage without
using quotes. it basically cut out the space at the
end which prevented normal usage without adding an
additional space or using quotes for file
completion
2022-08-30 23:38:46 -04:00
TorchedSammy c13889592f
fix: pass alias expanded string to sh runner (fixes #201)
i have no idea why it didnt before, it *shouldnt*
introduce any problems and fixes this one.
2022-08-30 23:10:47 -04:00
TorchedSammy 2e192be2e1
refactor: setup autocd opt in a better way
with the previous commit allowing users to override
hilbish.runner.sh and it being ran by hilbish, the
code for the autocd opt can just override that
function and do the autocd functionality instead
of reimplementing a hybrid runner.

this means that if any other custom runner wants
autocd functionality they can have it with the sh runner
2022-08-30 23:08:22 -04:00
TorchedSammy c96605e79c
feat: allow hilbish.runner.sh to be overridden 2022-08-30 23:07:24 -04:00
TorchedSammy a1ce2ecba6
fix(readline): correct function to count len of prompt to accomodate east asian characters 2022-08-30 22:37:21 -04:00
TorchedSammy a1410ae7ad
fix: set prompt to normal after ctrl d exit
this prevents the prompt being stuck at the multiline
prompt instead of normal prompt after exiting
via the ctrl d bind
2022-08-30 21:52:07 -04:00
TorchedSammy 20870b9004
fix: only print motd when interactive 2022-08-17 18:05:20 -04:00
TorchedSammy 3dcd99563a docs: [ci] generate new docs 2022-08-17 22:01:55 +00:00
sammy 2337f9ab60
refactor: use custom event emitter (#193)
* refactor: use custom event emitter

* fix: sigint hook emit on windows

* fix: restore correct hilbish conf file

* fix: call recoverer for go listeners

* refactor(golibs/bait): use 1 map for listeners

* feat: add once listeners, ability to remove listeners and remove listener on error

* perf: reslice listener slice instead of trying to do ordered move with append

* feat(bait): add release function to remove event listener

* perf: remove listener directly from once emit instead of using off function

* refactor: use bait event emitter on commander

* docs(golibs/bait): add doc strings for functions

* docs: set changelog

* docs(golibs/bait): add docs for lua release function
2022-08-17 18:01:32 -04:00
TorchedSammy 6ce4fb3973
fix: add dot to sample config path on windows 2022-08-16 12:30:39 -04:00
TorchedSammy 387d7d2243
fix: percentages in completion entries causing a problem in the completion menus 2022-07-26 19:24:02 -04:00
TorchedSammy 7de835fab4
chore: update lunacolors (adds blackBg format arg) 2022-07-26 10:41:12 -04:00
TorchedSammy 3e0a2d630b
feat(hilbish.editor): add getLine function to get contents of line 2022-07-19 17:55:03 -04:00
TorchedSammy 477fcaf980
chore: update branch 2022-07-13 18:51:15 -04:00
TorchedSammy 09a8b41063
chore!: remove guide command (closes #188) 2022-07-13 16:04:57 -04:00
sammy 349380ae6b
feat: lua backed history (#187)
* refactor: put file history handler in line reader instance instead of global

* feat: add lua history handler in go

* feat: use lua to retrieve readline history

* refactor: handle history in lua

this also introduces a new opt: history
if it is false, command history won't get added

* fix: remove nature.history require

* docs: add changes in changelog

* fix: add comma after history opt
2022-07-13 16:02:09 -04:00
TorchedSammy f7806f5479 docs: [ci] generate new docs 2022-07-13 19:46:40 +00:00
TorchedSammy c8c30e9861
docs: update changelog 2022-07-13 15:46:18 -04:00
TorchedSammy 083c266438
feat(golibs/fs): add join function to join path elements 2022-07-13 15:38:07 -04:00
TorchedSammy dd9bdca5e0
fix(readline): only call raw input callback if not nil 2022-07-13 15:35:33 -04:00
TorchedSammy 9902560061
fix: set vt in on stdin 2022-07-13 15:11:06 -04:00
TorchedSammy dd9aa4b6ea
fix: enable vt input for windows 2022-07-13 15:04:18 -04:00
TorchedSammy be8bdef9c8
style: use single line import instead of list syntax 2022-07-13 14:18:23 -04:00
TorchedSammy e185a32685
fix: expand tilde in dataDir on windows 2022-07-13 14:10:29 -04:00
TorchedSammy 2b480e50e6
feat: print tracebacks for errors 2022-07-13 10:08:23 -04:00
TorchedSammy b65acca903
fix: initialize line reader before lua init 2022-07-10 22:07:01 -04:00
TorchedSammy 08e2951513
feat: add raw input hook (closes #180) 2022-07-10 20:34:00 -04:00
TorchedSammy 83a2ce38ea
docs: remove getting started section on readme 2022-07-09 18:39:21 -04:00
TorchedSammy 60dd5f598a
docs: replace make with task on readme 2022-07-09 18:38:57 -04:00
TorchedSammy 6eea5bce47
feat: add motd (closes #185) 2022-07-09 17:54:21 -04:00
TorchedSammy a106f4aea0
refactor!: move hilbish.greeting to an opt (closes #184) 2022-07-09 17:15:13 -04:00
TorchedSammy 90ed12d551
feat: add hilbish.init hook (closes #186) 2022-07-09 17:04:50 -04:00
sammy e0694c8862
build!: replace make with task (#171)
* build!: replace make with task

allows easy windows building (besides literally go build)
down the line because getting make on windows is dumb
and even if you do it probably wouldnt work as intended
on there

(i also find task more intuitive for simple things)

* ci: use task in build workflow instead of make

* style: remove whitespace errors in task file

* docs: add task in changelog

* docs: fix link to task

* docs: change message for task notice
2022-07-09 11:44:53 -04:00
TorchedSammy d27ce26be0
feat: complete files plainly if tab query begins with quotes (workaround #183) 2022-07-09 10:45:42 -04:00
44 changed files with 555 additions and 228 deletions

View File

@ -26,8 +26,10 @@ jobs:
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: '1.17.7' go-version: '1.17.7'
- name: Download Task
run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d'
- name: Build - name: Build
run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} make run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} ./bin/task
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
if: matrix.goos == 'windows' if: matrix.goos == 'windows'
with: with:

View File

@ -9,8 +9,6 @@ local function doPrompt(fail)
)) ))
end end
print(lunacolors.format(hilbish.greeting))
doPrompt() doPrompt()
bait.catch('command.exit', function(code) bait.catch('command.exit', function(code)

View File

@ -1,6 +1,10 @@
# 🎀 Changelog # 🎀 Changelog
## Unreleased ## Unreleased
**NOTE:** Hilbish now uses [Task] insead of Make for builds.
[Task]: https://taskfile.dev/#/
### Added ### Added
- Inline hints, akin to fish and the others. - Inline hints, akin to fish and the others.
To make a handler for hint text, you can set the `hilbish.hinter` function. To make a handler for hint text, you can set the `hilbish.hinter` function.
@ -52,11 +56,25 @@ having and using multiple runners.
- `fs.basename(path)` gets the basename of path - `fs.basename(path)` gets the basename of path
- `fs.dir(path)` gets the directory part of path - `fs.dir(path)` gets the directory part of path
- `fs.glob(pattern)` globs files and directories based on patterns - `fs.glob(pattern)` globs files and directories based on patterns
- `fs.join(dirs...)` joins directories by OS dir separator
- .. and 2 properties - .. and 2 properties
- `fs.pathSep` is the separator for filesystem paths and directories - `fs.pathSep` is the separator for filesystem paths and directories
- `fs.pathListSep` is the separator for $PATH env entries - `fs.pathListSep` is the separator for $PATH env entries
- Lua modules located in `hilbish.userDir.data .. '/hilbish/start'` (like `~/.local/share/hilbish/start/foo/init.lua`) - Lua modules located in `hilbish.userDir.data .. '/hilbish/start'` (like `~/.local/share/hilbish/start/foo/init.lua`)
will be ran on startup will be ran on startup
- `hilbish.init` hook, thrown after Hilbish has initialized Lua side
- Message of the day on startup (`hilbish.motd`), mainly intended as quick
small news pieces for releases. It is printed by default. To disable it,
set `hilbish.opts.motd` to false.
- `history` opt has been added and is true by default. Setting it to false
disables commands being added to history.
- `hilbish.rawInput` hook for input from the readline library
- Completion of files in quotes
- A new and "safer" event emitter has been added. This causes a performance deficit, but avoids a lot of
random errors introduced with the new Lua runtime (see [#197])
- `bait.release(name, catcher)` removes `handler` for the named `event`
[#197]: https://github.com/Rosettea/Hilbish/issues/197
### Changed ### Changed
- **Breaking Change:** Upgraded to Lua 5.4. - **Breaking Change:** Upgraded to Lua 5.4.
@ -73,11 +91,16 @@ 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. `continue` prompt for continued input, and to add it properly to history. `continue`
got added so that it would be easier for runners to get continued input got added so that it would be easier for runners to get continued input
without having to actually handle it at all. without having to actually handle it at all.
- **Breaking Change:** Job objects and timers are now Lua userdata instead - **Breaking Change:** Job objects and timers are now Lua userdata instead
of a table, so their functions require you to call them with a colon instead of a table, so their functions require you to call them with a colon instead
of a dot. (ie. `job.stop()` -> `job:stop()`) of a dot. (ie. `job.stop()` -> `job:stop()`)
- All `fs` module functions which take paths now implicitly expand ~ to home. - All `fs` module functions which take paths now implicitly expand ~ to home.
- **Breaking Change:** `hilbish.greeting` has been moved to an opt (`hilbish.opts.greeting`) and is
always printed by default. To disable it, set the opt to false.
- History is now fetched from Lua, which means users can override `hilbish.history`
methods to make it act how they want.
- `guide` has been removed. See the [website](https://rosettea.github.io/Hilbish/)
for general tips and documentation
### Fixed ### Fixed
- If in Vim replace mode, input at the end of the line inserts instead of - If in Vim replace mode, input at the end of the line inserts instead of
@ -112,6 +135,16 @@ for explanation.
Lua `job.stop` function. Lua `job.stop` function.
- Jobs are always started in sh exec handler now instead of only successful start. - Jobs are always started in sh exec handler now instead of only successful start.
- SIGTERM is handled properly now, which means stopping jobs and timers. - SIGTERM is handled properly now, which means stopping jobs and timers.
- Fix panic on trailing newline on pasted multiline text.
- Completions will no longer be refreshed if the prompt refreshes while the
menu is open.
- Print error on search fail instead of panicking
- Windows related fixes:
- `hilbish.dataDir` now has tilde (`~`) expanded.
- Arrow keys now work on Windows terminals.
- Escape codes now work.
- Escape percentage symbols in completion entries, so you will no longer see
an error of missing format variable
## [1.2.0] - 2022-03-17 ## [1.2.0] - 2022-03-17
### Added ### Added

View File

@ -1,30 +0,0 @@
PREFIX ?= /usr
BINDIR ?= $(PREFIX)/bin
LIBDIR ?= $(PREFIX)/share/hilbish
MY_GOFLAGS = -ldflags "-s -w"
all: dev
dev: MY_GOFLAGS = -ldflags "-s -w -X main.gitCommit=$(shell git rev-parse --short HEAD) -X main.gitBranch=$(shell git rev-parse --abbrev-ref HEAD)"
dev: build
build:
go build $(MY_GOFLAGS)
install:
install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v hilbish "$(DESTDIR)$(BINDIR)/hilbish"
mkdir -p "$(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:
rm -vrf \
"$(DESTDIR)$(BINDIR)/hilbish" \
"$(DESTDIR)$(LIBDIR)"
sed -i '/hilbish/d' /etc/shells
clean:
go clean
.PHONY: all dev build install uninstall clean

View File

@ -66,6 +66,7 @@ If you're new to nix you should probably read up on how to do that [here](https:
## Manual Build ## Manual Build
### Prerequisites ### Prerequisites
- [Go 1.17+](https://go.dev) - [Go 1.17+](https://go.dev)
- [Task](https://taskfile.dev/#/)
### Build ### Build
First, clone Hilbish. The recursive is required, as some Lua libraries First, clone Hilbish. The recursive is required, as some Lua libraries
@ -78,30 +79,16 @@ go get -d ./...
To build, run: To build, run:
``` ```
make dev task
``` ```
Or, if you want a stable branch, run these commands: Or, if you want a stable branch, run these commands:
``` ```
git checkout $(git describe --tags `git rev-list --tags --max-count=1`) git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
make build task build
``` ```
After you did all that, run `sudo make install` to install Hilbish globally. After you did all that, run `sudo task install` to install Hilbish globally.
# Getting Started
At startup, you should see a message which says to run a `guide` command.
This guide is a *very* simple and basic step through text of what Hilbish is
and where to find documentation.
Documentation is primarily viewed via the in shell `doc` command.
Autogenerated function docs and general docs about other things are included
there, so be sure to read it.
Using Hilbish is the same as using any other Linux shell, with an addition
that you can also run Lua. Hilbish can also act as an enhanced Lua REPL
via `hilbish.runnerMode 'lua'`. To switch back to normal, use
`hilbish.runnerMode 'hybrid'`.
# Contributing # Contributing
Any kind of contributions are welcome! Hilbish is very easy to contribute to. Any kind of contributions are welcome! Hilbish is very easy to contribute to.

36
Taskfile.yaml 100644
View File

@ -0,0 +1,36 @@
# https://taskfile.dev
version: '3'
vars:
PREFIX: '{{default "/usr" .PREFIX}}'
bindir__: '{{.PREFIX}}/bin'
BINDIR: '{{default .bindir__ .BINDIR}}'
libdir__: '{{.PREFIX}}/share/hilbish'
LIBDIR: '{{default .libdir__ .LIBDIR}}'
GOFLAGS: '-ldflags "-s -w"'
tasks:
default:
cmds:
- go build {{.GOFLAGS}}
vars:
GOFLAGS: '-ldflags "-s -w -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"'
build:
cmds:
- go build {{.GOFLAGS}}
install:
cmds:
- install -v -d "{{.DESTDIR}}{{.BINDIR}}/" && install -m 0755 -v hilbish "{{.DESTDIR}}{{.BINDIR}}/hilbish"
- mkdir -p "{{.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:
cmds:
- rm -vrf
"{{.DESTDIR}}{{.BINDIR}}/hilbish"
"{{.DESTDIR}}{{.LIBDIR}}"
- sed -i '/hilbish/d' /etc/shells

8
api.go
View File

@ -44,7 +44,6 @@ var exports = map[string]util.LuaExport{
"which": {hlwhich, 1, false}, "which": {hlwhich, 1, false},
} }
var greeting string
var hshMod *rt.Table var hshMod *rt.Table
var hilbishLoader = packagelib.Loader{ var hilbishLoader = packagelib.Loader{
Load: hilbishLoad, Load: hilbishLoad,
@ -103,10 +102,6 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows
} }
greeting = `Welcome to {magenta}Hilbish{reset}, {cyan}` + username + `{reset}.
The nice lil shell for {blue}Lua{reset} fanatics!
Check out the {blue}{bold}guide{reset} command to get started.
`
util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(getVersion()), "Hilbish version") util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(getVersion()), "Hilbish version")
util.SetFieldProtected(fakeMod, mod, "user", rt.StringValue(username), "Username of user") 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, "host", rt.StringValue(host), "Host name of the machine")
@ -114,7 +109,6 @@ Check out the {blue}{bold}guide{reset} command to get started.
util.SetFieldProtected(fakeMod, 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.SetFieldProtected(fakeMod, 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.SetFieldProtected(fakeMod, 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.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, "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.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.") util.Document(fakeMod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
@ -199,7 +193,7 @@ func getenv(key, fallback string) string {
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.Emit("hilbish.vimMode", mode)
} }
func unsetVimMode() { func unsetVimMode() {

View File

@ -11,8 +11,37 @@ import (
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
) )
func splitQuote(str string) []string {
split := []string{}
sb := &strings.Builder{}
quoted := false
for _, r := range str {
if r == '"' {
quoted = !quoted
sb.WriteRune(r)
} else if !quoted && r == ' ' {
split = append(split, sb.String())
sb.Reset()
} else {
sb.WriteRune(r)
}
}
if strings.HasSuffix(str, " ") {
split = append(split, "")
}
if sb.Len() > 0 {
split = append(split, sb.String())
}
return split
}
func fileComplete(query, ctx string, fields []string) ([]string, string) { func fileComplete(query, ctx string, fields []string) ([]string, string) {
return matchPath(query) q := splitQuote(ctx)
return matchPath(q[len(q) - 1])
} }
func binaryComplete(query, ctx string, fields []string) ([]string, string) { func binaryComplete(query, ctx string, fields []string) ([]string, string) {
@ -68,6 +97,8 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
} }
func matchPath(query string) ([]string, string) { func matchPath(query string) ([]string, string) {
oldQuery := query
query = strings.TrimPrefix(query, "\"")
var entries []string var entries []string
var baseName string var baseName string
@ -87,7 +118,9 @@ func matchPath(query string) ([]string, string) {
if file.IsDir() { if file.IsDir() {
entry = entry + string(os.PathSeparator) entry = entry + string(os.PathSeparator)
} }
entry = escapeFilename(entry) if !strings.HasPrefix(oldQuery, "\"") {
entry = escapeFilename(entry)
}
entries = append(entries, entry) entries = append(entries, entry)
} }
} }

View File

@ -2,5 +2,9 @@ catch(name, cb) > Catches a hook with `name`. Runs the `cb` when it is thrown
catchOnce(name, cb) > Same as catch, but only runs the `cb` once and then removes the hook catchOnce(name, cb) > Same as catch, but only runs the `cb` once and then removes the hook
release(name, catcher) > Removes the `catcher` for the event with `name`
For this to work, `catcher` has to be the same function used to catch
an event, like one saved to a variable.
throw(name, ...args) > Throws a hook with `name` with the provided `args` throw(name, ...args) > Throws a hook with `name` with the provided `args`

View File

@ -11,6 +11,9 @@ filepath.Dir
glob(pattern) > Glob all files and directories that match the pattern. glob(pattern) > Glob all files and directories that match the pattern.
For the rules, see Go's filepath.Glob For the rules, see Go's filepath.Glob
join(paths...) > Takes paths and joins them together with the OS's
directory separator (forward or backward slash).
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.
readdir(dir) > Returns a table of files in `dir` readdir(dir) > Returns a table of files in `dir`

View File

@ -11,6 +11,7 @@ func editorLoader(rtm *rt.Runtime) *rt.Table {
"insert": {editorInsert, 1, false}, "insert": {editorInsert, 1, false},
"setVimRegister": {editorSetRegister, 1, false}, "setVimRegister": {editorSetRegister, 1, false},
"getVimRegister": {editorGetRegister, 2, false}, "getVimRegister": {editorGetRegister, 2, false},
"getLine": {editorGetLine, 0, false},
} }
mod := rt.NewTable() mod := rt.NewTable()
@ -68,3 +69,9 @@ func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
} }
func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
buf := lr.rl.GetLine()
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
}

View File

@ -12,6 +12,11 @@ function bait.catch(name, cb) end
--- @param cb function --- @param cb function
function bait.catchOnce(name, cb) end function bait.catchOnce(name, cb) end
--- Removes the `catcher` for the event with `name`
--- For this to work, `catcher` has to be the same function used to catch
--- an event, like one saved to a variable.
function bait.release() end
--- Throws a hook with `name` with the provided `args` --- Throws a hook with `name` with the provided `args`
--- @param name string --- @param name string
--- @vararg any --- @vararg any

View File

@ -22,6 +22,10 @@ function fs.dir() end
--- For the rules, see Go's filepath.Glob --- For the rules, see Go's filepath.Glob
function fs.glob() end function fs.glob() end
--- Takes paths and joins them together with the OS's
--- directory separator (forward or backward slash).
function fs.join() end
--- Makes a directory called `name`. If `recursive` is true, it will create its parent directories. --- Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
--- @param name string --- @param name string
--- @param recursive boolean --- @param recursive boolean

27
exec.go
View File

@ -85,7 +85,7 @@ func isExecError(err error) (execError, bool) {
func runInput(input string, priv bool) { func runInput(input string, priv bool) {
running = true running = true
cmdString := aliases.Resolve(input) cmdString := aliases.Resolve(input)
hooks.Em.Emit("command.preexec", input, cmdString) hooks.Emit("command.preexec", input, cmdString)
rerun: rerun:
var exitCode uint8 var exitCode uint8
@ -101,7 +101,7 @@ func runInput(input string, priv bool) {
cmdFinish(0, input, priv) cmdFinish(0, input, priv)
return return
} }
input, exitCode, cont, err = handleSh(input) input, exitCode, cont, err = handleSh(cmdString)
case "hybridRev": case "hybridRev":
_, _, _, err = handleSh(input) _, _, _, err = handleSh(input)
if err == nil { if err == nil {
@ -112,7 +112,7 @@ func runInput(input string, priv bool) {
case "lua": case "lua":
input, exitCode, err = handleLua(cmdString) input, exitCode, err = handleLua(cmdString)
case "sh": case "sh":
input, exitCode, cont, err = handleSh(input) input, exitCode, cont, err = handleSh(cmdString)
} }
} else { } else {
// can only be a string or function so // can only be a string or function so
@ -140,7 +140,7 @@ func runInput(input string, priv bool) {
if err != nil { if err != nil {
if exErr, ok := isExecError(err); ok { if exErr, ok := isExecError(err); ok {
hooks.Em.Emit("command." + exErr.typ, exErr.cmd) hooks.Emit("command." + exErr.typ, exErr.cmd)
err = exErr.sprint() err = exErr.sprint()
} }
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
@ -152,6 +152,7 @@ func reprompt(input string) (string, error) {
for { for {
in, err := continuePrompt(strings.TrimSuffix(input, "\\")) in, err := continuePrompt(strings.TrimSuffix(input, "\\"))
if err != nil { if err != nil {
lr.SetPrompt(fmtPrompt(prompt))
return input, err return input, err
} }
@ -220,7 +221,17 @@ func handleLua(cmdString string) (string, uint8, error) {
return cmdString, 125, err return cmdString, 125, err
} }
func handleSh(cmdString string) (string, uint8, bool, error) { func handleSh(cmdString string) (input string, exitCode uint8, cont bool, runErr error) {
shRunner := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("sh"))
var err error
input, exitCode, cont, runErr, err = runLuaRunner(shRunner, cmdString)
if err != nil {
runErr = err
}
return
}
func execSh(cmdString string) (string, uint8, bool, error) {
_, _, err := execCommand(cmdString, true) _, _, err := execCommand(cmdString, true)
if err != nil { if err != nil {
// If input is incomplete, start multiline prompting // If input is incomplete, start multiline prompting
@ -540,13 +551,9 @@ func splitInput(input string) ([]string, string) {
} }
func cmdFinish(code uint8, cmdstr string, private bool) { func cmdFinish(code uint8, cmdstr string, private bool) {
// if input has space at the beginning, dont put in history
if interactive && !private {
handleHistory(cmdstr)
}
util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)), "Exit code of last exected command") util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)), "Exit code of last exected command")
// using AsValue (to convert to lua type) on an interface which is an int // using AsValue (to convert to lua type) on an interface which is an int
// results in it being unknown in lua .... ???? // results in it being unknown in lua .... ????
// so we allow the hook handler to take lua runtime Values // so we allow the hook handler to take lua runtime Values
hooks.Em.Emit("command.exit", rt.IntValue(int64(code)), cmdstr) hooks.Emit("command.exit", rt.IntValue(int64(code)), cmdstr, private)
} }

View File

@ -1,27 +1,41 @@
package bait package bait
import ( import (
"fmt"
"hilbish/util" "hilbish/util"
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib" "github.com/arnodel/golua/lib/packagelib"
"github.com/chuckpreslar/emission"
) )
type Bait struct{ type listenerType int
Em *emission.Emitter const (
Loader packagelib.Loader goListener listenerType = iota
luaListener
)
// Recoverer is a function which is called when a panic occurs in an event.
type Recoverer func(event string, handler *Listener, err interface{})
// Listener is a struct that holds the handler for an event.
type Listener struct{
typ listenerType
once bool
caller func(...interface{})
luaCaller *rt.Closure
} }
func New() Bait { type Bait struct{
emitter := emission.NewEmitter() Loader packagelib.Loader
emitter.RecoverWith(func(hookname, hookfunc interface{}, err error) { recoverer Recoverer
emitter.Off(hookname, hookfunc) handlers map[string][]*Listener
fmt.Println(err) rtm *rt.Runtime
}) }
b := Bait{
Em: emitter, // New creates a new Bait instance.
func New(rtm *rt.Runtime) *Bait {
b := &Bait{
handlers: make(map[string][]*Listener),
rtm: rtm,
} }
b.Loader = packagelib.Loader{ b.Loader = packagelib.Loader{
Load: b.loaderFunc, Load: b.loaderFunc,
@ -31,11 +45,148 @@ func New() Bait {
return b return b
} }
// Emit throws an event.
func (b *Bait) Emit(event string, args ...interface{}) {
handles := b.handlers[event]
if handles == nil {
return
}
for idx, handle := range handles {
defer func() {
if err := recover(); err != nil {
b.callRecoverer(event, handle, err)
}
}()
if handle.typ == luaListener {
funcVal := rt.FunctionValue(handle.luaCaller)
var luaArgs []rt.Value
for _, arg := range args {
var luarg rt.Value
switch arg.(type) {
case rt.Value: luarg = arg.(rt.Value)
default: luarg = rt.AsValue(arg)
}
luaArgs = append(luaArgs, luarg)
}
_, err := rt.Call1(b.rtm.MainThread(), funcVal, luaArgs...)
if err != nil {
// panicking here won't actually cause hilbish to panic and instead will
// print the error and remove the hook. reference the recoverer function in lua.go
panic(err)
}
} else {
handle.caller(args...)
}
if handle.once {
b.removeListener(event, idx)
}
}
}
// On adds a Go function handler for an event.
func (b *Bait) On(event string, handler func(...interface{})) *Listener {
listener := &Listener{
typ: goListener,
caller: handler,
}
b.addListener(event, listener)
return listener
}
// OnLua adds a Lua function handler for an event.
func (b *Bait) OnLua(event string, handler *rt.Closure) *Listener {
listener :=&Listener{
typ: luaListener,
luaCaller: handler,
}
b.addListener(event, listener)
return listener
}
// Off removes a Go function handler for an event.
func (b *Bait) Off(event string, listener *Listener) {
handles := b.handlers[event]
for i, handle := range handles {
if handle == listener {
b.removeListener(event, i)
}
}
}
// OffLua removes a Lua function handler for an event.
func (b *Bait) OffLua(event string, handler *rt.Closure) {
handles := b.handlers[event]
for i, handle := range handles {
if handle.luaCaller == handler {
b.removeListener(event, i)
}
}
}
// Once adds a Go function listener for an event that only runs once.
func (b *Bait) Once(event string, handler func(...interface{})) *Listener {
listener := &Listener{
typ: goListener,
once: true,
caller: handler,
}
b.addListener(event, listener)
return listener
}
// OnceLua adds a Lua function listener for an event that only runs once.
func (b *Bait) OnceLua(event string, handler *rt.Closure) *Listener {
listener := &Listener{
typ: luaListener,
once: true,
luaCaller: handler,
}
b.addListener(event, listener)
return listener
}
// SetRecoverer sets the function to be executed when a panic occurs in an event.
func (b *Bait) SetRecoverer(recoverer Recoverer) {
b.recoverer = recoverer
}
func (b *Bait) addListener(event string, listener *Listener) {
if b.handlers[event] == nil {
b.handlers[event] = []*Listener{}
}
b.handlers[event] = append(b.handlers[event], listener)
}
func (b *Bait) removeListener(event string, idx int) {
b.handlers[event][idx] = b.handlers[event][len(b.handlers[event]) - 1]
b.handlers[event] = b.handlers[event][:len(b.handlers[event]) - 1]
}
func (b *Bait) callRecoverer(event string, handler *Listener, err interface{}) {
if b.recoverer == nil {
panic(err)
}
b.recoverer(event, handler, err)
}
func (b *Bait) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { func (b *Bait) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
exports := map[string]util.LuaExport{ exports := map[string]util.LuaExport{
"catch": util.LuaExport{b.bcatch, 2, false}, "catch": util.LuaExport{b.bcatch, 2, false},
"catchOnce": util.LuaExport{b.bcatchOnce, 2, false}, "catchOnce": util.LuaExport{b.bcatchOnce, 2, false},
"throw": util.LuaExport{b.bthrow, 1, true}, "throw": util.LuaExport{b.bthrow, 1, true},
"release": util.LuaExport{b.brelease, 2, false},
} }
mod := rt.NewTable() mod := rt.NewTable()
util.SetExports(rtm, mod, exports) util.SetExports(rtm, mod, exports)
@ -89,7 +240,7 @@ func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
for i, v := range c.Etc() { for i, v := range c.Etc() {
ifaceSlice[i] = v ifaceSlice[i] = v
} }
b.Em.Emit(name, ifaceSlice...) b.Emit(name, ifaceSlice...)
return c.Next(), nil return c.Next(), nil
} }
@ -104,9 +255,7 @@ func (b *Bait) bcatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
b.Em.On(name, func(args ...interface{}) { b.OnLua(name, catcher)
handleHook(t, c, name, catcher, args...)
})
return c.Next(), nil return c.Next(), nil
} }
@ -121,9 +270,22 @@ func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
b.Em.Once(name, func(args ...interface{}) { b.OnceLua(name, catcher)
handleHook(t, c, name, catcher, args...)
}) return c.Next(), nil
}
// release(name, catcher)
// Removes the `catcher` for the event with `name`
// For this to work, `catcher` has to be the same function used to catch
// an event, like one saved to a variable.
func (b *Bait) brelease(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
name, catcher, err := util.HandleStrCallback(t, c)
if err != nil {
return nil, err
}
b.OffLua(name, catcher)
return c.Next(), nil return c.Next(), nil
} }

View File

@ -2,20 +2,20 @@ package commander
import ( import (
"hilbish/util" "hilbish/util"
"hilbish/golibs/bait"
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib" "github.com/arnodel/golua/lib/packagelib"
"github.com/chuckpreslar/emission"
) )
type Commander struct{ type Commander struct{
Events *emission.Emitter Events *bait.Bait
Loader packagelib.Loader Loader packagelib.Loader
} }
func New() Commander { func New(rtm *rt.Runtime) Commander {
c := Commander{ c := Commander{
Events: emission.NewEmitter(), Events: bait.New(rtm),
} }
c.Loader = packagelib.Loader{ c.Loader = packagelib.Loader{
Load: c.loaderFunc, Load: c.loaderFunc,

View File

@ -1,6 +1,7 @@
package fs package fs
import ( import (
"fmt"
"path/filepath" "path/filepath"
"strconv" "strconv"
"os" "os"
@ -27,6 +28,7 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
"basename": util.LuaExport{fbasename, 1, false}, "basename": util.LuaExport{fbasename, 1, false},
"dir": util.LuaExport{fdir, 1, false}, "dir": util.LuaExport{fdir, 1, false},
"glob": util.LuaExport{fglob, 1, false}, "glob": util.LuaExport{fglob, 1, false},
"join": util.LuaExport{fjoin, 0, true},
} }
mod := rt.NewTable() mod := rt.NewTable()
util.SetExports(rtm, mod, exports) util.SetExports(rtm, mod, exports)
@ -216,3 +218,21 @@ func fglob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext(t.Runtime, rt.TableValue(luaMatches)), nil return c.PushingNext(t.Runtime, rt.TableValue(luaMatches)), nil
} }
// join(paths...)
// Takes paths and joins them together with the OS's
// directory separator (forward or backward slash).
func fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
strs := make([]string, len(c.Etc()))
for i, v := range c.Etc() {
if v.Type() != rt.StringType {
// +2; go indexes of 0 and first arg from above
return nil, fmt.Errorf("bad argument #%d to run (expected string, got %s)", i + 1, v.TypeName())
}
strs[i] = v.AsString()
}
res := filepath.Join(strs...)
return c.PushingNext(t.Runtime, rt.StringValue(res)), nil
}

View File

@ -4,21 +4,69 @@ import (
"errors" "errors"
"io/fs" "io/fs"
"os" "os"
"path/filepath"
"strings" "strings"
rt "github.com/arnodel/golua/runtime"
) )
type luaHistory struct {}
func (h *luaHistory) Write(line string) (int, error) {
histWrite := hshMod.Get(rt.StringValue("history")).AsTable().Get(rt.StringValue("add"))
ln, err := rt.Call1(l.MainThread(), histWrite, rt.StringValue(line))
var num int64
if ln.Type() == rt.IntType {
num = ln.AsInt()
}
return int(num), err
}
func (h *luaHistory) GetLine(idx int) (string, error) {
histGet := hshMod.Get(rt.StringValue("history")).AsTable().Get(rt.StringValue("get"))
lcmd, err := rt.Call1(l.MainThread(), histGet, rt.IntValue(int64(idx)))
var cmd string
if lcmd.Type() == rt.StringType {
cmd = lcmd.AsString()
}
return cmd, err
}
func (h *luaHistory) Len() int {
histSize := hshMod.Get(rt.StringValue("history")).AsTable().Get(rt.StringValue("size"))
ln, _ := rt.Call1(l.MainThread(), histSize)
var num int64
if ln.Type() == rt.IntType {
num = ln.AsInt()
}
return int(num)
}
func (h *luaHistory) Dump() interface{} {
// hilbish.history interface already has all function, this isnt used in readline
return nil
}
type fileHistory struct { type fileHistory struct {
items []string items []string
f *os.File f *os.File
} }
func newFileHistory() *fileHistory { func newFileHistory(path string) *fileHistory {
err := os.MkdirAll(defaultHistDir, 0755) dir := filepath.Dir(path)
err := os.MkdirAll(dir, 0755)
if err != nil { if err != nil {
panic(err) panic(err)
} }
data, err := os.ReadFile(defaultHistPath) data, err := os.ReadFile(path)
if err != nil { if err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
panic(err) panic(err)
@ -33,7 +81,7 @@ func newFileHistory() *fileHistory {
} }
itms = append(itms, l) itms = append(itms, l)
} }
f, err := os.OpenFile(defaultHistPath, os.O_APPEND | os.O_WRONLY | os.O_CREATE, 0755) f, err := os.OpenFile(path, os.O_APPEND | os.O_WRONLY | os.O_CREATE, 0755)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -5,7 +5,13 @@ package main
import "golang.org/x/sys/windows" import "golang.org/x/sys/windows"
func init() { func init() {
var mode uint32 // vt output (escape codes)
windows.GetConsoleMode(windows.Stdout, &mode) var outMode uint32
windows.SetConsoleMode(windows.Stdout, mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) windows.GetConsoleMode(windows.Stdout, &outMode)
windows.SetConsoleMode(windows.Stdout, outMode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
// vt input
var inMode uint32
windows.GetConsoleMode(windows.Stdin, &inMode)
windows.SetConsoleMode(windows.Stdin, inMode | windows.ENABLE_VIRTUAL_TERMINAL_INPUT)
} }

6
job.go
View File

@ -67,7 +67,7 @@ func (j *job) start() error {
j.pid = proc.Pid j.pid = proc.Pid
j.running = true j.running = true
hooks.Em.Emit("job.start", rt.UserDataValue(j.ud)) hooks.Emit("job.start", rt.UserDataValue(j.ud))
return err return err
} }
@ -82,7 +82,7 @@ func (j *job) stop() {
func (j *job) finish() { func (j *job) finish() {
j.running = false j.running = false
hooks.Em.Emit("job.done", rt.UserDataValue(j.ud)) hooks.Emit("job.done", rt.UserDataValue(j.ud))
} }
func (j *job) wait() { func (j *job) wait() {
@ -236,7 +236,7 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job {
jb.ud = jobUserData(jb) jb.ud = jobUserData(jb)
j.jobs[j.latestID] = jb j.jobs[j.latestID] = jb
hooks.Em.Emit("job.add", rt.UserDataValue(jb.ud)) hooks.Emit("job.add", rt.UserDataValue(jb.ud))
return jb return jb
} }

@ -1 +1 @@
Subproject commit d60cd77c73875b5bb55e5a2fdc30bae01a7ac499 Subproject commit 8467b87dd8d49c68b4100b2d129d5f071544b8cf

28
lua.go
View File

@ -12,12 +12,16 @@ import (
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib" "github.com/arnodel/golua/lib"
"github.com/arnodel/golua/lib/debuglib"
) )
var minimalconf = `hilbish.prompt '& '` var minimalconf = `hilbish.prompt '& '`
func luaInit() { func luaInit() {
l = rt.New(os.Stdout) l = rt.New(os.Stdout)
l.PushContext(rt.RuntimeContextDef{
MessageHandler: debuglib.Traceback,
})
lib.LoadAll(l) lib.LoadAll(l)
lib.LoadLibs(l, hilbishLoader) lib.LoadLibs(l, hilbishLoader)
@ -28,26 +32,40 @@ func luaInit() {
lib.LoadLibs(l, fs.Loader) lib.LoadLibs(l, fs.Loader)
lib.LoadLibs(l, terminal.Loader) lib.LoadLibs(l, terminal.Loader)
cmds := commander.New() cmds := commander.New(l)
// When a command from Lua is added, register it for use // When a command from Lua is added, register it for use
cmds.Events.On("commandRegister", func(cmdName string, cmd *rt.Closure) { cmds.Events.On("commandRegister", func(args ...interface{}) {
cmdName := args[0].(string)
cmd := args[1].(*rt.Closure)
commands[cmdName] = cmd commands[cmdName] = cmd
}) })
cmds.Events.On("commandDeregister", func(cmdName string) { cmds.Events.On("commandDeregister", func(args ...interface{}) {
cmdName := args[0].(string)
delete(commands, cmdName) delete(commands, cmdName)
}) })
lib.LoadLibs(l, cmds.Loader) lib.LoadLibs(l, cmds.Loader)
hooks = bait.New() hooks = bait.New(l)
hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) {
fmt.Println("Error in", event, "event:", err)
hooks.Off(event, handler)
})
lib.LoadLibs(l, hooks.Loader) lib.LoadLibs(l, hooks.Loader)
// Add Ctrl-C handler // Add Ctrl-C handler
hooks.Em.On("signal.sigint", func() { hooks.On("signal.sigint", func(...interface{}) {
if !interactive { if !interactive {
os.Exit(0) os.Exit(0)
} }
}) })
lr.rl.RawInputCallback = func(r []rune) {
hooks.Emit("hilbish.rawInput", string(r))
}
// 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 {

16
main.go
View File

@ -30,7 +30,7 @@ var (
userDataDir string userDataDir string
curuser *user.User curuser *user.User
hooks bait.Bait hooks *bait.Bait
defaultConfPath string defaultConfPath string
defaultHistPath string defaultHistPath string
) )
@ -116,8 +116,8 @@ func main() {
} }
go handleSignals() go handleSignals()
luaInit()
lr = newLineReader("", false) lr = newLineReader("", false)
luaInit()
// If user's config doesn't exixt, // If user's config doesn't exixt,
if _, err := os.Stat(defaultConfPath); os.IsNotExist(err) && *configflag == defaultConfPath { if _, err := os.Stat(defaultConfPath); os.IsNotExist(err) && *configflag == defaultConfPath {
// Read default from current directory // Read default from current directory
@ -138,6 +138,7 @@ func main() {
} else { } else {
runConfig(*configflag) runConfig(*configflag)
} }
hooks.Emit("hilbish.init")
if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 { if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
scanner := bufio.NewScanner(bufio.NewReader(os.Stdin)) scanner := bufio.NewScanner(bufio.NewReader(os.Stdin))
@ -176,7 +177,7 @@ input:
if err == io.EOF { if err == io.EOF {
// Exit if user presses ^D (ctrl + d) // Exit if user presses ^D (ctrl + d)
hooks.Em.Emit("hilbish.exit") hooks.Emit("hilbish.exit")
break break
} }
if err != nil { if err != nil {
@ -195,7 +196,7 @@ input:
input = strings.TrimSpace(input) input = strings.TrimSpace(input)
if len(input) == 0 { if len(input) == 0 {
running = true running = true
hooks.Em.Emit("command.exit", 0) hooks.Emit("command.exit", 0)
continue continue
} }
@ -226,7 +227,7 @@ input:
} }
func continuePrompt(prev string) (string, error) { func continuePrompt(prev string) (string, error) {
hooks.Em.Emit("multiline", nil) hooks.Emit("multiline", nil)
lr.SetPrompt(multilinePrompt) lr.SetPrompt(multilinePrompt)
cont, err := lr.Read() cont, err := lr.Read()
if err != nil { if err != nil {
@ -268,11 +269,6 @@ func fmtPrompt(prompt string) string {
return nprompt return nprompt
} }
func handleHistory(cmd string) {
lr.AddHistory(cmd)
// TODO: load history again (history shared between sessions like this ye)
}
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

@ -1,54 +0,0 @@
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

@ -1,9 +1,19 @@
-- Add command builtins local fs = require 'fs'
require 'nature.commands.cd'
require 'nature.commands.cdr' -- explanation: this specific function gives to us info about
require 'nature.commands.doc' -- the currently running source. this includes a path to the
require 'nature.commands.exit' -- source file (info.source)
require 'nature.commands.guide' -- we will use that to automatically load all commands by reading
require 'nature.commands.disown' -- all the files in this dir and just requiring it.
require 'nature.commands.fg' local info = debug.getinfo(1)
require 'nature.commands.bg' local commandDir = fs.dir(info.source)
if commandDir == '.' then return end
local commands = fs.readdir(commandDir)
for _, command in ipairs(commands) do
local name = command:gsub('%.lua', '') -- chop off extension
if name ~= 'init' then
-- skip this file (for obvious reasons)
require('nature.commands.' .. name)
end
end

View File

@ -1,13 +1,8 @@
local fs = require 'fs' local fs = require 'fs'
function cdHandle(inp) local oldShRunner = hilbish.runner.sh
local res = hilbish.runner.lua(inp) function hilbish.runner.sh(input)
local res = oldShRunner(input)
if not res.err then
return res
end
res = hilbish.runner.sh(inp)
if res.exit ~= 0 and hilbish.opts.autocd then if res.exit ~= 0 and hilbish.opts.autocd then
local ok, stat = pcall(fs.stat, res.input) local ok, stat = pcall(fs.stat, res.input)
@ -21,5 +16,3 @@ function cdHandle(inp)
return res return res
end end
hilbish.runner.setMode(cdHandle)

View File

@ -0,0 +1,8 @@
local bait = require 'bait'
local lunacolors = require 'lunacolors'
bait.catch('hilbish.init', function()
if hilbish.interactive and type(hilbish.opts.greeting) == 'string' then
print(lunacolors.format(hilbish.opts.greeting))
end
end)

View File

@ -0,0 +1,5 @@
local bait = require 'bait'
bait.catch('command.exit', function(_, cmd, priv)
if not priv and hilbish.opts.history then hilbish.history.add(cmd) end
end)

View File

@ -20,7 +20,12 @@ local function setupOpt(name, default)
end end
local defaultOpts = { local defaultOpts = {
autocd = false autocd = false,
history = true,
greeting = string.format([[Welcome to {magenta}Hilbish{reset}, {cyan}%s{reset}.
The nice lil shell for {blue}Lua{reset} fanatics!
]], hilbish.user),
motd = true
} }
for optsName, default in pairs(defaultOpts) do for optsName, default in pairs(defaultOpts) do

View File

@ -0,0 +1,13 @@
local bait = require 'bait'
local lunacolors = require 'lunacolors'
hilbish.motd = [[
Hilbish 2.0 is a {red}major{reset} update! If your config doesn't work
anymore, that will definitely be why! A MOTD, very message, much day.
]]
bait.catch('hilbish.init', function()
if hilbish.interactive and hilbish.opts.motd then
print(lunacolors.format(hilbish.motd))
end
end)

View File

@ -99,7 +99,7 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
// If group title, print it and adjust offset. // If group title, print it and adjust offset.
if g.Name != "" { if g.Name != "" {
comp += fmt.Sprintf("%s%s%s %s\n", BOLD, YELLOW, g.Name, RESET) comp += fmt.Sprintf("%s%s%s %s\n", BOLD, YELLOW, fmtEscape(g.Name), RESET)
rl.tcUsedY++ rl.tcUsedY++
} }
@ -124,7 +124,7 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
comp += seqInvert comp += seqInvert
} }
comp += fmt.Sprintf("%-"+cellWidth+"s %s", g.Suggestions[i], seqReset) comp += fmt.Sprintf("%-"+cellWidth+"s %s", fmtEscape(g.Suggestions[i]), seqReset)
} }
// Always add a newline to the group if the end if not punctuated with one // Always add a newline to the group if the end if not punctuated with one

View File

@ -1,5 +1,7 @@
package readline package readline
import "strings"
// CompletionGroup - A group/category of items offered to completion, with its own // CompletionGroup - A group/category of items offered to completion, with its own
// name, descriptions and completion display format/type. // name, descriptions and completion display format/type.
// The output, if there are multiple groups available for a given completion input, // The output, if there are multiple groups available for a given completion input,
@ -285,3 +287,7 @@ func (g *CompletionGroup) goLastCell() {
g.tcPosX = 0 g.tcPosX = 0
} }
} }
func fmtEscape(s string) string {
return strings.Replace(s, "%", "%%", -1)
}

View File

@ -206,12 +206,12 @@ func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
if len(item) > maxLength { if len(item) > maxLength {
item = item[:maxLength-3] + "..." item = item[:maxLength-3] + "..."
} }
sugg := fmt.Sprintf("\r%s%-"+cellWidth+"s", highlight(y, 0), item) sugg := fmt.Sprintf("\r%s%-"+cellWidth+"s", highlight(y, 0), fmtEscape(item))
// Alt suggestion // Alt suggestion
alt, ok := g.Aliases[item] alt, ok := g.Aliases[item]
if ok { if ok {
alt = fmt.Sprintf(" %s%"+cellWidthAlt+"s", highlight(y, 1), alt) alt = fmt.Sprintf(" %s%"+cellWidthAlt+"s", highlight(y, 1), fmtEscape(alt))
} else { } else {
// Else, make an empty cell // Else, make an empty cell
alt = strings.Repeat(" ", maxLengthAlt+1) // + 2 to keep account of spaces alt = strings.Repeat(" ", maxLengthAlt+1) // + 2 to keep account of spaces

View File

@ -76,7 +76,7 @@ func (g *CompletionGroup) writeMap(rl *Instance) (comp string) {
if g.Name != "" { if g.Name != "" {
// Print group title (changes with line returns depending on type) // Print group title (changes with line returns depending on type)
comp += fmt.Sprintf("%s%s%s %s\n", BOLD, YELLOW, g.Name, RESET) comp += fmt.Sprintf("%s%s%s %s\n", BOLD, YELLOW, fmtEscape(g.Name), RESET)
rl.tcUsedY++ rl.tcUsedY++
} }
@ -126,7 +126,7 @@ func (g *CompletionGroup) writeMap(rl *Instance) (comp string) {
} }
comp += fmt.Sprintf("\r%-"+cellWidth+"s %s %-"+itemWidth+"s %s\n", comp += fmt.Sprintf("\r%-"+cellWidth+"s %s %-"+itemWidth+"s %s\n",
description, highlight(y), item, seqReset) description, highlight(y), fmtEscape(item), seqReset)
} }
// Add the equivalent of this group's size to final screen clearing // Add the equivalent of this group's size to final screen clearing

View File

@ -198,6 +198,8 @@ type Instance struct {
ViModeCallback func(ViMode) ViModeCallback func(ViMode)
ViActionCallback func(ViAction, []string) ViActionCallback func(ViAction, []string)
RawInputCallback func([]rune) // called on all input
} }
// NewInstance is used to create a readline instance and initialise it with sane defaults. // NewInstance is used to create a readline instance and initialise it with sane defaults.

View File

@ -18,7 +18,7 @@ func (rl *Instance) updateLine(line []rune) {
// getLine - In many places we need the current line input. We either return the real line, // getLine - In many places we need the current line input. We either return the real line,
// or the one that includes the current completion candidate, if there is any. // or the one that includes the current completion candidate, if there is any.
func (rl *Instance) getLine() []rune { func (rl *Instance) GetLine() []rune {
if len(rl.currentComp) > 0 { if len(rl.currentComp) > 0 {
return rl.lineComp return rl.lineComp
} }

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
ansi "github.com/acarl005/stripansi" ansi "github.com/acarl005/stripansi"
"github.com/rivo/uniseg"
) )
// SetPrompt will define the readline prompt string. // SetPrompt will define the readline prompt string.
@ -209,7 +208,7 @@ func (rl *Instance) colorizeVimPrompt(p []rune) (cp []rune) {
// getting its real-printed length. // getting its real-printed length.
func getRealLength(s string) (l int) { func getRealLength(s string) (l int) {
stripped := ansi.Strip(s) stripped := ansi.Strip(s)
return uniseg.GraphemeClusterCount(stripped) return getWidth([]rune(stripped))
} }
func (rl *Instance) echoRightPrompt() { func (rl *Instance) echoRightPrompt() {

View File

@ -94,6 +94,9 @@ func (rl *Instance) Readline() (string, error) {
rl.skipStdinRead = false rl.skipStdinRead = false
r := []rune(string(b)) r := []rune(string(b))
if rl.RawInputCallback != nil {
rl.RawInputCallback(r[:i])
}
if isMultiline(r[:i]) || len(rl.multiline) > 0 { if isMultiline(r[:i]) || len(rl.multiline) > 0 {
rl.multiline = append(rl.multiline, b[:i]...) rl.multiline = append(rl.multiline, b[:i]...)

View File

@ -24,7 +24,7 @@ func delayedSyntaxTimer(rl *Instance, i int64) {
// } // }
// We pass either the current line or the one with the current completion. // We pass either the current line or the one with the current completion.
newLine := rl.DelayedSyntaxWorker(rl.getLine()) newLine := rl.DelayedSyntaxWorker(rl.GetLine())
var sLine string var sLine string
count := atomic.LoadInt64(&rl.delayedSyntaxCount) count := atomic.LoadInt64(&rl.delayedSyntaxCount)
if count != i { if count != i {

32
rl.go
View File

@ -13,18 +13,22 @@ import (
type lineReader struct { type lineReader struct {
rl *readline.Instance rl *readline.Instance
fileHist *fileHistory
} }
var fileHist *fileHistory
var hinter *rt.Closure var hinter *rt.Closure
var highlighter *rt.Closure var highlighter *rt.Closure
func newLineReader(prompt string, noHist bool) *lineReader { func newLineReader(prompt string, noHist bool) *lineReader {
rl := readline.NewInstance() rl := readline.NewInstance()
lr := &lineReader{
rl: rl,
}
// we don't mind hilbish.read rl instances having completion, // we don't mind hilbish.read rl instances having completion,
// but it cant have shared history // but it cant have shared history
if !noHist { if !noHist {
fileHist = newFileHistory() lr.fileHist = newFileHistory(defaultHistPath)
rl.SetHistoryCtrlR("History", fileHist) rl.SetHistoryCtrlR("History", &luaHistory{})
rl.HistoryAutoWrite = false rl.HistoryAutoWrite = false
} }
rl.ShowVimMode = false rl.ShowVimMode = false
@ -44,14 +48,14 @@ func newLineReader(prompt string, noHist bool) *lineReader {
case readline.VimActionPaste: actionStr = "paste" case readline.VimActionPaste: actionStr = "paste"
case readline.VimActionYank: actionStr = "yank" case readline.VimActionYank: actionStr = "yank"
} }
hooks.Em.Emit("hilbish.vimAction", actionStr, args) hooks.Emit("hilbish.vimAction", actionStr, args)
} }
rl.HintText = func(line []rune, pos int) []rune { rl.HintText = func(line []rune, pos int) []rune {
if hinter == nil { if hinter == nil {
return []rune{} return []rune{}
} }
retVal, err := rt.Call1(l.MainThread(), rt.FunctionValue(highlighter), retVal, err := rt.Call1(l.MainThread(), rt.FunctionValue(hinter),
rt.StringValue(string(line)), rt.IntValue(int64(pos))) rt.StringValue(string(line)), rt.IntValue(int64(pos)))
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -171,13 +175,11 @@ func newLineReader(prompt string, noHist bool) *lineReader {
return pfx, compGroups return pfx, compGroups
} }
return &lineReader{ return lr
rl,
}
} }
func (lr *lineReader) Read() (string, error) { func (lr *lineReader) Read() (string, error) {
hooks.Em.Emit("command.precmd", nil) hooks.Emit("command.precmd", nil)
s, err := lr.rl.Readline() s, err := lr.rl.Readline()
// this is so dumb // this is so dumb
if err == readline.EOF { if err == readline.EOF {
@ -212,7 +214,7 @@ func (lr *lineReader) SetRightPrompt(p string) {
} }
func (lr *lineReader) AddHistory(cmd string) { func (lr *lineReader) AddHistory(cmd string) {
fileHist.Write(cmd) lr.fileHist.Write(cmd)
} }
func (lr *lineReader) ClearInput() { func (lr *lineReader) ClearInput() {
@ -253,7 +255,7 @@ func (lr *lineReader) luaAddHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error)
} }
func (lr *lineReader) luaSize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (lr *lineReader) luaSize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.IntValue(int64(fileHist.Len()))), nil return c.PushingNext1(t.Runtime, rt.IntValue(int64(lr.fileHist.Len()))), nil
} }
func (lr *lineReader) luaGetHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (lr *lineReader) luaGetHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -265,17 +267,17 @@ func (lr *lineReader) luaGetHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error)
return nil, err return nil, err
} }
cmd, _ := fileHist.GetLine(int(idx)) cmd, _ := lr.fileHist.GetLine(int(idx))
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
} }
func (lr *lineReader) luaAllHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (lr *lineReader) luaAllHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
tbl := rt.NewTable() tbl := rt.NewTable()
size := fileHist.Len() size := lr.fileHist.Len()
for i := 1; i < size; i++ { for i := 1; i < size; i++ {
cmd, _ := fileHist.GetLine(i) cmd, _ := lr.fileHist.GetLine(i)
tbl.Set(rt.IntValue(int64(i)), rt.StringValue(cmd)) tbl.Set(rt.IntValue(int64(i)), rt.StringValue(cmd))
} }
@ -283,6 +285,6 @@ func (lr *lineReader) luaAllHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error)
} }
func (lr *lineReader) luaClearHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (lr *lineReader) luaClearHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
fileHist.clear() lr.fileHist.clear()
return c.Next(), nil return c.Next(), nil
} }

View File

@ -28,7 +28,7 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
input, exitCode, cont, err := handleSh(cmd) input, exitCode, cont, err := execSh(cmd)
var luaErr rt.Value = rt.NilValue var luaErr rt.Value = rt.NilValue
if err != nil { if err != nil {
luaErr = rt.StringValue(err.Error()) luaErr = rt.StringValue(err.Error())

View File

@ -15,11 +15,11 @@ func handleSignals() {
for s := range c { for s := range c {
switch s { switch s {
case os.Interrupt: hooks.Em.Emit("signal.sigint") case os.Interrupt: hooks.Emit("signal.sigint")
case syscall.SIGTERM: exit(0) case syscall.SIGTERM: exit(0)
case syscall.SIGWINCH: hooks.Em.Emit("signal.resize") case syscall.SIGWINCH: hooks.Emit("signal.resize")
case syscall.SIGUSR1: hooks.Em.Emit("signal.sigusr1") case syscall.SIGUSR1: hooks.Emit("signal.sigusr1")
case syscall.SIGUSR2: hooks.Em.Emit("signal.sigusr2") case syscall.SIGUSR2: hooks.Emit("signal.sigusr2")
} }
} }
} }

View File

@ -14,7 +14,7 @@ func handleSignals() {
for s := range c { for s := range c {
switch s { switch s {
case os.Interrupt: case os.Interrupt:
hooks.Em.Emit("signal.sigint") hooks.Emit("signal.sigint")
if !running && interactive { if !running && interactive {
lr.ClearInput() lr.ClearInput()
} }

View File

@ -2,14 +2,16 @@
package main package main
import "hilbish/util"
// String vars that are free to be changed at compile time // String vars that are free to be changed at compile time
var ( var (
requirePaths = commonRequirePaths + `.. ';' requirePaths = commonRequirePaths + `.. ';'
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\init.lua;' .. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\init.lua;'
.. 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 = util.ExpandHome("~\\Appdata\\Roaming\\Hilbish") // ~ and \ gonna cry?
preloadPath = dataDir + "\\nature\\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 = ""
) )