chore: merge from master

popen
sammyette 2022-12-13 15:17:00 -04:00
commit 5c748daaa3
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
88 changed files with 1454 additions and 305 deletions

6
.editorconfig 100644
View File

@ -0,0 +1,6 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = tab

View File

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

View File

@ -33,10 +33,13 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: true
- name: Download Task
run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d'
- uses: wangyoucao577/go-release-action@v1.25
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
build_command: task
binary_name: hilbish
extra_files: LICENSE README.md CHANGELOG.md .hilbishrc.lua nature libs docs emmyLuaDocs

31
.github/workflows/website.yml vendored 100644
View File

@ -0,0 +1,31 @@
name: Build website
on:
push:
branches:
- master
- website
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build
run: 'cd website && hugo --minify'
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./website/public

View File

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

View File

@ -1,6 +1,19 @@
# 🎀 Changelog
## Unreleased
**NOTES FOR USERS/PACKAGERS UPDATING:**
- Hilbish now uses [Task] insead of Make for builds.
- The doc format has been changed from plain text to markdown.
**YOU MUST reinstall Hilbish to remove the duplicate, old docs.**
- Hilbish will by default install to **`/usr/local`** instead of just `/usr/`
when building via Task. This is mainly to avoid conflict of distro packages
and local installs, and is the correct place when building from git either way.
To keep Hilbish in `/usr`, you must have `PREFIX="/usr/"` when running `task build` or `task install`
- Windows is no longer supported. It will build and run, but **will** have problems.
If you want to help fix the situation, start a discussion or open an issue and contribute.
[Task]: https://taskfile.dev/#/
### Added
- Inline hints, akin to fish and the others.
To make a handler for hint text, you can set the `hilbish.hinter` function.
@ -52,11 +65,29 @@ having and using multiple runners.
- `fs.basename(path)` gets the basename of path
- `fs.dir(path)` gets the directory part of path
- `fs.glob(pattern)` globs files and directories based on patterns
- `fs.join(dirs...)` joins directories by OS dir separator
- .. and 2 properties
- `fs.pathSep` is the separator for filesystem paths and directories
- `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`)
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`
- `exec`, `clear` and `cat` builtin commands
- `hilbish.cancel` hook
- 1st item on history is now inserted when history search menu is opened ([#148])
[#148]: https://github.com/Rosettea/Hilbish/issues/148
[#197]: https://github.com/Rosettea/Hilbish/issues/197
### Changed
- **Breaking Change:** Upgraded to Lua 5.4.
@ -73,11 +104,17 @@ 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`
got added so that it would be easier for runners to get continued input
without having to actually handle it at all.
- **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 dot. (ie. `job.stop()` -> `job:stop()`)
- 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.
- **Breaking Change:** `command.no-perm` hook has been replaced with `command.not-executable`
- 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
- If in Vim replace mode, input at the end of the line inserts instead of
@ -112,6 +149,33 @@ for explanation.
Lua `job.stop` function.
- 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.
- 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
- Fix an error with sh syntax in aliases
- Prompt now works with east asian characters (CJK)
- Set back the prompt to normal after exiting the continue prompt with ctrl-d
- Users can now tab complete files with spaces while quoted or with escaped spaces.
This means a query of `Files\ to\ ` with file names of `Files to tab complete` and `Files to complete`
will result in the files being completed.
- Fixed grid menu display if cell width ends up being the width of the terminal
- Cut off item names in grid menu if its longer than cell width
- Fix completion search menu disappearing
- Completion paths having duplicated characters if it's escaped
- Get custom completion command properly to call from Lua
- Put proper command on the line when using up and down arrow keys to go through command history
## [2.0.0-rc1] - 2022-09-14
This is a pre-release version of Hilbish for testing. To see the changelog,
refer to the `Unreleased` section of the [full changelog](CHANGELOG.md)
(version 2.0.0 for future reference).
## [1.2.0] - 2022-03-17
### Added
@ -536,6 +600,8 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish.
[2.0.0-rc1]: https://github.com/Rosettea/Hilbish/compare/v1.2.0...v2.0.0-rc1
[1.2.0]: https://github.com/Rosettea/Hilbish/compare/v1.1.4...v1.2.0
[1.1.0]: https://github.com/Rosettea/Hilbish/compare/v1.0.4...v1.1.0
[1.0.4]: https://github.com/Rosettea/Hilbish/compare/v1.0.3...v1.0.4
[1.0.3]: https://github.com/Rosettea/Hilbish/compare/v1.0.2...v1.0.3

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

@ -31,7 +31,6 @@ and aims to be infinitely configurable. If something isn't, open an issue!
- [AUR](#AUR)
- [Nixpkgs](#Nixpkgs)
- [Manual Build](#Manual-Build)
- [Getting Started](#Getting-Started)
- [Contributing](#Contributing)
# Screenshots
@ -42,6 +41,10 @@ and aims to be infinitely configurable. If something isn't, open an issue!
</div>
# Installation
**NOTE:** Hilbish is not guaranteed to work properly on Windows, starting
from the 2.0 version. It will still be able to compile, but functionality
may be lacking.
## Prebuilt binaries
Go [here](https://nightly.link/Rosettea/Hilbish/workflows/build/master) for
builds on the master branch.
@ -66,6 +69,7 @@ If you're new to nix you should probably read up on how to do that [here](https:
## Manual Build
### Prerequisites
- [Go 1.17+](https://go.dev)
- [Task](https://taskfile.dev/installation/) (**Go on the hyperlink here to see Task's install method for your OS.**)
### Build
First, clone Hilbish. The recursive is required, as some Lua libraries
@ -78,30 +82,16 @@ go get -d ./...
To build, run:
```
make dev
task
```
Or, if you want a stable branch, run these commands:
```
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.
# 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'`.
After you did all that, run `sudo task install` to install Hilbish globally.
# Contributing
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/local" .PREFIX}}'
bindir__: '{{.PREFIX}}/bin'
BINDIR: '{{default .bindir__ .BINDIR}}'
libdir__: '{{.PREFIX}}/share/hilbish'
LIBDIR: '{{default .libdir__ .LIBDIR}}'
GOFLAGS: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}}"'
tasks:
default:
cmds:
- CGO_ENABLED=0 go build {{.GOFLAGS}}
vars:
GOFLAGS: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}} -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"'
build:
cmds:
- CGO_ENABLED=0 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

30
api.go
View File

@ -44,7 +44,6 @@ var exports = map[string]util.LuaExport{
"which": {hlwhich, 1, false},
}
var greeting string
var hshMod *rt.Table
var hilbishLoader = packagelib.Loader{
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
}
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, "user", rt.StringValue(username), "Username of user")
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, "interactive", rt.BoolValue(interactive), "If this is an interactive shell")
util.SetFieldProtected(fakeMod, mod, "login", rt.BoolValue(login), "Whether this is a login shell")
util.SetFieldProtected(fakeMod, mod, "greeting", rt.StringValue(greeting), "Hilbish's welcome message for interactive shells. It has Lunacolors formatting.")
util.SetFieldProtected(fakeMod, mod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
util.SetFieldProtected(fakeMod, mod, "exitCode", rt.IntValue(0), "Exit code of last exected command")
util.Document(fakeMod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
@ -195,7 +189,7 @@ func getenv(key, fallback string) string {
func setVimMode(mode string) {
util.SetField(l, hshMod, "vimMode", rt.StringValue(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)")
hooks.Em.Emit("hilbish.vimMode", mode)
hooks.Emit("hilbish.vimMode", mode)
}
func unsetVimMode() {
@ -256,21 +250,27 @@ func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// read(prompt) -> input?
// read(prompt?) -> input?
// Read input from the user, using Hilbish's line editor/input reader.
// This is a separate instance from the one Hilbish actually uses.
// Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
// --- @param prompt string
func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
luaprompt := c.Arg(0)
if typ := luaprompt.Type(); typ != rt.StringType && typ != rt.NilType {
return nil, errors.New("expected #1 to be a string")
}
luaprompt, err := c.StringArg(0)
if err != nil {
return nil, err
prompt, ok := luaprompt.TryString()
if !ok {
// if we are here and `luaprompt` is not a string, it's nil
// substitute with an empty string
prompt = ""
}
lualr := newLineReader("", true)
lualr.SetPrompt(luaprompt)
lualr := &lineReader{
rl: readline.NewInstance(),
}
lualr.SetPrompt(prompt)
input, err := lualr.Read()
if err != nil {

View File

@ -11,11 +11,77 @@ import (
rt "github.com/arnodel/golua/runtime"
)
var charEscapeMap = []string{
"\"", "\\\"",
"'", "\\'",
"`", "\\`",
" ", "\\ ",
"(", "\\(",
")", "\\)",
"[", "\\[",
"]", "\\]",
"$", "\\$",
"&", "\\&",
"*", "\\*",
">", "\\>",
"<", "\\<",
"|", "\\|",
}
var charEscapeMapInvert = invert(charEscapeMap)
var escapeReplaer = strings.NewReplacer(charEscapeMap...)
var escapeInvertReplaer = strings.NewReplacer(charEscapeMapInvert...)
func invert(m []string) []string {
newM := make([]string, len(charEscapeMap))
for i := range m {
if (i + 1) % 2 == 0 {
newM[i] = m[i - 1]
newM[i - 1] = m[i]
}
}
return newM
}
func splitForFile(str string) []string {
split := []string{}
sb := &strings.Builder{}
quoted := false
for i, r := range str {
if r == '"' {
quoted = !quoted
sb.WriteRune(r)
} else if r == ' ' && str[i - 1] == '\\' {
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) {
return matchPath(query)
q := splitForFile(ctx)
return matchPath(q[len(q) - 1])
}
func binaryComplete(query, ctx string, fields []string) ([]string, string) {
q := splitForFile(ctx)
query = q[len(q) - 1]
var completions []string
prefixes := []string{"./", "../", "/", "~/"}
@ -25,7 +91,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
if len(fileCompletions) != 0 {
for _, f := range fileCompletions {
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
if err := findExecutable(fullPath, false, true); err != nil {
if err := findExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
continue
}
completions = append(completions, f)
@ -37,7 +103,6 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
// filter out executables, but in path
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
// print dir to stderr for debugging
// search for an executable which matches our query string
if matches, err := filepath.Glob(filepath.Join(dir, query + "*")); err == nil {
// get basename from matches
@ -68,9 +133,12 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
}
func matchPath(query string) ([]string, string) {
oldQuery := query
query = strings.TrimPrefix(query, "\"")
var entries []string
var baseName string
query = escapeInvertReplaer.Replace(query)
path, _ := filepath.Abs(util.ExpandHome(filepath.Dir(query)))
if string(query) == "" {
// filepath base below would give us "."
@ -87,34 +155,21 @@ func matchPath(query string) ([]string, string) {
if file.IsDir() {
entry = entry + string(os.PathSeparator)
}
if !strings.HasPrefix(oldQuery, "\"") {
entry = escapeFilename(entry)
}
entries = append(entries, entry)
}
}
if !strings.HasPrefix(oldQuery, "\"") {
baseName = escapeFilename(baseName)
}
return entries, baseName
}
func escapeFilename(fname string) string {
args := []string{
"\"", "\\\"",
"'", "\\'",
"`", "\\`",
" ", "\\ ",
"(", "\\(",
")", "\\)",
"[", "\\[",
"]", "\\]",
"$", "\\$",
"&", "\\&",
"*", "\\*",
">", "\\>",
"<", "\\<",
"|", "\\|",
}
r := strings.NewReplacer(args...)
return r.Replace(fname)
return escapeReplaer.Replace(fname)
}
func completionLoader(rtm *rt.Runtime) *rt.Table {

View File

@ -2,5 +2,11 @@ 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
hooks(name) -> {cb, cb...} > Returns a table with hooks on the event with `name`.
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`

View File

@ -11,6 +11,9 @@ filepath.Dir
glob(pattern) > Glob all files and directories that match the pattern.
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.
readdir(dir) > Returns a table of files in `dir`

View File

@ -41,7 +41,7 @@ These will be formatted and replaced with the appropriate values.
`%u` - Name of current user
`%h` - Hostname of device
read(prompt) -> input? > Read input from the user, using Hilbish's line editor/input reader.
read(prompt?) -> input? > Read input from the user, using Hilbish's line editor/input reader.
This is a separate instance from the one Hilbish actually uses.
Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)

View File

@ -5,3 +5,5 @@
+ `hilbish.vimAction` -> actionName, args > Sent when the user does a "vim action," being something
like yanking or pasting text. See `doc vim-mode actions` for more info.
+ `hilbish.cancel` > Sent when the user cancels their input with Ctrl-C.

View File

@ -6,3 +6,8 @@ Here is the format for a doc for a hook:
`<args>` just means the arguments of the hook. If a hook doc has the format
of `arg...`, it means the hook can take/recieve any number of `arg`.
+ error -> eventName, handler, err > Emitted when there is an error in
an event handler. The `eventName` is the name of the event the handler
is for, the `handler` is the callback function, and `err` is the error
message.

View File

@ -11,6 +11,7 @@ func editorLoader(rtm *rt.Runtime) *rt.Table {
"insert": {editorInsert, 1, false},
"setVimRegister": {editorSetRegister, 1, false},
"getVimRegister": {editorGetRegister, 2, false},
"getLine": {editorGetLine, 0, false},
}
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
}
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,18 @@ function bait.catch(name, cb) end
--- @param cb function
function bait.catchOnce(name, cb) end
--- Returns a table with hooks on the event with `name`.
--- @param name string
--- @returns table
function bait.hooks(name) 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.
--- @param name string
--- @param catcher function
function bait.release(name, catcher) end
--- Throws a hook with `name` with the provided `args`
--- @param name string
--- @vararg any

View File

@ -22,6 +22,10 @@ function fs.dir() end
--- For the rules, see Go's filepath.Glob
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.
--- @param name string
--- @param recursive boolean

36
exec.go
View File

@ -85,7 +85,7 @@ func isExecError(err error) (execError, bool) {
func runInput(input string, priv bool) {
running = true
cmdString := aliases.Resolve(input)
hooks.Em.Emit("command.preexec", input, cmdString)
hooks.Emit("command.preexec", input, cmdString)
rerun:
var exitCode uint8
@ -96,7 +96,7 @@ func runInput(input string, priv bool) {
if currentRunner.Type() == rt.StringType {
switch currentRunner.AsString() {
case "hybrid":
_, _, err = handleLua(cmdString)
_, _, err = handleLua(input)
if err == nil {
cmdFinish(0, input, priv)
return
@ -108,9 +108,9 @@ func runInput(input string, priv bool) {
cmdFinish(0, input, priv)
return
}
input, exitCode, err = handleLua(cmdString)
input, exitCode, err = handleLua(input)
case "lua":
input, exitCode, err = handleLua(cmdString)
input, exitCode, err = handleLua(input)
case "sh":
input, exitCode, cont, err = handleSh(input)
}
@ -140,11 +140,11 @@ func runInput(input string, priv bool) {
if err != nil {
if exErr, ok := isExecError(err); ok {
hooks.Em.Emit("command." + exErr.typ, exErr.cmd)
err = exErr.sprint()
}
hooks.Emit("command." + exErr.typ, exErr.cmd)
} else {
fmt.Fprintln(os.Stderr, err)
}
}
cmdFinish(exitCode, input, priv)
}
@ -152,6 +152,7 @@ func reprompt(input string) (string, error) {
for {
in, err := continuePrompt(strings.TrimSuffix(input, "\\"))
if err != nil {
lr.SetPrompt(fmtPrompt(prompt))
return input, err
}
@ -194,7 +195,8 @@ func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8
return
}
func handleLua(cmdString string) (string, uint8, error) {
func handleLua(input string) (string, uint8, error) {
cmdString := aliases.Resolve(input)
// First try to load input, essentially compiling to bytecode
chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv()))
if err != nil && noexecute {
@ -220,7 +222,17 @@ func handleLua(cmdString string) (string, uint8, error) {
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)
if err != nil {
// If input is incomplete, start multiline prompting
@ -540,13 +552,9 @@ func splitInput(input string) ([]string, string) {
}
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")
// using AsValue (to convert to lua type) on an interface which is an int
// results in it being unknown in lua .... ????
// 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,43 @@
package bait
import (
"fmt"
"errors"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib/packagelib"
"github.com/chuckpreslar/emission"
)
type Bait struct{
Em *emission.Emitter
Loader packagelib.Loader
type listenerType int
const (
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 {
emitter := emission.NewEmitter()
emitter.RecoverWith(func(hookname, hookfunc interface{}, err error) {
emitter.Off(hookname, hookfunc)
fmt.Println(err)
})
b := Bait{
Em: emitter,
type Bait struct{
Loader packagelib.Loader
recoverer Recoverer
handlers map[string][]*Listener
rtm *rt.Runtime
}
// 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{
Load: b.loaderFunc,
@ -31,11 +47,153 @@ func New() Bait {
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 {
if event != "error" {
b.Emit("error", event, handle.luaCaller, err.Error())
return
}
// if there is an error in an error event handler, panic instead
// (calls the go recoverer function)
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()) {
exports := map[string]util.LuaExport{
"catch": util.LuaExport{b.bcatch, 2, false},
"catchOnce": util.LuaExport{b.bcatchOnce, 2, false},
"throw": util.LuaExport{b.bthrow, 1, true},
"release": util.LuaExport{b.brelease, 2, false},
"hooks": util.LuaExport{b.bhooks, 1, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
@ -89,7 +247,7 @@ func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
for i, v := range c.Etc() {
ifaceSlice[i] = v
}
b.Em.Emit(name, ifaceSlice...)
b.Emit(name, ifaceSlice...)
return c.Next(), nil
}
@ -104,9 +262,7 @@ func (b *Bait) bcatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
b.Em.On(name, func(args ...interface{}) {
handleHook(t, c, name, catcher, args...)
})
b.OnLua(name, catcher)
return c.Next(), nil
}
@ -121,9 +277,56 @@ func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
b.Em.Once(name, func(args ...interface{}) {
handleHook(t, c, name, catcher, args...)
})
b.OnceLua(name, catcher)
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.
// --- @param name string
// --- @param catcher function
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
}
// hooks(name) -> {cb, cb...}
// Returns a table with hooks on the event with `name`.
// --- @param name string
// --- @returns table
func (b *Bait) bhooks(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
evName, err := c.StringArg(0)
if err != nil {
return nil, err
}
noHooks := errors.New("no hooks for event " + evName)
handlers := b.handlers[evName]
if handlers == nil {
return nil, noHooks
}
luaHandlers := rt.NewTable()
for _, handler := range handlers {
if handler.typ != luaListener { continue }
luaHandlers.Set(rt.IntValue(luaHandlers.Len() + 1), rt.FunctionValue(handler.luaCaller))
}
if luaHandlers.Len() == 0 {
return nil, noHooks
}
return c.PushingNext1(t.Runtime, rt.TableValue(luaHandlers)), nil
}

View File

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

View File

@ -1,6 +1,7 @@
package fs
import (
"fmt"
"path/filepath"
"strconv"
"os"
@ -27,6 +28,7 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
"basename": util.LuaExport{fbasename, 1, false},
"dir": util.LuaExport{fdir, 1, false},
"glob": util.LuaExport{fglob, 1, false},
"join": util.LuaExport{fjoin, 0, true},
}
mod := rt.NewTable()
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
}
// 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,36 +4,84 @@ import (
"errors"
"io/fs"
"os"
"path/filepath"
"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 {
items []string
f *os.File
}
func newFileHistory() *fileHistory {
err := os.MkdirAll(defaultHistDir, 0755)
func newFileHistory(path string) *fileHistory {
dir := filepath.Dir(path)
err := os.MkdirAll(dir, 0755)
if err != nil {
panic(err)
}
data, err := os.ReadFile(defaultHistPath)
data, err := os.ReadFile(path)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
panic(err)
}
}
itms := []string{""}
lines := strings.Split(string(data), "\n")
itms := make([]string, len(lines) - 1)
for i, l := range lines {
if i == len(lines) - 1 {
continue
}
itms = append(itms, l)
itms[i] = 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 {
panic(err)
}

View File

@ -5,7 +5,13 @@ package main
import "golang.org/x/sys/windows"
func init() {
var mode uint32
windows.GetConsoleMode(windows.Stdout, &mode)
windows.SetConsoleMode(windows.Stdout, mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
// vt output (escape codes)
var outMode uint32
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.running = true
hooks.Em.Emit("job.start", rt.UserDataValue(j.ud))
hooks.Emit("job.start", rt.UserDataValue(j.ud))
return err
}
@ -82,7 +82,7 @@ func (j *job) stop() {
func (j *job) finish() {
j.running = false
hooks.Em.Emit("job.done", rt.UserDataValue(j.ud))
hooks.Emit("job.done", rt.UserDataValue(j.ud))
}
func (j *job) wait() {
@ -236,7 +236,7 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job {
jb.ud = jobUserData(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
}

@ -1 +1 @@
Subproject commit d60cd77c73875b5bb55e5a2fdc30bae01a7ac499
Subproject commit 34a57c964590f89aa065188a588c7b38aff99c28

28
lua.go
View File

@ -12,12 +12,16 @@ import (
rt "github.com/arnodel/golua/runtime"
"github.com/arnodel/golua/lib"
"github.com/arnodel/golua/lib/debuglib"
)
var minimalconf = `hilbish.prompt '& '`
func luaInit() {
l = rt.New(os.Stdout)
l.PushContext(rt.RuntimeContextDef{
MessageHandler: debuglib.Traceback,
})
lib.LoadAll(l)
lib.LoadLibs(l, hilbishLoader)
@ -28,26 +32,40 @@ func luaInit() {
lib.LoadLibs(l, fs.Loader)
lib.LoadLibs(l, terminal.Loader)
cmds := commander.New()
cmds := commander.New(l)
// 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
})
cmds.Events.On("commandDeregister", func(cmdName string) {
cmds.Events.On("commandDeregister", func(args ...interface{}) {
cmdName := args[0].(string)
delete(commands, cmdName)
})
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 `error` hook handler:", err)
hooks.Off(event, handler)
})
lib.LoadLibs(l, hooks.Loader)
// Add Ctrl-C handler
hooks.Em.On("signal.sigint", func() {
hooks.On("signal.sigint", func(...interface{}) {
if !interactive {
os.Exit(0)
}
})
lr.rl.RawInputCallback = func(r []rune) {
hooks.Emit("hilbish.rawInput", string(r))
}
// Add more paths that Lua can require from
err := util.DoString(l, "package.path = package.path .. " + requirePaths)
if err != nil {

23
main.go
View File

@ -30,7 +30,7 @@ var (
userDataDir string
curuser *user.User
hooks bait.Bait
hooks *bait.Bait
defaultConfPath string
defaultHistPath string
)
@ -116,8 +116,8 @@ func main() {
}
go handleSignals()
luaInit()
lr = newLineReader("", false)
luaInit()
// If user's config doesn't exixt,
if _, err := os.Stat(defaultConfPath); os.IsNotExist(err) && *configflag == defaultConfPath {
// Read default from current directory
@ -138,6 +138,7 @@ func main() {
} else {
runConfig(*configflag)
}
hooks.Emit("hilbish.init")
if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
scanner := bufio.NewScanner(bufio.NewReader(os.Stdin))
@ -176,15 +177,18 @@ input:
if err == io.EOF {
// Exit if user presses ^D (ctrl + d)
hooks.Em.Emit("hilbish.exit")
hooks.Emit("hilbish.exit")
break
}
if err != nil {
if err != readline.CtrlC {
if err == readline.CtrlC {
fmt.Println("^C")
hooks.Emit("hilbish.cancel")
} else {
// If we get a completely random error, print
fmt.Fprintln(os.Stderr, err)
}
fmt.Println("^C")
// TODO: Halt if any other error occurs
continue
}
var priv bool
@ -195,7 +199,7 @@ input:
input = strings.TrimSpace(input)
if len(input) == 0 {
running = true
hooks.Em.Emit("command.exit", 0)
hooks.Emit("command.exit", 0)
continue
}
@ -226,7 +230,7 @@ input:
}
func continuePrompt(prev string) (string, error) {
hooks.Em.Emit("multiline", nil)
hooks.Emit("multiline", nil)
lr.SetPrompt(multilinePrompt)
cont, err := lr.Read()
if err != nil {
@ -268,11 +272,6 @@ func fmtPrompt(prompt string) string {
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 {
all := make(map[string]bool)
newSlice := []string{}

View File

@ -0,0 +1,25 @@
local commander = require 'commander'
local fs = require 'fs'
commander.register('cat', function(args)
local exit = 0
if #args == 0 then
print [[
usage: cat [file]...]]
end
for _, fName in ipairs(args) do
local f = io.open(fName)
if f == nil then
exit = 1
print(string.format('cat: %s: no such file or directory', fName))
goto continue
end
io.write(f:read '*a')
::continue::
end
io.flush()
return exit
end)

View File

@ -0,0 +1,7 @@
local ansikit = require 'ansikit'
local commander = require 'commander'
commander.register('clear', function()
ansikit.clear(true)
ansikit.cursorTo(0, 0)
end)

View File

@ -0,0 +1,5 @@
local commander = require 'commander'
commander.register('exec', function(args)
hilbish.exec(args[1])
end)

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
require 'nature.commands.cd'
require 'nature.commands.cdr'
require 'nature.commands.doc'
require 'nature.commands.exit'
require 'nature.commands.guide'
require 'nature.commands.disown'
require 'nature.commands.fg'
require 'nature.commands.bg'
local fs = require 'fs'
-- explanation: this specific function gives to us info about
-- the currently running source. this includes a path to the
-- source file (info.source)
-- we will use that to automatically load all commands by reading
-- all the files in this dir and just requiring it.
local info = debug.getinfo(1)
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

@ -24,7 +24,7 @@ function hilbish.completion.handler(line, pos)
return {compGroup}, pfx
else
local ok, compGroups, pfx = pcall(hilbish.completion.call,
'command.' .. #fields[1], query, ctx, fields)
'command.' .. fields[1], query, ctx, fields)
if ok then
return compGroups, pfx
end

View File

@ -1,5 +1,6 @@
-- Prelude initializes everything else for our shell
local _ = require 'succulent' -- Function additions
local bait = require 'bait'
local fs = require 'fs'
package.path = package.path .. ';' .. hilbish.dataDir .. '/?/init.lua'
@ -28,7 +29,9 @@ do
return got_virt
end
if type(key) == 'string' then
virt_G[key] = os.getenv(key)
end
return virt_G[key]
end,
@ -54,7 +57,6 @@ do
if ok then
for _, module in ipairs(modules) do
local entry = package.searchpath(module, startSearchPath)
print(entry)
if entry then
dofile(entry)
end
@ -63,3 +65,15 @@ do
package.path = package.path .. ';' .. startSearchPath
end
bait.catch('error', function(event, handler, err)
bait.release(event, handler)
end)
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)

View File

@ -1,13 +1,8 @@
local fs = require 'fs'
function cdHandle(inp)
local res = hilbish.runner.lua(inp)
if not res.err then
return res
end
res = hilbish.runner.sh(inp)
local oldShRunner = hilbish.runner.sh
function hilbish.runner.sh(input)
local res = oldShRunner(input)
if res.exit ~= 0 and hilbish.opts.autocd then
local ok, stat = pcall(fs.stat, res.input)
@ -21,5 +16,3 @@ function cdHandle(inp)
return res
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,6 @@
local bait = require 'bait'
bait.catch('command.exit', function(_, cmd, priv)
if not cmd then return end
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
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

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

@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
"strings"
"github.com/rivo/uniseg"
)
// initGrid - Grid display details. Called each time we want to be sure to have
@ -13,8 +14,8 @@ func (g *CompletionGroup) initGrid(rl *Instance) {
// Compute size of each completion item box
tcMaxLength := 1
for i := range g.Suggestions {
if len(g.Suggestions[i]) > tcMaxLength {
tcMaxLength = len([]rune(g.Suggestions[i]))
if uniseg.GraphemeClusterCount(g.Suggestions[i]) > tcMaxLength {
tcMaxLength = uniseg.GraphemeClusterCount(g.Suggestions[i])
}
}
@ -99,11 +100,11 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
// If group title, print it and adjust offset.
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++
}
cellWidth := strconv.Itoa((GetTermWidth() / g.tcMaxX) - 2)
cellWidth := strconv.Itoa((GetTermWidth() / g.tcMaxX) - 4)
x := 0
y := 1
@ -124,7 +125,15 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
comp += seqInvert
}
comp += fmt.Sprintf("%-"+cellWidth+"s %s", g.Suggestions[i], seqReset)
sugg := g.Suggestions[i]
if len(sugg) > GetTermWidth() {
sugg = sugg[:GetTermWidth() - 4] + "..."
}
formatStr := "%-"+cellWidth+"s%s "
if g.tcMaxX == 1 {
formatStr = "%s%s"
}
comp += fmt.Sprintf(formatStr, fmtEscape(sugg), seqReset)
}
// Always add a newline to the group if the end if not punctuated with one

View File

@ -1,5 +1,7 @@
package readline
import "strings"
// CompletionGroup - A group/category of items offered to completion, with its own
// name, descriptions and completion display format/type.
// The output, if there are multiple groups available for a given completion input,
@ -285,3 +287,7 @@ func (g *CompletionGroup) goLastCell() {
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 {
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, ok := g.Aliases[item]
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, make an empty cell
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 != "" {
// 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++
}
@ -126,7 +126,7 @@ func (g *CompletionGroup) writeMap(rl *Instance) (comp string) {
}
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

View File

@ -123,23 +123,20 @@ func (rl *Instance) walkHistory(i int) {
// When we are exiting the current line buffer to move around
// the history, we make buffer the current line
if rl.histPos == 0 && (rl.histPos+i) == 1 {
if rl.histOffset == 0 && rl.histOffset + i == 1 {
rl.lineBuf = string(rl.line)
}
switch rl.histPos + i {
case 0, history.Len() + 1:
rl.histPos = 0
rl.histOffset += i
if rl.histOffset == 0 {
rl.line = []rune(rl.lineBuf)
rl.pos = len(rl.lineBuf)
return
case -1:
rl.histPos = 0
rl.lineBuf = string(rl.line)
default:
} else if rl.histOffset <= -1 {
rl.histOffset = 0
} else {
dedup = true
old = string(rl.line)
new, err = history.GetLine(history.Len() - rl.histPos - 1)
new, err = history.GetLine(history.Len() - rl.histOffset)
if err != nil {
rl.resetHelpers()
print("\r\n" + err.Error() + "\r\n")
@ -148,7 +145,6 @@ func (rl *Instance) walkHistory(i int) {
}
rl.clearLine()
rl.histPos += i
rl.line = []rune(new)
rl.pos = len(rl.line)
if rl.pos > 0 {

View File

@ -134,6 +134,7 @@ type Instance struct {
// history operating params
lineBuf string
histPos int
histOffset int
histNavIdx int // Used for quick history navigation.
//
@ -198,6 +199,8 @@ type Instance struct {
ViModeCallback func(ViMode)
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.

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,
// 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 {
return rl.lineComp
}

View File

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

View File

@ -49,7 +49,7 @@ func (rl *Instance) Readline() (string, error) {
// History Init
// We need this set to the last command, so that we can access it quickly
rl.histPos = 0
rl.histOffset = 0
rl.viUndoHistory = []undoItem{{line: "", pos: 0}}
// Multisplit
@ -94,6 +94,9 @@ func (rl *Instance) Readline() (string, error) {
rl.skipStdinRead = false
r := []rune(string(b))
if rl.RawInputCallback != nil {
rl.RawInputCallback(r[:i])
}
if isMultiline(r[:i]) || len(rl.multiline) > 0 {
rl.multiline = append(rl.multiline, b[:i]...)
@ -235,7 +238,9 @@ func (rl *Instance) Readline() (string, error) {
// Normal completion search does only refresh the search pattern and the comps
if rl.modeTabFind || rl.modeAutoFind {
rl.resetVirtualComp(false)
rl.backspaceTabFind()
rl.renderHelpers()
rl.viUndoSkipAppend = true
} else {
// Always cancel any virtual completion
@ -328,6 +333,8 @@ func (rl *Instance) Readline() (string, error) {
rl.modeTabFind = true
rl.updateTabFind([]rune{})
rl.updateVirtualComp()
rl.renderHelpers()
rl.viUndoSkipAppend = true
// Tab Completion & Completion Search ---------------------------------------------------------------
@ -481,7 +488,10 @@ func (rl *Instance) Readline() (string, error) {
if string(r[:i]) != seqShiftTab &&
string(r[:i]) != seqForwards && string(r[:i]) != seqBackwards &&
string(r[:i]) != seqUp && string(r[:i]) != seqDown {
rl.resetVirtualComp(false)
// basically only applies except on 1st ctrl r open
// so if we have not explicitly selected something
// (tabCompletionSelect is false) drop virtual completion
rl.resetVirtualComp(!rl.tabCompletionSelect)
}
}
@ -514,7 +524,9 @@ func (rl *Instance) Readline() (string, error) {
if rl.modeAutoFind || rl.modeTabFind {
rl.resetVirtualComp(false)
rl.updateTabFind(r[:i])
rl.renderHelpers()
rl.viUndoSkipAppend = true
continue
} else {
rl.resetVirtualComp(false)
rl.editorInput(r[:i])
@ -534,6 +546,10 @@ func (rl *Instance) Readline() (string, error) {
// entry readline is currently configured for and then update the line entries
// accordingly.
func (rl *Instance) editorInput(r []rune) {
if len(r) == 0 {
return
}
switch rl.modeViMode {
case VimKeys:
rl.vi(r[0])
@ -601,6 +617,7 @@ func (rl *Instance) escapeSeq(r []rune) {
case string(charEscape):
switch {
case rl.modeAutoFind:
rl.resetVirtualComp(true)
rl.resetTabFind()
rl.clearHelpers()
rl.resetTabCompletion()
@ -608,6 +625,7 @@ func (rl *Instance) escapeSeq(r []rune) {
rl.renderHelpers()
case rl.modeTabFind:
rl.resetVirtualComp(true)
rl.resetTabFind()
rl.resetTabCompletion()

View File

@ -2,6 +2,7 @@ package readline
import (
"strings"
"github.com/rivo/uniseg"
)
// insertCandidateVirtual - When a completion candidate is selected, we insert it virtually in the input line:
@ -249,10 +250,10 @@ func (rl *Instance) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, i
return
case pos >= len(word)-1:
word = rTrimWhiteSpace(split[index+1])
adjust = len(split[index]) - pos
adjust += len(word) - 1
adjust = uniseg.GraphemeClusterCount(split[index]) - pos
adjust += uniseg.GraphemeClusterCount(word) - 1
default:
adjust = len(word) - pos - 1
adjust = uniseg.GraphemeClusterCount(word) - pos - 1
}
return
}

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.
newLine := rl.DelayedSyntaxWorker(rl.getLine())
newLine := rl.DelayedSyntaxWorker(rl.GetLine())
var sLine string
count := atomic.LoadInt64(&rl.delayedSyntaxCount)
if count != i {

View File

@ -1,6 +1,10 @@
package readline
import "golang.org/x/text/width"
import (
"strings"
"golang.org/x/text/width"
)
// updateHelpers is a key part of the whole refresh process:
// it should coordinate reprinting the input line, any Infos and completions
@ -52,19 +56,19 @@ func (rl *Instance) updateReferences() {
rl.posY = 0
rl.fullY = 0
var fullLine, cPosLine int
var curLine []rune
if len(rl.currentComp) > 0 {
fullLine = getWidth(rl.lineComp)
cPosLine = getWidth(rl.lineComp[:rl.pos])
curLine = rl.lineComp
} else {
fullLine = getWidth(rl.line)
cPosLine = getWidth(rl.line[:rl.pos])
curLine = rl.line
}
fullLine := getWidth(curLine)
cPosLine := getWidth(curLine[:rl.pos])
// We need the X offset of the whole line
toEndLine := rl.promptLen + fullLine
fullOffset := toEndLine / GetTermWidth()
rl.fullY = fullOffset
rl.fullY = fullOffset + strings.Count(string(curLine), "\n")
fullRest := toEndLine % GetTermWidth()
rl.fullX = fullRest

View File

@ -245,7 +245,7 @@ func (rl *Instance) vi(r rune) {
}
// Keep the previous cursor position
prev := rl.pos
//prev := rl.pos
new, err := rl.StartEditorWithBuffer(multiline, "")
if err != nil || len(new) == 0 || string(new) == string(multiline) {
@ -257,11 +257,11 @@ func (rl *Instance) vi(r rune) {
// Clean the shell and put the new buffer, with adjusted pos if needed.
rl.clearLine()
rl.line = new
if prev > len(rl.line) {
rl.pos = len(rl.line) - 1
rl.pos = len(rl.line)
/*if prev > len(rl.line) {
} else {
rl.pos = prev
}
}*/
case 'w':
// If we were not yanking

32
rl.go
View File

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

View File

@ -28,13 +28,13 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
input, exitCode, cont, err := handleSh(cmd)
_, exitCode, cont, err := execSh(aliases.Resolve(cmd))
var luaErr rt.Value = rt.NilValue
if err != nil {
luaErr = rt.StringValue(err.Error())
}
runnerRet := rt.NewTable()
runnerRet.Set(rt.StringValue("input"), rt.StringValue(input))
runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd))
runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode)))
runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont))
runnerRet.Set(rt.StringValue("err"), luaErr)

View File

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

View File

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

View File

@ -11,7 +11,7 @@ var (
// Version info
var (
ver = "v2.0.0"
ver = "v2.0.0-rc1"
releaseName = "Hibiscus"
gitCommit string
gitBranch string

View File

@ -14,7 +14,7 @@ var (
.. hilbish.userDir.config .. '/hilbish/?/init.lua;'
.. hilbish.userDir.config .. '/hilbish/?/?.lua;'
.. hilbish.userDir.config .. '/hilbish/?.lua'`
dataDir = "/usr/share/hilbish"
dataDir = "/usr/local/share/hilbish"
preloadPath = dataDir + "/nature/init.lua"
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
defaultConfDir = ""

View File

@ -2,14 +2,16 @@
package main
import "hilbish/util"
// String vars that are free to be changed at compile time
var (
requirePaths = commonRequirePaths + `.. ';'
.. hilbish.userDir.config .. '\\Hilbish\\libs\\?\\init.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"
sampleConfPath = dataDir + "\\hilbishrc.lua" // Path to default/sample config
sampleConfPath = dataDir + "\\.hilbishrc.lua" // Path to default/sample config
defaultConfDir = ""
)

View File

View File

@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

View File

@ -0,0 +1,25 @@
baseURL = 'https://rosettea.github.io/Hilbish/'
languageCode = 'en-us'
title = 'Hilbish'
theme = 'hsh'
enableGitInfo = true
[menu]
[[menu.nav]]
identifier = 'home'
name = 'Home'
pageref = '/'
weight = 1
[[menu.nav]]
identifier = 'install'
name = 'Install'
pageref = '/install'
weight = 2
[[menu.nav]]
identifier = 'docs'
name = 'Docs'
pageref = '/docs'
weight = 3
[markup.goldmark.renderer]
unsafe = true

View File

@ -0,0 +1,134 @@
---
description: 'Something Unique. Hilbish is the new interactive shell for Lua fans. Extensible, scriptable, configurable: All in Lua.'
---
[//]: <>
<!-- hugo (prob goldmark) is funny; the html wont work if its the first thing -->
<div class="text-center">
<h1 class="fw-light">Something Unique.</h1>
<p>
<strong>Hilbish</strong> is the new interactive shell for Lua fans.<br>
Extensible, scriptable, configurable: All in Lua.
</p>
<a href="install" class="btn btn-primary">Install</a>
<a href="https://github.com/Rosettea/Hilbish" class="btn btn-secondary" target="_blank">Github</a>
</div>
<hr>
<div class="row row-cols-1 row-cols-md-2 g-4">
<div class="col">
<div class="card border-light mb-3">
<div class="row g-0">
<div class="col-md-4">
<a href="https://safe.kashima.moe/6njmopm47u1x.png">
<img src="https://safe.kashima.moe/6njmopm47u1x.png" class="img-fluid rounded-start">
</a>
</div>
<div class="col-md-8">
<h5 class="card-header">Simple and Easy Scripting</h5>
<div class="card-body">
<p class="card-text">
Hilbish is configured and scripted in the Lua programming language.
This removes all the old, ugly things about Shell script and introduces
everything good about Lua, including other languages (Moonscript & Fennel).
</p>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card border-light mb-3">
<div class="row g-0">
<div class="col-md-4">
<a href="https://safe.kashima.moe/jkndbi636lzj.png">
<img src="https://safe.kashima.moe/jkndbi636lzj.png" class="img-fluid rounded-start">
</a>
</div>
<div class="col-md-8">
<h5 class="card-header">History and Completion Menus</h5>
<div class="card-body">
<p class="card-text">
Hilbish provides the user with proper menus for completions,
history searching. Want to see your previous commands? Hit Ctrl-R.
</p>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card border-light mb-3">
<div class="row g-0">
<div class="col-md-4">
<a href="https://safe.kashima.moe/6yfeooamzro4.png">
<img src="https://safe.kashima.moe/6yfeooamzro4.png" class="img-fluid rounded-start">
</a>
</div>
<div class="col-md-8">
<h5 class="card-header">Tons of Features, and More to Come</h5>
<div class="card-body">
<p class="card-text">
Hilbish offers a bunch of features to make your interactive
shell experience rich. Things like syntax highlighting and hinting
available via the Lua API.
</p>
<p class="card-small text-muted">* Command hints shown in photo are not default.</p>
</div>
</div>
</div>
</div>
</div>
<!-- uncomment, replace top when editor interface can be replaced (and replace the images) -->
<!--
<div class="col">
<div class="card border-light mb-3">
<div class="row g-0">
<div class="col-md-4">
<a href="https://safe.kashima.moe/6yfeooamzro4.png">
<img src="https://safe.kashima.moe/6yfeooamzro4.png" class="img-fluid rounded-start">
</a>
</div>
<div class="col-md-8">
<h5 class="card-header">Highly Extensible</h5>
<div class="card-body">
<p class="card-text">
Hilbish can be turned into an all new shell if wanted. One of our
main goals is that most (if not all) interfaces can be replaced.
</p>
</div>
</div>
</div>
</div>
</div>
-->
</div>
<hr>
<h1 class="fw-light">Why not just Lua?</h1>
<p>
Hilbish is your interactive shell as well as a just a Lua interpreter
and enhanced REPL.<br>
</p>
<ul class="list-group" style="max-width: 64em;">
<li class="list-group-item"><i class="fa-solid fa-battery-full"></i> Batteries included Lua runtime that's also your user shell!</li>
<li class="list-group-item"><i class="fa-solid fa-network-wired"></i> Hilbish is easily cross platform. It has OS agnostic interfaces for easy cross platform Lua code.</li>
</ul>
<hr>
<h1 class="fw-light">Try It Today!</h1>
<p>
Hilbish is known to run on the 3 major platforms (Windows, MacOS, Linux)
but likely builds on other Unixes!
<br>
Windows doesn't work as well as it should, so if you're a Windows user,
<a href="https://github.com/Rosettea/Hilbish/discussions/165">say something</a>!
<ul class="list-group" style="max-width: 64em;">
<li class="list-group-item"><i class="fa-solid fa-cloud-arrow-down"></i> <a href="/Hilbish/install" style="text-decoration: none;"><strong>Download</strong></a> the binary</li>
<li class="list-group-item"><i class="fa-solid fa-screwdriver-wrench"></i> <a href="https://github.com/Rosettea/Hilbish#manual-build" style="text-decoration: none;"><strong>Build</strong></a> from source</li>
</ul>
</p>

View File

@ -0,0 +1,19 @@
---
title: Introduction
layout: doc
weight: -1
menu: docs
---
Here lies the documentation for Hilbish, the hyper extensible Lua shell.
Hilbish provides you with a few quality of life features and useful
functions to ensure you can make the shell fully yours.
These features include:
- Completion and history search menus
- Hinting and syntax highlighting (scripted by user)
# Installation
Steps on installing Hilbish will be at the Install page in the navigation bar
at the top. This also included getting development builds from the GitHub
repository.

View File

@ -0,0 +1,25 @@
---
title: Frequently Asked Questions
layout: doc
weight: -20
menu: docs
---
# Is Hilbish POSIX compliant?
No, it is not. POSIX compliance is a non-goal. Perhaps in the future,
someone would be able to write a native plugin to support shell scripting
(which would be against it's main goal, but ....)
# Windows Support?
It compiles for Windows (CI ensures it does), but otherwise it is not
directly supported. If you'd like to improve this situation,
checkout [the discussion](https://github.com/Rosettea/Hilbish/discussions/165).
# Where is the API documentation?
The builtin `doc` command supplies all documentation of Hilbish provided
APIs. This will be on the website in the near future.
# Why?
Hilbish emerged from the desire of a Lua configured shell.
It was the initial reason that it was created, but now it's more:
to be hyper extensible, simpler and more user friendly.

View File

@ -0,0 +1,11 @@
---
title: Features
layout: doc
weight: -40
menu: docs
---
Hilbish has a wide range of features to enhance the user's experience and
is always adding new ones. If there is something missing here or something
you would like to see, please [start a discussion](https://github.com/Rosettea/Hilbish/discussions)
or comment on any existing ones which match your request.

View File

@ -0,0 +1,17 @@
---
title: Runner Mode
description: Customize the interactive script/command runner.
layout: doc
menu:
docs:
parent: "Features"
---
Hilbish allows you to change how interactive text can be interpreted.
This is mainly due to the fact that the default method Hilbish uses
is that it runs Lua first and then falls back to shell script.
In some cases, someone might want to switch to just shell script to avoid
it while interactive but still have a Lua config, or go full Lua to use
Hilbish as a REPL. This also allows users to add alternative languages,
instead of either like Fennel.

View File

@ -0,0 +1,59 @@
---
title: Getting Started
layout: doc
weight: -10
menu: docs
---
To start Hilbish, open a terminal. If Hilbish has been installed and is not the
default shell, you can simply run `hilbish` to start it. This will launch
a normal interactive session.
To exit, you can either run the `exit` command or hit Ctrl+D.
# Setting as Default
## Login shell
There are a few ways to make Hilbish your default shell. A simple way is
to make it your user/login shell.
{{< warning `It is not recommended to set Hilbish as your login shell. That is expected to be a
POSIX compliant shell, which Hilbish is not. At most, there will just be a
few variables missing in your environment` >}}
To do that, simply run `chsh -s /usr/bin/hilbish`.
Some distros (namely Fedora) might have `lchsh` instead, which is used like `lchsh <user>`.
When prompted, you can put the path for Hilbish.
## Default with terminal
The simpler way is to set the default shell for your terminal. The way of
doing this depends on how your terminal settings are configured.
## Run after login shell
Some shells (like zsh) have an rc file, like `.zlogin`, which is ran when the shell session
is a login shell. In that file, you can run Hilbish. Example:
```
exec hilbish -S -l
```
This will replace the shell with Hilbish, set $SHELL to Hilbish and launch it as a login shell.
# Configuration
Once installation and setup has been done, you can then configure Hilbish.
It is configured and scripted via Lua, so the config file is a Lua file.
You can use any pure Lua library to do whatever you want.
Hilbish's sample configuration is usually located in `hilbish.dataDir .. '/.hilbishrc.lua'`.
You can print that path via Lua to see what it is: `print(hilbish.dataDir .. '/.hilbishrc.lua')`.
As an example, it will usually will result in `/usr/share/hilbish/.hilbishrc.lua` on Linux.
To edit your user configuration, you can copy that file to `hilbish.userDir.config .. '/hilbish/init.lua'`,
which follows XDG on Linux and MacOS, and is located in %APPDATA% on Windows.
As the directory is usually `~/.config` on Linux, you can run this command to copy it:
`cp /usr/share/hilbish/.hilbishrc.lua ~/.config/hilbish/init.lua`
Now you can get to editing it. Since it's just a Lua file, having basic
knowledge of Lua would help. All of Lua's standard libraries and functions
from Lua 5.4 are available. Hilbish has some custom and modules that are
available. To see them, you can run the `doc` command. This also works as
general documentation for other things.

View File

@ -0,0 +1,38 @@
---
title: Install
description: Steps on how to install Hilbish on all the OSes and distros supported.
layout: page
---
## Official Binaries
The best way to get Hilbish is to get a build directly from GitHub.
At any time, there are 2 versions of Hilbish recommended for download:
the latest stable release, and development builds from the master branch.
You can download both at any time, but note that the development builds may
have breaking changes.
For the latest **stable release**, check here: https://github.com/Rosettea/Hilbish/releases/latest
For a **development build**: https://nightly.link/Rosettea/Hilbish/workflows/build/master
## Package Repositories
### Arch Linux (AUR)
Hilbish is on the AUR. Setup an AUR helper, and install.
Example with yay:
```
yay -S hilbish
```
Or, from master branch:
```
yay -S hilbish-git
```
### Alpine Linux
Hilbish is currentlty in the testing/edge repository for Alpine.
Follow the steps [here](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository)
(Using testing repositories) and install:
```
apk add hilbish
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Rosettea
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,2 @@
+++
+++

View File

@ -0,0 +1,7 @@
{{ define "main"}}
<main id="main">
<div>
<h1><a href="{{ "/" | relURL }}">Go Home</a></h1>
</div>
</main>
{{ end }}

View File

@ -0,0 +1,6 @@
<h{{ (add .Level 1) }} id="{{ .Anchor | safeURL }}">
{{ .Text | safeHTML }}
</h{{ (add .Level 1) }}>
{{ if eq .Text ""}}
<hr>
{{ end }}

View File

@ -0,0 +1,4 @@
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if eq (substr .Destination 0 4) "http" }} target="_blank" rel="noopener"{{ end }}>
{{ .Text | safeHTML }}
</a>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body class="d-flex flex-column min-vh-100" style="overflow-x: hidden;">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
</symbol>
<symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
</symbol>
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</symbol>
</svg>
{{- partial "header.html" . -}}
{{- block "main" . }}{{- end }}
{{- partial "footer.html" . -}}
</body>
</html>

View File

@ -0,0 +1,53 @@
{{ define "main" }}
<div class="container py-3 row">
<div class="container" style="width: 240px;">
<div class="p-3 col">
<ul class="nav nav-pills mb-auto">
{{ $currentPage := . }}
{{ range .Site.Menus.docs.ByWeight.Reverse }}
<li class="nav-item">
<a href="{{ .URL }}" class="nav-link">
<strong>{{ .Title }}</strong>
</a>
</li>
{{ if .Children }}
<ul style="list-style: none;">
{{ range .Children }}
<li class="nav-item">
<a href="{{ .URL }}" class="nav-link">
{{ .Title }}
</a>
</li>
{{ end }}
</ul>
{{ end }}
{{ end }}
</ul>
</div>
</div>
<div class="p-3 col">
<div>
<h1>{{ .Title }}</h1>
<p><em>
{{ $date := .Date.UTC.Format "Jan 2, 2006" }}
{{ $lastmod := .Lastmod.UTC.Format "Jan 2, 2006" }}
{{ if and (ne $lastmod $date) (gt .Lastmod .Date) }}
Last updated {{ $lastmod }}<br>
{{ end }}
{{ if .Description }}
{{ .Description }}<br>
{{ end}}
</em></p>
{{.Content}}
</div>
<div class="footer mt-auto">
<p class="card-small text-muted">
Want to help improve this page? <a href="https://github.com/Rosettea/Hilbish/issues/new/choose">Create an issue.</a>
</p>
</div>
</div>
</div>
{{ end }}

View File

@ -0,0 +1,7 @@
{{ define "main" }}
<main>
<div class="container mt-2">
{{.Content}}
</div>
</main>
{{ end }}

View File

@ -0,0 +1,8 @@
{{ define "main" }}
<main>
<div class="container mt-2">
<h1>{{ .Title }}</h1>
{{.Content}}
</div>
</main>
{{ end }}

View File

@ -0,0 +1,6 @@
{{ define "main" }}
<main style="max-width: 80em; margin: auto;">
{{.Content}}
</main>
{{ end }}

View File

@ -0,0 +1,32 @@
<footer class="footer mt-auto mt-auto py-3 bg-light row">
<div class="col mb-3">
</div>
<div class="col mb-3">
<a href="/Hilbish" class="d-flex align-items-center mb-3 link-dark text-decoration-none">
<img src="/Hilbish/hilbish-flower.png" alt="" height="48" class="d-inline-block align-text-top">
</a>
<p class="text-muted">
Rosettea &copy; 2022
<br>
Made with <i class="fa-solid fa-heart" style="color: #f6345b;"></i>
</p>
</div>
<div class="col mb-3"></div>
<div class="col mb-3"></div>
<div class="col mb-3"></div>
<div class="col mb-3">
<h5>Hilbish</h5>
<ul class="nav flex-column">
<li class="nav-item mb-2"><a href="/Hilbish" class="nav-link p-0 text-muted">Home</a></li>
<li class="nav-item mb-2"><a href="/Hilbish/docs/faq" class="nav-link p-0 text-muted">FAQ</a></li>
<li class="nav-item mb-2"><a href="https://github.com/Rosettea/Hilbish" class="nav-link p-0 text-muted">Source</a></li>
<li class="nav-item mb-2"><a href="https://github.com/Rosettea/Hilbish/releases" class="nav-link p-0 text-muted">Releases</a></li>
<li class="nav-item mb-2"><a href="/Hilbish/docs" class="nav-link p-0 text-muted">Documentation</a></li>
</ul>
</div>
<div class="col mb-3"></div>
</footer>

View File

@ -0,0 +1,26 @@
<head>
{{ $title := print .Title " — " .Site.Title }}
{{ if .IsHome }}{{ $title = .Site.Title }}{{ end }}
<title>{{ $title }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"/>
<meta name="theme-color" content="#ff89dd">
<meta content="/Hilbish/hilbish-flower.png" property="og:image" />
<meta property="og:site_name" content="Hilbish" />
<meta content="{{ $title }}" property="og:title" />
<meta content="{{if .Description}}{{ .Description }}{{ else }}{{ .Summary }}{{ end }}" property="og:description" />
<meta content="{{if .Description}}{{ .Description }}{{ else }}{{ .Summary }}{{ end }}" name="description" />
<meta name="revisit-after" content="2 days">
<meta name="keywords" content="Lua, Hilbish, Linux, Shell">
<meta property="og:locale" content="en_GB" />
<link rel="canonical" href="https://rosettea.github.io/Hilbish/" />
<meta property="og:url" content="https://rosettea.github.io/Hilbish/" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>

View File

@ -0,0 +1,25 @@
<header>
<nav class="navbar navbar-expand-md sticky-top bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="/Hilbish">
<img src="/Hilbish/hilbish-flower.png" alt="" height="24" class="d-inline-block align-text-top">
Hilbish
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
{{ $currentPage := . }}
{{ range .Site.Menus.nav }}
<li class="nav-item">
<a href="{{ .URL }}" class="nav-link {{ if $currentPage.IsMenuCurrent "nav" . }}active{{ end }}">
{{ .Name }}
</a>
</li>
{{ end }}
</ul>
</div>
</div>
</nav>
</header>

View File

@ -0,0 +1,6 @@
<div class="alert alert-warning d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Warning:"><use xlink:href="#exclamation-triangle-fill"/></svg>
<div>
{{ .Get 0 }}
</div>
</div>

View File

@ -0,0 +1,21 @@
# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example
name = "Hsh"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.41.0"
[author]
name = ""
homepage = ""
# If porting an existing theme
[original]
name = ""
homepage = ""
repo = ""