mirror of
https://github.com/Hilbis/Hilbish
synced 2025-06-30 16:22:03 +00:00
Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
eb630b2de0 | |||
548de98551 | |||
49f2bae9e1 | |||
5ca858d112 | |||
1bb433dc64 | |||
aec17d1077 | |||
049b517b88 | |||
35272ce1ac | |||
|
0036932693 | ||
e676c095c2 | |||
ef4c925e37 | |||
80d72a29bb | |||
0854b5a769 | |||
2ca99fe831 | |||
8793a733a6 | |||
6827940466 | |||
fde615ff3f | |||
d002c82271 | |||
f64229b52c | |||
|
3d5766ac33 | ||
60edfc00ee | |||
6cd294373c | |||
02c89b99dd | |||
fe4e972fbe | |||
e4a9e06d2a | |||
487b5fa4f8 | |||
|
946e8e7228 | ||
04206b6a14 | |||
9ea2a2f332 | |||
|
364cb1ca2e | ||
|
2ba878713c | ||
2678ec723e | |||
dbf2d80863 | |||
fab98bc613 | |||
|
7b16cde540 | ||
4150001d8b | |||
32ed0d2348 | |||
8731b1a7d1 | |||
4743222044 | |||
14a600f922 | |||
13e6d180f8 | |||
|
836f941e16 | ||
a02cd1d7ef | |||
c969f5ed15 | |||
|
36ce05e85a | ||
ac7c97442e | |||
7fc3f4a569 | |||
dbb45a1947 | |||
3da150bb64 | |||
46968e632b | |||
|
1e01580d8f | ||
edbc758c67 | |||
|
824f5bd06d | ||
a7ba2fdf1a | |||
e6b88816fd | |||
db851cf4f8 | |||
fc6a9a33e1 | |||
0582fbd30c | |||
137efe5c62 | |||
72324c27de | |||
ea233facc8 | |||
19feda919e | |||
e4df61f020 | |||
54b85b1c99 | |||
ddf5117fd9 | |||
5b46158008 | |||
a41a5504f4 | |||
|
35a8d0eaa4 | ||
8a1614ab59 | |||
cc43cb2d6e | |||
1ba0dd183c | |||
44d63a398a | |||
92448eec67 | |||
4e882b376b | |||
5f8d942f0a | |||
41e5e3f789 | |||
826b0789cb | |||
bfa3b55542 | |||
f7e66bb957 | |||
d7ab887234 | |||
|
b24fc4a422 | ||
0e4b95d9b9 | |||
38d036d96f | |||
4c61c551aa | |||
ff6e08902f | |||
bbd5637e9b | |||
|
d46c079afb | ||
42ab856e45 | |||
|
478e3020b1 | ||
a20123fc24 | |||
fb9d30520a | |||
521298733e | |||
f8a391e24f | |||
|
aa376f9b14 | ||
16b39fe157 | |||
40c3cecabb | |||
ce99209f0d | |||
d1dbb84c16 | |||
5b84718021 | |||
3d727e6411 | |||
7d487bfc98 | |||
11fc1edd94 | |||
b416264138 | |||
11323a70aa | |||
db32fd7a87 | |||
017702ea0d |
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@ -1,8 +1,12 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -19,18 +23,18 @@ jobs:
|
||||
goos: windows
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.18.8'
|
||||
go-version: '1.22.2'
|
||||
- name: Download Task
|
||||
run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d'
|
||||
- name: Build
|
||||
run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} ./bin/task
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: matrix.goos == 'windows'
|
||||
with:
|
||||
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
@ -44,7 +48,7 @@ jobs:
|
||||
libs
|
||||
docs
|
||||
emmyLuaDocs
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: matrix.goos != 'windows'
|
||||
with:
|
||||
name: hilbish-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
15
.github/workflows/docs.yml
vendored
15
.github/workflows/docs.yml
vendored
@ -9,10 +9,19 @@ jobs:
|
||||
gen:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v2
|
||||
- name: Run docgen
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v5
|
||||
- name: Download Task
|
||||
run: 'sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d'
|
||||
- name: Build
|
||||
run: ./bin/task
|
||||
- name: Run docgen (go-written)
|
||||
run: go run cmd/docgen/docgen.go
|
||||
- name: Run docgen (lua-written)
|
||||
run: ./hilbish cmd/docgen/docgen.lua
|
||||
- name: Commit new docs
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: taiki-e/create-gh-release-action@v1
|
||||
with:
|
||||
title: Hilbish $tag
|
||||
@ -30,7 +30,7 @@ jobs:
|
||||
- goarch: arm64
|
||||
goos: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
23
.github/workflows/website.yml
vendored
23
.github/workflows/website.yml
vendored
@ -1,27 +1,34 @@
|
||||
name: Build website
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- v[0-9]+.*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Hugo
|
||||
uses: peaceiris/actions-hugo@v2
|
||||
uses: peaceiris/actions-hugo@v3
|
||||
with:
|
||||
hugo-version: 'latest'
|
||||
hugo-version: '0.111.3'
|
||||
extended: true
|
||||
|
||||
- name: Set branch name
|
||||
id: branch
|
||||
run: echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> "$GITHUB_ENV"
|
||||
run: echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/*/}}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Fix base URL
|
||||
if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea'
|
||||
@ -32,14 +39,14 @@ jobs:
|
||||
|
||||
- name: Deploy
|
||||
if: env.BRANCH_NAME == 'master' && github.repository_owner == 'Rosettea'
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./website/public
|
||||
keep_files: true
|
||||
- name: Deploy
|
||||
if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea'
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./website/public
|
||||
|
130
CHANGELOG.md
130
CHANGELOG.md
@ -1,7 +1,128 @@
|
||||
# 🎀 Changelog
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
- Forward/Right arrow key will fill in hint text (#327)
|
||||
- The readline library adds the ability to create custom instances of the Hilbish
|
||||
line editor. Now, `hilbish.editor` has been changed to a readline instance, instead of just being a table of a few functions to access it.
|
||||
This means the colon operator is now the *preferred* way of accessing its functions,
|
||||
and the dot operator will cause errors in 3.0.
|
||||
Example: `hilbish.editor.getLine()` should be changed to `hilbish.editor:getLine()`
|
||||
before 3.0
|
||||
- Added the `hilbish.editor:read` and `hilbish.editor:log(text)` functions.
|
||||
- `yarn` threading library (See the docs)
|
||||
|
||||
### Changed
|
||||
- Documentation for Lunacolors has been improved, with more information added.
|
||||
- Values returned by bait hooks will be passed to the `throw` caller
|
||||
- `display` property to completion groups entries to style completion entries when type is `list`.
|
||||
example:
|
||||
```lua
|
||||
local cg = {
|
||||
items = {
|
||||
'list item 1',
|
||||
['--command-flag-here'] = {'this does a thing', '--the-flag-alias'},
|
||||
['--styled-command-flag-here'] = {'this does a thing', '--the-flag-alias', display = lunacolors.blue '--styled-command-flag-here'}
|
||||
},
|
||||
type = 'list'
|
||||
}
|
||||
```
|
||||
|
||||
## [2.3.4] - 2024-12-28
|
||||
### Fixed
|
||||
- Skip over file and prevent panic if info cannot be retrieved during file completion (due to permission error or anything else)
|
||||
- Apply environment variables properly after 2.3 shell interpreter changes
|
||||
- hilbish.sink.readAll() function now reads data that doesn't end in a newline
|
||||
|
||||
## [2.3.3] - 2024-11-04
|
||||
### Fixed
|
||||
- Heredocs having issues
|
||||
|
||||
### Added
|
||||
- Adding `\` at the end of input will add a newline and prompt for more input.
|
||||
|
||||
## [2.3.2] - 2024-07-30
|
||||
### Fixed
|
||||
- Command path searching due to 2.3 changes to the shell interpreter
|
||||
|
||||
## [2.3.1] - 2024-07-27
|
||||
[hehe when you see it release](https://youtu.be/AaAF51Gwbxo?si=rhj2iYuQRkqDa693&t=64)
|
||||
|
||||
### Added
|
||||
- `hilbish.opts.tips` was added to display random tips on start up.
|
||||
Displayed tips can be modified via the `hilbish.tips` table.
|
||||
|
||||
### Fixed
|
||||
- Fix a minor regression related to the cd command not working with relative paths
|
||||
- Updated the motd for 2.3
|
||||
|
||||
## [2.3.0] - 2024-07-20
|
||||
### Added
|
||||
- `commander.registry` function to get all registered commanders.
|
||||
- `fs.pipe` function to get a pair of connected files (a pipe).
|
||||
- Added an alternative 2nd parameter to `hilbish.run`, which is `streams`.
|
||||
`streams` is a table of input and output streams to run the command with.
|
||||
It uses these 3 keys:
|
||||
- `input` as standard input for the command
|
||||
- `out` as standard output
|
||||
- `err` as standard error
|
||||
|
||||
Here is a minimal example of the new usage which allows users to now pipe commands
|
||||
directly via Lua functions:
|
||||
|
||||
```lua
|
||||
local fs = require 'fs'
|
||||
local pr, pw = fs.pipe()
|
||||
hilbish.run('ls -l', {
|
||||
stdout = pw,
|
||||
stderr = pw,
|
||||
})
|
||||
|
||||
pw:close()
|
||||
|
||||
hilbish.run('wc -l', {
|
||||
stdin = pr
|
||||
})
|
||||
```
|
||||
|
||||
### Changed
|
||||
- The `-S` flag will be set to Hilbish's absolute path
|
||||
- Hilbish now builds on any Unix (if any dependencies also work, which should.)
|
||||
|
||||
### Fixed
|
||||
- Fix ansi attributes causing issues with text when cut off in greenhouse
|
||||
- Fix greenhouse appearing on terminal resize
|
||||
- Fix crashes when history goes out of bounds when using history navigation
|
||||
- `exec` command should return if no arg presented
|
||||
- Commanders can now be cancelled by Ctrl-C and wont hang the shell anymore.
|
||||
See [issue 198](https://github.com/Rosettea/Hilbish/issues/198).
|
||||
- Shell interpreter can now preserve its environment and set PWD properly.
|
||||
|
||||
## [2.2.3] - 2024-04-27
|
||||
### Fixed
|
||||
- Highligher and hinter work now, since it was regressed from the previous minor release.
|
||||
- `cat` command no longer prints extra newline at end of each file
|
||||
|
||||
### Added
|
||||
- `cat` command now reads files in chunks, allowing for reading large files
|
||||
|
||||
## [2.2.2] - 2024-04-16
|
||||
### Fixed
|
||||
- Line refresh fixes (less flicker)
|
||||
- Do more checks for a TTY
|
||||
- Panic if ENOTTY is thrown from readline
|
||||
- use `x/term` function to check if a terminal
|
||||
|
||||
### Added
|
||||
- Page Up/Down keybinds for Greenhouse will now scroll up and down the size of the region (a page)
|
||||
|
||||
### Changed
|
||||
- Remove usage of `hilbish.goro` in Greenhouse.
|
||||
- Values in `hilbish` table are no longer protected. This means
|
||||
they can be overridden. (#287)
|
||||
|
||||
## [2.2.1] - 2023-12-26
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Removed a left over debug print
|
||||
- Recover panic in `hilbish.goro`
|
||||
|
||||
@ -693,6 +814,13 @@ This input for example will prompt for more input to complete:
|
||||
|
||||
First "stable" release of Hilbish.
|
||||
|
||||
[2.3.4]: https://github.com/Rosettea/Hilbish/compare/v2.3.3...v2.3.4
|
||||
[2.3.3]: https://github.com/Rosettea/Hilbish/compare/v2.3.2...v2.3.3
|
||||
[2.3.2]: https://github.com/Rosettea/Hilbish/compare/v2.3.1...v2.3.2
|
||||
[2.3.1]: https://github.com/Rosettea/Hilbish/compare/v2.3.0...v2.3.1
|
||||
[2.3.0]: https://github.com/Rosettea/Hilbish/compare/v2.2.3...v2.3.0
|
||||
[2.2.3]: https://github.com/Rosettea/Hilbish/compare/v2.2.2...v2.2.3
|
||||
[2.2.2]: https://github.com/Rosettea/Hilbish/compare/v2.2.1...v2.2.2
|
||||
[2.2.1]: https://github.com/Rosettea/Hilbish/compare/v2.2.0...v2.2.1
|
||||
[2.2.0]: https://github.com/Rosettea/Hilbish/compare/v2.1.0...v2.2.0
|
||||
[2.1.2]: https://github.com/Rosettea/Hilbish/compare/v2.1.1...v2.1.2
|
||||
|
23
README.md
23
README.md
@ -1,3 +1,6 @@
|
||||
> [!TIP]
|
||||
> Check out [Hilbish: Midnight Edition](https://github.com/Rosettea/Hilbish/tree/midnight-edition) if you want to use C Lua, LuaJIT or anything related!
|
||||
|
||||
<img src="./assets/hilbish-logo-and-text.png" width=512><br>
|
||||
<blockquote>
|
||||
🌓 The Moon-powered shell! A comfy and extensible shell for Lua fans! 🌺 ✨
|
||||
@ -10,19 +13,23 @@
|
||||
<br>
|
||||
|
||||
Hilbish is an extensible shell designed to be highly customizable.
|
||||
It is configured in Lua and provides a good range of features.
|
||||
It aims to be easy to use for anyone but powerful enough for
|
||||
those who need it.
|
||||
|
||||
It is configured in Lua, and provides a good range of features.
|
||||
It aims to be easy to use for anyone, and powerful enough for
|
||||
those who need more.
|
||||
|
||||
The motivation for choosing Lua was that its simpler and better to use
|
||||
than old shell script. It's fine for basic interactive shell uses,
|
||||
but that's the only place Hilbish has shell script; everything else is Lua
|
||||
and aims to be infinitely configurable. If something isn't, open an issue!
|
||||
than old shell scripts. It's fine for basic interactive shell uses,
|
||||
and supports [both Lua and Sh interactively](https://rosettea.github.io/Hilbish/docs/features/runner-mode/).
|
||||
|
||||
That's the only place Hilbish can use traditional shell syntax though;
|
||||
everything else is Lua and aims to be infinitely configurable.
|
||||
|
||||
If something isn't, open an issue!
|
||||
|
||||
# Screenshots
|
||||
<div align="center">
|
||||
<img src="gallery/tab.png">
|
||||
<img src="gallery/pillprompt.png">
|
||||
</div>
|
||||
|
||||
# Getting Hilbish
|
||||
@ -36,7 +43,7 @@ on the website for distributed binaries from GitHub or other package repositorie
|
||||
Otherwise, continue reading for steps on compiling.
|
||||
|
||||
## Prerequisites
|
||||
- [Go 1.17+](https://go.dev)
|
||||
- [Go 1.22+](https://go.dev)
|
||||
- [Task](https://taskfile.dev/installation/) (**Go on the hyperlink here to see Task's install method for your OS.**)
|
||||
|
||||
## Build
|
||||
|
@ -6,7 +6,7 @@ vars:
|
||||
PREFIX: '{{default "/usr/local" .PREFIX}}'
|
||||
bindir__: '{{.PREFIX}}/bin'
|
||||
BINDIR: '{{default .bindir__ .BINDIR}}'
|
||||
libdir__: '{{.PREFIX}}/share/hilbish'
|
||||
libdir__: ''
|
||||
LIBDIR: '{{default .libdir__ .LIBDIR}}'
|
||||
goflags__: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}}"'
|
||||
GOFLAGS: '{{default .goflags__ .GOFLAGS}}'
|
||||
|
198
api.go
198
api.go
@ -13,7 +13,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
//"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@ -25,10 +25,12 @@ import (
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
|
||||
//"github.com/arnodel/golua/lib/iolib"
|
||||
"github.com/maxlandon/readline"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
//"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
|
||||
var exports = map[string]util.LuaExport{
|
||||
@ -37,7 +39,6 @@ var exports = map[string]util.LuaExport{
|
||||
"complete": {hlcomplete, 2, false},
|
||||
"cwd": {hlcwd, 0, false},
|
||||
"exec": {hlexec, 1, false},
|
||||
"runnerMode": {hlrunnerMode, 1, false},
|
||||
"goro": {hlgoro, 1, true},
|
||||
"highlighter": {hlhighlighter, 1, false},
|
||||
"hinter": {hlhinter, 1, false},
|
||||
@ -47,7 +48,6 @@ var exports = map[string]util.LuaExport{
|
||||
"inputMode": {hlinputMode, 1, false},
|
||||
"interval": {hlinterval, 2, false},
|
||||
"read": {hlread, 1, false},
|
||||
"run": {hlrun, 1, true},
|
||||
"timeout": {hltimeout, 2, false},
|
||||
"which": {hlwhich, 1, false},
|
||||
}
|
||||
@ -59,49 +59,12 @@ var hilbishLoader = packagelib.Loader{
|
||||
}
|
||||
|
||||
func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
fakeMod := rt.NewTable()
|
||||
modmt := rt.NewTable()
|
||||
mod := rt.NewTable()
|
||||
|
||||
modIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
arg := c.Arg(1)
|
||||
val := mod.Get(arg)
|
||||
|
||||
return c.PushingNext1(t.Runtime, val), nil
|
||||
}
|
||||
modNewIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
k, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := c.Arg(2)
|
||||
if k == "highlighter" {
|
||||
var err error
|
||||
// fine to assign, since itll be either nil or a closure
|
||||
highlighter, err = c.ClosureArg(2)
|
||||
if err != nil {
|
||||
return nil, errors.New("hilbish.highlighter has to be a function")
|
||||
}
|
||||
} else if k == "hinter" {
|
||||
var err error
|
||||
hinter, err = c.ClosureArg(2)
|
||||
if err != nil {
|
||||
return nil, errors.New("hilbish.hinter has to be a function")
|
||||
}
|
||||
} else if modVal := mod.Get(rt.StringValue(k)); modVal != rt.NilValue {
|
||||
return nil, errors.New("not allowed to override in hilbish table")
|
||||
}
|
||||
mod.Set(rt.StringValue(k), v)
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
modmt.Set(rt.StringValue("__newindex"), rt.FunctionValue(rt.NewGoFunction(modNewIndex, "__newindex", 3, false)))
|
||||
modmt.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(modIndex, "__index", 2, false)))
|
||||
fakeMod.SetMetatable(modmt)
|
||||
|
||||
util.SetExports(rtm, mod, exports)
|
||||
if hshMod == nil {
|
||||
hshMod = mod
|
||||
}
|
||||
|
||||
host, _ := os.Hostname()
|
||||
username := curuser.Username
|
||||
@ -110,16 +73,16 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows
|
||||
}
|
||||
|
||||
util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(getVersion()))
|
||||
util.SetFieldProtected(fakeMod, mod, "goVersion", rt.StringValue(runtime.Version()))
|
||||
util.SetFieldProtected(fakeMod, mod, "user", rt.StringValue(username))
|
||||
util.SetFieldProtected(fakeMod, mod, "host", rt.StringValue(host))
|
||||
util.SetFieldProtected(fakeMod, mod, "home", rt.StringValue(curuser.HomeDir))
|
||||
util.SetFieldProtected(fakeMod, mod, "dataDir", rt.StringValue(dataDir))
|
||||
util.SetFieldProtected(fakeMod, mod, "interactive", rt.BoolValue(interactive))
|
||||
util.SetFieldProtected(fakeMod, mod, "login", rt.BoolValue(login))
|
||||
util.SetFieldProtected(fakeMod, mod, "vimMode", rt.NilValue)
|
||||
util.SetFieldProtected(fakeMod, mod, "exitCode", rt.IntValue(0))
|
||||
util.SetField(rtm, mod, "ver", rt.StringValue(getVersion()))
|
||||
util.SetField(rtm, mod, "goVersion", rt.StringValue(runtime.Version()))
|
||||
util.SetField(rtm, mod, "user", rt.StringValue(username))
|
||||
util.SetField(rtm, mod, "host", rt.StringValue(host))
|
||||
util.SetField(rtm, mod, "home", rt.StringValue(curuser.HomeDir))
|
||||
util.SetField(rtm, mod, "dataDir", rt.StringValue(dataDir))
|
||||
util.SetField(rtm, mod, "interactive", rt.BoolValue(interactive))
|
||||
util.SetField(rtm, mod, "login", rt.BoolValue(login))
|
||||
util.SetField(rtm, mod, "vimMode", rt.NilValue)
|
||||
util.SetField(rtm, mod, "exitCode", rt.IntValue(0))
|
||||
|
||||
// hilbish.userDir table
|
||||
hshuser := userDirLoader(rtm)
|
||||
@ -141,7 +104,6 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
// hilbish.completion table
|
||||
hshcomp := completionLoader(rtm)
|
||||
// TODO: REMOVE "completion" AND ONLY USE "completions" WITH AN S
|
||||
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))
|
||||
mod.Set(rt.StringValue("completions"), rt.TableValue(hshcomp))
|
||||
|
||||
// hilbish.runner table
|
||||
@ -158,9 +120,6 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
timersModule := timers.loader(rtm)
|
||||
mod.Set(rt.StringValue("timers"), rt.TableValue(timersModule))
|
||||
|
||||
editorModule := editorLoader(rtm)
|
||||
mod.Set(rt.StringValue("editor"), rt.TableValue(editorModule))
|
||||
|
||||
versionModule := rt.NewTable()
|
||||
util.SetField(rtm, versionModule, "branch", rt.StringValue(gitBranch))
|
||||
util.SetField(rtm, versionModule, "full", rt.StringValue(getVersion()))
|
||||
@ -171,7 +130,10 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
pluginModule := moduleLoader(rtm)
|
||||
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
|
||||
|
||||
return rt.TableValue(fakeMod), nil
|
||||
sinkModule := util.SinkLoader(rtm)
|
||||
mod.Set(rt.StringValue("sink"), rt.TableValue(sinkModule))
|
||||
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
@ -191,50 +153,36 @@ func unsetVimMode() {
|
||||
util.SetField(l, hshMod, "vimMode", rt.NilValue)
|
||||
}
|
||||
|
||||
// run(cmd, returnOut) -> exitCode (number), stdout (string), stderr (string)
|
||||
// Runs `cmd` in Hilbish's shell script interpreter.
|
||||
// #param cmd string
|
||||
// #param returnOut boolean If this is true, the function will return the standard output and error of the command instead of printing it.
|
||||
// #returns number, string, string
|
||||
func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var terminalOut bool
|
||||
if len(c.Etc()) != 0 {
|
||||
tout := c.Etc()[0]
|
||||
termOut, ok := tout.TryBool()
|
||||
terminalOut = termOut
|
||||
/*
|
||||
func handleStream(v rt.Value, strms *streams, errStream bool) error {
|
||||
ud, ok := v.TryUserData()
|
||||
if !ok {
|
||||
return nil, errors.New("bad argument to run (expected boolean, got " + tout.TypeName() + ")")
|
||||
return errors.New("expected metatable argument")
|
||||
}
|
||||
|
||||
val := ud.Value()
|
||||
var varstrm io.Writer
|
||||
if f, ok := val.(*iolib.File); ok {
|
||||
varstrm = f.Handle()
|
||||
}
|
||||
|
||||
if f, ok := val.(*sink); ok {
|
||||
varstrm = f.writer
|
||||
}
|
||||
|
||||
if varstrm == nil {
|
||||
return errors.New("expected either a sink or file")
|
||||
}
|
||||
|
||||
if errStream {
|
||||
strms.stderr = varstrm
|
||||
} else {
|
||||
terminalOut = true
|
||||
strms.stdout = varstrm
|
||||
}
|
||||
|
||||
var exitcode uint8
|
||||
stdout, stderr, err := execCommand(cmd, terminalOut)
|
||||
|
||||
if code, ok := interp.IsExitStatus(err); ok {
|
||||
exitcode = code
|
||||
} else if err != nil {
|
||||
exitcode = 1
|
||||
}
|
||||
|
||||
stdoutStr := ""
|
||||
stderrStr := ""
|
||||
if !terminalOut {
|
||||
stdoutStr = stdout.(*bytes.Buffer).String()
|
||||
stderrStr = stderr.(*bytes.Buffer).String()
|
||||
}
|
||||
|
||||
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
// cwd() -> string
|
||||
// Returns the current directory of the shell.
|
||||
@ -245,7 +193,6 @@ func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(cwd)), nil
|
||||
}
|
||||
|
||||
|
||||
// read(prompt) -> input (string)
|
||||
// Read input from the user, using Hilbish's line editor/input reader.
|
||||
// This is a separate instance from the one Hilbish actually uses.
|
||||
@ -319,8 +266,10 @@ func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
case "left":
|
||||
prompt = p
|
||||
lr.SetPrompt(fmtPrompt(prompt))
|
||||
case "right": lr.SetRightPrompt(fmtPrompt(p))
|
||||
default: return nil, errors.New("expected prompt type to be right or left, got " + typ)
|
||||
case "right":
|
||||
lr.SetRightPrompt(fmtPrompt(p))
|
||||
default:
|
||||
return nil, errors.New("expected prompt type to be right or left, got " + typ)
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
@ -351,7 +300,7 @@ hilbish.multiprompt '-->'
|
||||
*/
|
||||
func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(multilinePrompt)), nil
|
||||
}
|
||||
prompt, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
@ -455,7 +404,7 @@ func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
}
|
||||
cmdArgs, _ := splitInput(cmd)
|
||||
if runtime.GOOS != "windows" {
|
||||
cmdPath, err := exec.LookPath(cmdArgs[0])
|
||||
cmdPath, err := util.LookPath(cmdArgs[0])
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
// if we get here, cmdPath will be nothing
|
||||
@ -648,12 +597,12 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
cmd := strings.Split(alias, " ")[0]
|
||||
|
||||
// check for commander
|
||||
if commands[cmd] != nil {
|
||||
if cmds.Commands[cmd] != nil {
|
||||
// they dont resolve to a path, so just send the cmd
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
|
||||
}
|
||||
|
||||
path, err := exec.LookPath(cmd)
|
||||
path, err := util.LookPath(cmd)
|
||||
if err != nil {
|
||||
return c.Next(), nil
|
||||
}
|
||||
@ -689,34 +638,6 @@ func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// runnerMode(mode)
|
||||
// Sets the execution/runner mode for interactive Hilbish.
|
||||
// This determines whether Hilbish wll try to run input as Lua
|
||||
// and/or sh or only do one of either.
|
||||
// Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||
// sh, and lua. It also accepts a function, to which if it is passed one
|
||||
// will call it to execute user input instead.
|
||||
// Read [about runner mode](../features/runner-mode) for more information.
|
||||
// #param mode string|function
|
||||
func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mode := c.Arg(0)
|
||||
|
||||
switch mode.Type() {
|
||||
case rt.StringType:
|
||||
switch mode.AsString() {
|
||||
case "hybrid", "hybridRev", "lua", "sh": runnerMode = mode
|
||||
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.AsString())
|
||||
}
|
||||
case rt.FunctionType: runnerMode = mode
|
||||
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.TypeName())
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// hinter(line, pos)
|
||||
// The command line hint handler. It gets called on every key insert to
|
||||
// determine what text to use as an inline hint. It is passed the current
|
||||
@ -746,10 +667,21 @@ func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
// #example
|
||||
// --This code will highlight all double quoted strings in green.
|
||||
// function hilbish.highlighter(line)
|
||||
//
|
||||
// return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
|
||||
//
|
||||
// end
|
||||
// #example
|
||||
// #param line string
|
||||
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
return c.Next(), nil
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
line, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(line)), nil
|
||||
}
|
||||
|
@ -2,14 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"go/ast"
|
||||
"go/doc"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
md "github.com/atsushinee/go-markdown-generator/doc"
|
||||
@ -84,6 +84,9 @@ var prefix = map[string]string{
|
||||
"commander": "c",
|
||||
"bait": "b",
|
||||
"terminal": "term",
|
||||
"snail": "snail",
|
||||
"readline": "rl",
|
||||
"yarn": "yarn",
|
||||
}
|
||||
|
||||
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
||||
@ -208,6 +211,10 @@ func setupDocType(mod string, typ *doc.Type) *docPiece {
|
||||
}
|
||||
|
||||
func setupDoc(mod string, fun *doc.Func) *docPiece {
|
||||
if fun.Doc == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
docs := strings.TrimSpace(fun.Doc)
|
||||
tags, parts := getTagsAndDocs(docs)
|
||||
|
||||
@ -216,6 +223,10 @@ func setupDoc(mod string, fun *doc.Func) *docPiece {
|
||||
goto start
|
||||
}
|
||||
|
||||
if prefix[mod] == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if (!strings.HasPrefix(fun.Name, prefix[mod]) && tags["interface"] == nil) || (strings.ToLower(fun.Name) == "loader" && tags["interface"] == nil) {
|
||||
return nil
|
||||
}
|
||||
@ -299,10 +310,28 @@ start:
|
||||
func main() {
|
||||
fset := token.NewFileSet()
|
||||
os.Mkdir("docs", 0777)
|
||||
os.RemoveAll("docs/api")
|
||||
os.Mkdir("docs/api", 0777)
|
||||
|
||||
f, err := os.Create("docs/api/_index.md")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.WriteString(`---
|
||||
title: API
|
||||
layout: doc
|
||||
weight: -100
|
||||
menu: docs
|
||||
---
|
||||
|
||||
Welcome to the API documentation for Hilbish. This documents Lua functions
|
||||
provided by Hilbish.
|
||||
`)
|
||||
f.Close()
|
||||
|
||||
os.Mkdir("emmyLuaDocs", 0777)
|
||||
|
||||
dirs := []string{"./"}
|
||||
dirs := []string{"./", "./util"}
|
||||
filepath.Walk("golibs/", func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
@ -329,7 +358,7 @@ func main() {
|
||||
pieces := []docPiece{}
|
||||
typePieces := []docPiece{}
|
||||
mod := l
|
||||
if mod == "main" {
|
||||
if mod == "main" || mod == "util" {
|
||||
mod = "hilbish"
|
||||
}
|
||||
var hasInterfaces bool
|
||||
@ -413,6 +442,14 @@ func main() {
|
||||
interfaceModules[modname].Types = append(interfaceModules[modname].Types, piece)
|
||||
}
|
||||
|
||||
fmt.Println(filteredTypePieces)
|
||||
if newDoc, ok := docs[mod]; ok {
|
||||
oldMod := docs[mod]
|
||||
newDoc.Types = append(filteredTypePieces, oldMod.Types...)
|
||||
newDoc.Docs = append(filteredPieces, oldMod.Docs...)
|
||||
|
||||
docs[mod] = newDoc
|
||||
} else {
|
||||
docs[mod] = module{
|
||||
Types: filteredTypePieces,
|
||||
Docs: filteredPieces,
|
||||
@ -423,6 +460,7 @@ func main() {
|
||||
Fields: docPieceTag("field", tags),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, mod := range interfaceModules {
|
||||
docs[key] = *mod
|
||||
@ -488,8 +526,12 @@ func main() {
|
||||
}
|
||||
|
||||
mdTable.SetContent(i-diff, 0, fmt.Sprintf(`<a href="#%s">%s</a>`, dps.FuncName, dps.FuncSig))
|
||||
if len(dps.Doc) == 0 {
|
||||
fmt.Printf("WARNING! Function %s on module %s has no documentation!\n", dps.FuncName, modname)
|
||||
} else {
|
||||
mdTable.SetContent(i-diff, 1, dps.Doc[0])
|
||||
}
|
||||
}
|
||||
f.WriteString(mdTable.String())
|
||||
f.WriteString("\n")
|
||||
}
|
||||
@ -501,7 +543,6 @@ func main() {
|
||||
mdTable.SetTitle(0, "")
|
||||
mdTable.SetTitle(1, "")
|
||||
|
||||
|
||||
for i, dps := range modu.Fields {
|
||||
mdTable.SetContent(i, 0, dps.FuncName)
|
||||
mdTable.SetContent(i, 1, strings.Join(dps.Doc, " "))
|
||||
@ -516,7 +557,6 @@ func main() {
|
||||
mdTable.SetTitle(0, "")
|
||||
mdTable.SetTitle(1, "")
|
||||
|
||||
|
||||
for i, dps := range modu.Properties {
|
||||
mdTable.SetContent(i, 0, dps.FuncName)
|
||||
mdTable.SetContent(i, 1, strings.Join(dps.Doc, " "))
|
||||
|
@ -1,7 +1,9 @@
|
||||
local fs = require 'fs'
|
||||
local emmyPattern = '^%-%-%- (.+)'
|
||||
local modpattern = '^%-+ @module (%w+)'
|
||||
local emmyPattern2 = '^%-%- (.+)'
|
||||
local modpattern = '^%-+ @module (.+)'
|
||||
local pieces = {}
|
||||
local descriptions = {}
|
||||
|
||||
local files = fs.readdir 'nature'
|
||||
for _, fname in ipairs(files) do
|
||||
@ -13,18 +15,25 @@ for _, fname in ipairs(files) do
|
||||
local mod = header:match(modpattern)
|
||||
if not mod then goto continue end
|
||||
|
||||
print(fname, mod)
|
||||
pieces[mod] = {}
|
||||
descriptions[mod] = {}
|
||||
|
||||
local docPiece = {}
|
||||
local lines = {}
|
||||
local lineno = 0
|
||||
local doingDescription = true
|
||||
|
||||
for line in f:lines() do
|
||||
lineno = lineno + 1
|
||||
lines[lineno] = line
|
||||
|
||||
if line == header then goto continue2 end
|
||||
if not line:match(emmyPattern) then
|
||||
if line:match(emmyPattern) or line:match(emmyPattern2) then
|
||||
if doingDescription then
|
||||
table.insert(descriptions[mod], line:match(emmyPattern) or line:match(emmyPattern2))
|
||||
end
|
||||
else
|
||||
doingDescription = false
|
||||
if line:match '^function' then
|
||||
local pattern = (string.format('^function %s%%.', mod) .. '(%w+)')
|
||||
local funcName = line:match(pattern)
|
||||
@ -32,10 +41,12 @@ for _, fname in ipairs(files) do
|
||||
|
||||
local dps = {
|
||||
description = {},
|
||||
example = {},
|
||||
params = {}
|
||||
}
|
||||
|
||||
local offset = 1
|
||||
local doingExample = false
|
||||
while true do
|
||||
local prev = lines[lineno - offset]
|
||||
|
||||
@ -51,19 +62,31 @@ for _, fname in ipairs(files) do
|
||||
if emmy == 'param' then
|
||||
table.insert(dps.params, 1, {
|
||||
name = emmythings[1],
|
||||
type = emmythings[2]
|
||||
type = emmythings[2],
|
||||
-- the +1 accounts for space.
|
||||
description = table.concat(emmythings, ' '):sub(emmythings[1]:len() + 1 + emmythings[2]:len() + 1)
|
||||
})
|
||||
end
|
||||
else
|
||||
if docline:match '#example' then
|
||||
doingExample = not doingExample
|
||||
end
|
||||
|
||||
if not docline:match '#example' then
|
||||
if doingExample then
|
||||
table.insert(dps.example, 1, docline)
|
||||
else
|
||||
table.insert(dps.description, 1, docline)
|
||||
end
|
||||
end
|
||||
end
|
||||
offset = offset + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
pieces[mod][funcName] = dps
|
||||
table.insert(pieces[mod], {funcName, dps})
|
||||
end
|
||||
docPiece = {}
|
||||
goto continue2
|
||||
@ -81,29 +104,82 @@ description: %s
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "Nature"
|
||||
parent: "%s"
|
||||
---
|
||||
|
||||
]]
|
||||
|
||||
for iface, dps in pairs(pieces) do
|
||||
local mod = iface:match '(%w+)%.' or 'nature'
|
||||
local path = string.format('docs/%s/%s.md', mod, iface)
|
||||
local docParent = 'Nature'
|
||||
|
||||
path = string.format('docs/%s/%s.md', mod, iface)
|
||||
if mod ~= 'nature' then
|
||||
docParent = "API"
|
||||
path = string.format('docs/api/%s/%s.md', mod, iface)
|
||||
end
|
||||
if iface == 'hilbish' then
|
||||
docParent = "API"
|
||||
path = string.format('docs/api/hilbish/_index.md', mod, iface)
|
||||
end
|
||||
|
||||
fs.mkdir(fs.dir(path), true)
|
||||
local f <close> = io.open(path, 'w')
|
||||
f:write(string.format(header, 'Module', iface, 'No description.'))
|
||||
print(f)
|
||||
|
||||
print(mod, path)
|
||||
local exists = pcall(fs.stat, path)
|
||||
local newOrNotNature = (exists and mod ~= 'nature') or iface == 'hilbish'
|
||||
|
||||
for func, docs in pairs(dps) do
|
||||
f:write(string.format('<hr>\n<div id=\'%s\'>', func))
|
||||
local f <close> = io.open(path, newOrNotNature and 'r+' or 'w+')
|
||||
local tocPos
|
||||
if not newOrNotNature then
|
||||
f:write(string.format(header, 'Module', iface, (descriptions[iface] and #descriptions[iface] > 0) and descriptions[iface][1] or 'No description.', docParent))
|
||||
if descriptions[iface] and #descriptions[iface] > 0 then
|
||||
table.remove(descriptions[iface], 1)
|
||||
f:write(string.format('\n## Introduction\n%s\n\n', table.concat(descriptions[iface], '\n')))
|
||||
f:write('## Functions\n')
|
||||
f:write([[|||
|
||||
|----|----|
|
||||
]])
|
||||
tocPos = f:seek()
|
||||
end
|
||||
end
|
||||
|
||||
local tocSearch = false
|
||||
for line in f:lines() do
|
||||
if line:match '^## Functions' then
|
||||
tocSearch = true
|
||||
end
|
||||
if tocSearch and line == '' then
|
||||
tocSearch = false
|
||||
tocPos = f:seek() - 1
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(dps, function(a, b) return a[1] < b[1] end)
|
||||
for _, piece in pairs(dps) do
|
||||
local func = piece[1]
|
||||
local docs = piece[2]
|
||||
local sig = string.format('%s.%s(', iface, func)
|
||||
local params = ''
|
||||
for idx, param in ipairs(docs.params) do
|
||||
sig = sig .. ((param.name:gsub('%?$', '')))
|
||||
if idx ~= #docs.params then sig = sig .. ', ' end
|
||||
sig = sig .. param.name:gsub('%?$', '')
|
||||
params = params .. param.name:gsub('%?$', '')
|
||||
if idx ~= #docs.params then
|
||||
sig = sig .. ', '
|
||||
params = params .. ', '
|
||||
end
|
||||
end
|
||||
sig = sig .. ')'
|
||||
|
||||
if tocPos then
|
||||
f:seek('set', tocPos)
|
||||
local contents = f:read '*a'
|
||||
f:seek('set', tocPos)
|
||||
local tocLine = string.format('|<a href="#%s">%s</a>|%s|\n', func, string.format('%s(%s)', func, params), docs.description[1])
|
||||
f:write(tocLine .. contents)
|
||||
f:seek 'end'
|
||||
end
|
||||
|
||||
f:write(string.format('<hr>\n<div id=\'%s\'>\n', func))
|
||||
f:write(string.format([[
|
||||
<h4 class='heading'>
|
||||
%s
|
||||
@ -121,6 +197,11 @@ for iface, dps in pairs(pieces) do
|
||||
end
|
||||
for _, param in ipairs(docs.params) do
|
||||
f:write(string.format('`%s` **`%s`** \n', param.name:gsub('%?$', ''), param.type))
|
||||
f:write(string.format('%s\n\n', param.description))
|
||||
end
|
||||
if #docs.example ~= 0 then
|
||||
f:write '#### Example\n'
|
||||
f:write(string.format('```lua\n%s\n```\n', table.concat(docs.example, '\n')))
|
||||
end
|
||||
--[[
|
||||
local params = table.filter(docs, function(t)
|
||||
|
13
complete.go
13
complete.go
@ -98,7 +98,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(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
|
||||
if err := util.FindExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
|
||||
continue
|
||||
}
|
||||
completions = append(completions, f)
|
||||
@ -115,7 +115,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||
// get basename from matches
|
||||
for _, match := range matches {
|
||||
// check if we have execute permissions for our match
|
||||
err := findExecutable(match, true, false)
|
||||
err := util.FindExecutable(match, true, false)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -128,7 +128,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||
}
|
||||
|
||||
// add lua registered commands to completions
|
||||
for cmdName := range commands {
|
||||
for cmdName := range cmds.Commands {
|
||||
if strings.HasPrefix(cmdName, query) {
|
||||
completions = append(completions, cmdName)
|
||||
}
|
||||
@ -157,9 +157,12 @@ func matchPath(query string) ([]string, string) {
|
||||
|
||||
files, _ := os.ReadDir(path)
|
||||
for _, entry := range files {
|
||||
// should we handle errors here?
|
||||
file, err := entry.Info()
|
||||
if err == nil && file.Mode() & os.ModeSymlink != 0 {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if file.Mode() & os.ModeSymlink != 0 {
|
||||
path, err := filepath.EvalSymlinks(filepath.Join(path, file.Name()))
|
||||
if err == nil {
|
||||
file, err = os.Lstat(path)
|
||||
|
@ -26,8 +26,11 @@ In this example, a command with the name of `hello` is created
|
||||
that will print `Hello world!` to output. One question you may
|
||||
have is: What is the `sinks` parameter?
|
||||
|
||||
The `sinks` parameter is a table with 3 keys: `in`, `out`,
|
||||
and `err`. All of them are a <a href="/Hilbish/docs/api/hilbish/#sink" style="text-decoration: none;">Sink</a>.
|
||||
The `sinks` parameter is a table with 3 keys: `input`, `out`, and `err`.
|
||||
There is an `in` alias to `input`, but it requires using the string accessor syntax (`sinks['in']`)
|
||||
as `in` is also a Lua keyword, so `input` is preferred for use.
|
||||
All of them are a <a href="/Hilbish/docs/api/hilbish/#sink" style="text-decoration: none;">Sink</a>.
|
||||
In the future, `sinks.in` will be removed.
|
||||
|
||||
- `in` is the standard input.
|
||||
You may use the read functions on this sink to get input from the user.
|
||||
@ -41,6 +44,7 @@ This sink is for writing errors, as the name would suggest.
|
||||
|----|----|
|
||||
|<a href="#deregister">deregister(name)</a>|Removes the named command. Note that this will only remove Commander-registered commands.|
|
||||
|<a href="#register">register(name, cb)</a>|Adds a new command with the given `name`. When Hilbish has to run a command with a name,|
|
||||
|<a href="#registry">registry() -> table</a>|Returns all registered commanders. Returns a list of tables with the following keys:|
|
||||
|
||||
<hr>
|
||||
<div id='deregister'>
|
||||
@ -91,3 +95,19 @@ end)
|
||||
```
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='registry'>
|
||||
<h4 class='heading'>
|
||||
commander.registry() -> table
|
||||
<a href="#registry" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Returns all registered commanders. Returns a list of tables with the following keys:
|
||||
- `exec`: The function used to run the commander. Commanders require args and sinks to be passed.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
|
@ -23,6 +23,7 @@ library offers more functions and will work on any operating system Hilbish does
|
||||
|<a href="#glob">glob(pattern) -> matches (table)</a>|Match all files based on the provided `pattern`.|
|
||||
|<a href="#join">join(...path) -> string</a>|Takes any list of paths and joins them based on the operating system's path separator.|
|
||||
|<a href="#mkdir">mkdir(name, recursive)</a>|Creates a new directory with the provided `name`.|
|
||||
|<a href="#pipe">fpipe() -> File, File</a>|Returns a pair of connected files, also known as a pipe.|
|
||||
|<a href="#readdir">readdir(path) -> table[string]</a>|Returns a list of all files and directories in the provided path.|
|
||||
|<a href="#stat">stat(path) -> {}</a>|Returns the information about a given `path`.|
|
||||
|
||||
@ -183,6 +184,22 @@ fs.mkdir('./foo/bar', true)
|
||||
```
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='pipe'>
|
||||
<h4 class='heading'>
|
||||
fs.fpipe() -> File, File
|
||||
<a href="#pipe" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Returns a pair of connected files, also known as a pipe.
|
||||
The type returned is a Lua file, same as returned from `io` functions.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='readdir'>
|
||||
<h4 class='heading'>
|
||||
|
@ -28,10 +28,10 @@ interfaces and functions which directly relate to shell functionality.
|
||||
|<a href="#prependPath">prependPath(dir)</a>|Prepends `dir` to $PATH.|
|
||||
|<a href="#prompt">prompt(str, typ)</a>|Changes the shell prompt to the provided string.|
|
||||
|<a href="#read">read(prompt) -> input (string)</a>|Read input from the user, using Hilbish's line editor/input reader.|
|
||||
|<a href="#run">run(cmd, returnOut) -> exitCode (number), stdout (string), stderr (string)</a>|Runs `cmd` in Hilbish's shell script interpreter.|
|
||||
|<a href="#runnerMode">runnerMode(mode)</a>|Sets the execution/runner mode for interactive Hilbish.|
|
||||
|<a href="#timeout">timeout(cb, time) -> @Timer</a>|Executed the `cb` function after a period of `time`.|
|
||||
|<a href="#which">which(name) -> string</a>|Checks if `name` is a valid command.|
|
||||
|<a href="#runnerMode">runnerMode(mode)</a>|Sets the execution/runner mode for interactive Hilbish.|
|
||||
|<a href="#run">run(cmd, streams)</a>|Runs `cmd` in Hilbish's shell script interpreter.|
|
||||
|
||||
## Static module fields
|
||||
|||
|
||||
@ -229,7 +229,9 @@ Note that to set a highlighter, one has to override this function.
|
||||
```lua
|
||||
--This code will highlight all double quoted strings in green.
|
||||
function hilbish.highlighter(line)
|
||||
|
||||
return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
|
||||
|
||||
end
|
||||
```
|
||||
</div>
|
||||
@ -408,49 +410,6 @@ Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
|
||||
`string` **`prompt?`**
|
||||
Text to print before input, can be empty.
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='run'>
|
||||
<h4 class='heading'>
|
||||
hilbish.run(cmd, returnOut) -> exitCode (number), stdout (string), stderr (string)
|
||||
<a href="#run" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Runs `cmd` in Hilbish's shell script interpreter.
|
||||
|
||||
#### Parameters
|
||||
`string` **`cmd`**
|
||||
|
||||
|
||||
`boolean` **`returnOut`**
|
||||
If this is true, the function will return the standard output and error of the command instead of printing it.
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='runnerMode'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runnerMode(mode)
|
||||
<a href="#runnerMode" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Sets the execution/runner mode for interactive Hilbish.
|
||||
This determines whether Hilbish wll try to run input as Lua
|
||||
and/or sh or only do one of either.
|
||||
Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||
sh, and lua. It also accepts a function, to which if it is passed one
|
||||
will call it to execute user input instead.
|
||||
Read [about runner mode](../features/runner-mode) for more information.
|
||||
|
||||
#### Parameters
|
||||
`string|function` **`mode`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
@ -496,8 +455,7 @@ Will return the path of the binary, or a basename if it's a commander.
|
||||
<hr>
|
||||
|
||||
## Sink
|
||||
A sink is a structure that has input and/or output to/from
|
||||
a desination.
|
||||
A sink is a structure that has input and/or output to/from a desination.
|
||||
|
||||
### Methods
|
||||
#### autoFlush(auto)
|
||||
@ -519,3 +477,65 @@ Writes data to a sink.
|
||||
#### writeln(str)
|
||||
Writes data to a sink with a newline at the end.
|
||||
|
||||
<hr>
|
||||
<div id='run'>
|
||||
<h4 class='heading'>
|
||||
hilbish.run(cmd, streams)
|
||||
<a href="#run" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Runs `cmd` in Hilbish's shell script interpreter.
|
||||
The `streams` parameter specifies the output and input streams the command should use.
|
||||
For example, to write command output to a sink.
|
||||
As a table, the caller can directly specify the standard output, error, and input
|
||||
streams of the command with the table keys `out`, `err`, and `input` respectively.
|
||||
As a boolean, it specifies whether the command should use standard output or return its output streams.
|
||||
#### Parameters
|
||||
`cmd` **`string`**
|
||||
|
||||
|
||||
`streams` **`table|boolean`**
|
||||
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
-- This code is the same as `ls -l | wc -l`
|
||||
local fs = require 'fs'
|
||||
local pr, pw = fs.pipe()
|
||||
hilbish.run('ls -l', {
|
||||
stdout = pw,
|
||||
stderr = pw,
|
||||
})
|
||||
pw:close()
|
||||
hilbish.run('wc -l', {
|
||||
stdin = pr
|
||||
})
|
||||
```
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='runnerMode'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runnerMode(mode)
|
||||
<a href="#runnerMode" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Sets the execution/runner mode for interactive Hilbish.
|
||||
**NOTE: This function is deprecated and will be removed in 3.0**
|
||||
Use `hilbish.runner.setCurrent` instead.
|
||||
This determines whether Hilbish wll try to run input as Lua
|
||||
and/or sh or only do one of either.
|
||||
Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||
sh, and lua. It also accepts a function, to which if it is passed one
|
||||
will call it to execute user input instead.
|
||||
Read [about runner mode](../features/runner-mode) for more information.
|
||||
#### Parameters
|
||||
`mode` **`string|function`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
67
docs/api/hilbish/hilbish.abbr.md
Normal file
67
docs/api/hilbish/hilbish.abbr.md
Normal file
@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Module hilbish.abbr
|
||||
description: command line abbreviations
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
|
||||
## Introduction
|
||||
The abbr module manages Hilbish abbreviations. These are words that can be replaced
|
||||
with longer command line strings when entered.
|
||||
As an example, `git push` can be abbreviated to `gp`. When the user types
|
||||
`gp` into the command line, after hitting space or enter, it will expand to `git push`.
|
||||
Abbreviations can be used as an alternative to aliases. They are saved entirely in the history
|
||||
Instead of the aliased form of the same command.
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#remove">remove(abbr)</a>|Removes the named `abbr`.|
|
||||
|<a href="#add">add(abbr, expanded|function, opts)</a>|Adds an abbreviation. The `abbr` is the abbreviation itself,|
|
||||
<hr>
|
||||
<div id='add'>
|
||||
<h4 class='heading'>
|
||||
hilbish.abbr.add(abbr, expanded|function, opts)
|
||||
<a href="#add" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Adds an abbreviation. The `abbr` is the abbreviation itself,
|
||||
while `expanded` is what the abbreviation should expand to.
|
||||
It can be either a function or a string. If it is a function, it will expand to what
|
||||
the function returns.
|
||||
`opts` is a table that accepts 1 key: `anywhere`.
|
||||
`opts.anywhere` defines whether the abbr expands anywhere in the command line or not,
|
||||
whereas the default behavior is only at the beginning of the line
|
||||
#### Parameters
|
||||
`abbr` **`string`**
|
||||
|
||||
|
||||
`expanded|function` **`string`**
|
||||
|
||||
|
||||
`opts` **`table`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='remove'>
|
||||
<h4 class='heading'>
|
||||
hilbish.abbr.remove(abbr)
|
||||
<a href="#remove" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Removes the named `abbr`.
|
||||
#### Parameters
|
||||
`abbr` **`string`**
|
||||
|
||||
|
||||
</div>
|
||||
|
@ -1,103 +0,0 @@
|
||||
---
|
||||
title: Module hilbish.editor
|
||||
description: interactions for Hilbish's line reader
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
## Introduction
|
||||
The hilbish.editor interface provides functions to
|
||||
directly interact with the line editor in use.
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#editor.getLine">getLine() -> string</a>|Returns the current input line.|
|
||||
|<a href="#editor.getVimRegister">getVimRegister(register) -> string</a>|Returns the text that is at the register.|
|
||||
|<a href="#editor.insert">insert(text)</a>|Inserts text into the Hilbish command line.|
|
||||
|<a href="#editor.getChar">getChar() -> string</a>|Reads a keystroke from the user. This is in a format of something like Ctrl-L.|
|
||||
|<a href="#editor.setVimRegister">setVimRegister(register, text)</a>|Sets the vim register at `register` to hold the passed text.|
|
||||
|
||||
<hr>
|
||||
<div id='editor.getLine'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.getLine() -> string
|
||||
<a href="#editor.getLine" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Returns the current input line.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='editor.getVimRegister'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.getVimRegister(register) -> string
|
||||
<a href="#editor.getVimRegister" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Returns the text that is at the register.
|
||||
|
||||
#### Parameters
|
||||
`string` **`register`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='editor.insert'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.insert(text)
|
||||
<a href="#editor.insert" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Inserts text into the Hilbish command line.
|
||||
|
||||
#### Parameters
|
||||
`string` **`text`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='editor.getChar'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.getChar() -> string
|
||||
<a href="#editor.getChar" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='editor.setVimRegister'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.setVimRegister(register, text)
|
||||
<a href="#editor.setVimRegister" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Sets the vim register at `register` to hold the passed text.
|
||||
|
||||
#### Parameters
|
||||
`string` **`text`**
|
||||
|
||||
|
||||
</div>
|
||||
|
135
docs/api/hilbish/hilbish.messages.md
Normal file
135
docs/api/hilbish/hilbish.messages.md
Normal file
@ -0,0 +1,135 @@
|
||||
---
|
||||
title: Module hilbish.messages
|
||||
description: simplistic message passing
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
|
||||
## Introduction
|
||||
The messages interface defines a way for Hilbish-integrated commands,
|
||||
user config and other tasks to send notifications to alert the user.z
|
||||
The `hilbish.message` type is a table with the following keys:
|
||||
`title` (string): A title for the message notification.
|
||||
`text` (string): The contents of the message.
|
||||
`channel` (string): States the origin of the message, `hilbish.*` is reserved for Hilbish tasks.
|
||||
`summary` (string): A short summary of the `text`.
|
||||
`icon` (string): Unicode (preferably standard emoji) icon for the message notification
|
||||
`read` (boolean): Whether the full message has been read or not.
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#unreadCount">unreadCount()</a>|Returns the amount of unread messages.|
|
||||
|<a href="#send">send(message)</a>|Sends a message.|
|
||||
|<a href="#readAll">readAll()</a>|Marks all messages as read.|
|
||||
|<a href="#read">read(idx)</a>|Marks a message at `idx` as read.|
|
||||
|<a href="#delete">delete(idx)</a>|Deletes the message at `idx`.|
|
||||
|<a href="#clear">clear()</a>|Deletes all messages.|
|
||||
|<a href="#all">all()</a>|Returns all messages.|
|
||||
<hr>
|
||||
<div id='all'>
|
||||
<h4 class='heading'>
|
||||
hilbish.messages.all()
|
||||
<a href="#all" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Returns all messages.
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='clear'>
|
||||
<h4 class='heading'>
|
||||
hilbish.messages.clear()
|
||||
<a href="#clear" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Deletes all messages.
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='delete'>
|
||||
<h4 class='heading'>
|
||||
hilbish.messages.delete(idx)
|
||||
<a href="#delete" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Deletes the message at `idx`.
|
||||
#### Parameters
|
||||
`idx` **`number`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='read'>
|
||||
<h4 class='heading'>
|
||||
hilbish.messages.read(idx)
|
||||
<a href="#read" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Marks a message at `idx` as read.
|
||||
#### Parameters
|
||||
`idx` **`number`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='readAll'>
|
||||
<h4 class='heading'>
|
||||
hilbish.messages.readAll()
|
||||
<a href="#readAll" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Marks all messages as read.
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='send'>
|
||||
<h4 class='heading'>
|
||||
hilbish.messages.send(message)
|
||||
<a href="#send" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Sends a message.
|
||||
#### Parameters
|
||||
`message` **`hilbish.message`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='unreadCount'>
|
||||
<h4 class='heading'>
|
||||
hilbish.messages.unreadCount()
|
||||
<a href="#unreadCount" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Returns the amount of unread messages.
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
39
docs/api/hilbish/hilbish.processors.md
Normal file
39
docs/api/hilbish/hilbish.processors.md
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Module hilbish.processors
|
||||
description: No description.
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
<hr>
|
||||
<div id='add'>
|
||||
<h4 class='heading'>
|
||||
hilbish.processors.add()
|
||||
<a href="#add" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='execute'>
|
||||
<h4 class='heading'>
|
||||
hilbish.processors.execute()
|
||||
<a href="#execute" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Run all command processors, in order by priority.
|
||||
It returns the processed command (which may be the same as the passed command)
|
||||
and a boolean which states whether to proceed with command execution.
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
@ -21,16 +21,18 @@ A runner is passed the input and has to return a table with these values.
|
||||
All are not required, only the useful ones the runner needs to return.
|
||||
(So if there isn't an error, just omit `err`.)
|
||||
|
||||
- `exitCode` (number): A numerical code to indicate the exit result.
|
||||
- `input` (string): The user input. This will be used to add
|
||||
to the history.
|
||||
- `err` (string): A string to indicate an interal error for the runner.
|
||||
It can be set to a few special values for Hilbish to throw the right hooks and have a better looking message:
|
||||
|
||||
`[command]: not-found` will throw a command.not-found hook based on what `[command]` is.
|
||||
|
||||
`[command]: not-executable` will throw a command.not-executable hook.
|
||||
- `continue` (boolean): Whether to prompt the user for more input.
|
||||
- `exitCode` (number): Exit code of the command
|
||||
- `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
|
||||
more is requested.
|
||||
- `err` (string): A string that represents an error from the runner.
|
||||
This should only be set when, for example, there is a syntax error.
|
||||
It can be set to a few special values for Hilbish to throw the right
|
||||
hooks and have a better looking message.
|
||||
- `\<command>: not-found` will throw a `command.not-found` hook
|
||||
based on what `\<command>` is.
|
||||
- `\<command>: not-executable` will throw a `command.not-executable` hook.
|
||||
- `continue` (boolean): Whether Hilbish should prompt the user for no input
|
||||
- `newline` (boolean): Whether a newline should be added at the end of `input`.
|
||||
|
||||
Here is a simple example of a fennel runner. It falls back to
|
||||
shell script if fennel eval has an error.
|
||||
@ -52,29 +54,16 @@ end)
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#runner.setMode">setMode(cb)</a>|This is the same as the `hilbish.runnerMode` function.|
|
||||
|<a href="#runner.lua">lua(cmd)</a>|Evaluates `cmd` as Lua input. This is the same as using `dofile`|
|
||||
|<a href="#runner.sh">sh(cmd)</a>|Runs a command in Hilbish's shell script interpreter.|
|
||||
|
||||
<hr>
|
||||
<div id='runner.setMode'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.setMode(cb)
|
||||
<a href="#runner.setMode" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
This is the same as the `hilbish.runnerMode` function.
|
||||
It takes a callback, which will be used to execute all interactive input.
|
||||
In normal cases, neither callbacks should be overrided by the user,
|
||||
as the higher level functions listed below this will handle it.
|
||||
|
||||
#### Parameters
|
||||
`function` **`cb`**
|
||||
|
||||
|
||||
</div>
|
||||
|<a href="#sh">sh()</a>|nil|
|
||||
|<a href="#setMode">setMode(mode)</a>|**NOTE: This function is deprecated and will be removed in 3.0**|
|
||||
|<a href="#setCurrent">setCurrent(name)</a>|Sets Hilbish's runner mode by name.|
|
||||
|<a href="#set">set(name, runner)</a>|*Sets* a runner by name. The difference between this function and|
|
||||
|<a href="#run">run(input, priv)</a>|Runs `input` with the currently set Hilbish runner.|
|
||||
|<a href="#getCurrent">getCurrent()</a>|Returns the current runner by name.|
|
||||
|<a href="#get">get(name)</a>|Get a runner by name.|
|
||||
|<a href="#exec">exec(cmd, runnerName)</a>|Executes `cmd` with a runner.|
|
||||
|<a href="#add">add(name, runner)</a>|Adds a runner to the table of available runners.|
|
||||
|
||||
<hr>
|
||||
<div id='runner.lua'>
|
||||
@ -95,20 +84,164 @@ or `load`, but is appropriated for the runner interface.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='runner.sh'>
|
||||
<div id='add'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.sh(cmd)
|
||||
<a href="#runner.sh" class='heading-link'>
|
||||
hilbish.runner.add(name, runner)
|
||||
<a href="#add" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Runs a command in Hilbish's shell script interpreter.
|
||||
This is the equivalent of using `source`.
|
||||
|
||||
Adds a runner to the table of available runners.
|
||||
If runner is a table, it must have the run function in it.
|
||||
#### Parameters
|
||||
`string` **`cmd`**
|
||||
`name` **`string`**
|
||||
Name of the runner
|
||||
|
||||
`runner` **`function|table`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='exec'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.exec(cmd, runnerName)
|
||||
<a href="#exec" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Executes `cmd` with a runner.
|
||||
If `runnerName` is not specified, it uses the default Hilbish runner.
|
||||
#### Parameters
|
||||
`cmd` **`string`**
|
||||
|
||||
|
||||
`runnerName` **`string?`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='get'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.get(name)
|
||||
<a href="#get" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Get a runner by name.
|
||||
#### Parameters
|
||||
`name` **`string`**
|
||||
Name of the runner to retrieve.
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='getCurrent'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.getCurrent()
|
||||
<a href="#getCurrent" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Returns the current runner by name.
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='run'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.run(input, priv)
|
||||
<a href="#run" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Runs `input` with the currently set Hilbish runner.
|
||||
This method is how Hilbish executes commands.
|
||||
`priv` is an optional boolean used to state if the input should be saved to history.
|
||||
#### Parameters
|
||||
`input` **`string`**
|
||||
|
||||
|
||||
`priv` **`bool`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='set'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.set(name, runner)
|
||||
<a href="#set" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
*Sets* a runner by name. The difference between this function and
|
||||
add, is set will *not* check if the named runner exists.
|
||||
The runner table must have the run function in it.
|
||||
#### Parameters
|
||||
`name` **`string`**
|
||||
|
||||
|
||||
`runner` **`table`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='setCurrent'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.setCurrent(name)
|
||||
<a href="#setCurrent" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Sets Hilbish's runner mode by name.
|
||||
#### Parameters
|
||||
`name` **`string`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='setMode'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.setMode(mode)
|
||||
<a href="#setMode" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
**NOTE: This function is deprecated and will be removed in 3.0**
|
||||
Use `hilbish.runner.setCurrent` instead.
|
||||
This is the same as the `hilbish.runnerMode` function.
|
||||
It takes a callback, which will be used to execute all interactive input.
|
||||
Or a string which names the runner mode to use.
|
||||
#### Parameters
|
||||
`mode` **`string|function`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='sh'>
|
||||
<h4 class='heading'>
|
||||
hilbish.runner.sh()
|
||||
<a href="#sh" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
|
66
docs/api/readline.md
Normal file
66
docs/api/readline.md
Normal file
@ -0,0 +1,66 @@
|
||||
---
|
||||
title: Module readline
|
||||
description: line reader library
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
## Introduction
|
||||
The readline module is responsible for reading input from the user.
|
||||
The readline module is what Hilbish uses to read input from the user,
|
||||
including all the interactive features of Hilbish like history search,
|
||||
syntax highlighting, everything. The global Hilbish readline instance
|
||||
is usable at `hilbish.editor`.
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#New">new() -> @Readline</a>|Creates a new readline instance.|
|
||||
|
||||
<hr>
|
||||
<div id='New'>
|
||||
<h4 class='heading'>
|
||||
readline.new() -> <a href="/Hilbish/docs/api/readline/#readline" style="text-decoration: none;" id="lol">Readline</a>
|
||||
<a href="#New" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Creates a new readline instance.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
## Types
|
||||
<hr>
|
||||
|
||||
## Readline
|
||||
|
||||
### Methods
|
||||
#### deleteByAmount(amount)
|
||||
Deletes characters in the line by the given amount.
|
||||
|
||||
#### getLine() -> string
|
||||
Returns the current input line.
|
||||
|
||||
#### getVimRegister(register) -> string
|
||||
Returns the text that is at the register.
|
||||
|
||||
#### insert(text)
|
||||
Inserts text into the Hilbish command line.
|
||||
|
||||
#### log(text)
|
||||
Prints a message *before* the prompt without it being interrupted by user input.
|
||||
|
||||
#### read() -> string
|
||||
Reads input from the user.
|
||||
|
||||
#### getChar() -> string
|
||||
Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
|
||||
#### setVimRegister(register, text)
|
||||
Sets the vim register at `register` to hold the passed text.
|
||||
|
50
docs/api/snail.md
Normal file
50
docs/api/snail.md
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Module snail
|
||||
description: shell script interpreter library
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
The snail library houses Hilbish's Lua wrapper of its shell script interpreter.
|
||||
It's not very useful other than running shell scripts, which can be done with other
|
||||
Hilbish functions.
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#new">new() -> @Snail</a>|Creates a new Snail instance.|
|
||||
|
||||
<hr>
|
||||
<div id='new'>
|
||||
<h4 class='heading'>
|
||||
snail.new() -> <a href="/Hilbish/docs/api/snail/#snail" style="text-decoration: none;" id="lol">Snail</a>
|
||||
<a href="#new" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Creates a new Snail instance.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
## Types
|
||||
<hr>
|
||||
|
||||
## Snail
|
||||
A Snail is a shell script interpreter instance.
|
||||
|
||||
### Methods
|
||||
#### dir(path)
|
||||
Changes the directory of the snail instance.
|
||||
The interpreter keeps its set directory even when the Hilbish process changes
|
||||
directory, so this should be called on the `hilbish.cd` hook.
|
||||
|
||||
#### run(command, streams)
|
||||
Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
||||
|
51
docs/api/yarn.md
Normal file
51
docs/api/yarn.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: Module yarn
|
||||
description: multi threading library
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
## Introduction
|
||||
Yarn is a simple multithreading library. Threads are individual Lua states,
|
||||
so they do NOT share the same environment as the code that runs the thread.
|
||||
Bait and Commanders are shared though, so you *can* throw hooks from 1 thread to another.
|
||||
|
||||
Example:
|
||||
|
||||
```lua
|
||||
local yarn = require 'yarn'
|
||||
|
||||
-- calling t will run the yarn thread.
|
||||
local t = yarn.thread(print)
|
||||
t 'printing from another lua state!'
|
||||
```
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#thread">thread(fun) -> @Thread</a>|Creates a new, fresh Yarn thread.|
|
||||
|
||||
<hr>
|
||||
<div id='thread'>
|
||||
<h4 class='heading'>
|
||||
yarn.thread(fun) -> <a href="/Hilbish/docs/api/yarn/#thread" style="text-decoration: none;" id="lol">Thread</a>
|
||||
<a href="#thread" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Creates a new, fresh Yarn thread.
|
||||
`fun` is the function that will run in the thread.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
## Types
|
||||
<hr>
|
||||
|
||||
## Thread
|
||||
|
||||
### Methods
|
@ -56,6 +56,50 @@ return {cg, cg2}, prefix
|
||||
Which looks like this:
|
||||
{{< video src="https://safe.saya.moe/t4CiLK6dgPbD.mp4" >}}
|
||||
|
||||
# Completion Group Types
|
||||
### grid
|
||||
Grid is the simplest completion group type. All items are strings and when
|
||||
completion is done is displayed in a grid based on size.
|
||||
|
||||
Example:
|
||||
```lua
|
||||
{
|
||||
items = {'just', 'a bunch', 'of items', 'here', 'hehe'},
|
||||
type = 'grid'
|
||||
}
|
||||
```
|
||||
|
||||
### list
|
||||
The list completion group type displays in a list. A list item can either be a string, or a table for additional display options.
|
||||
A completion alias can be specified either as the `2nd` entry in the options table
|
||||
or te `alias` key.
|
||||
|
||||
A description can optionally be displayed for a list item, which is either the `1st`
|
||||
entry or the `description` key.
|
||||
|
||||
Lastly, list entries can be styled. This is done with the `display` key. If this is present, this
|
||||
overrides what the completion item *looks* like.
|
||||
|
||||
Example:
|
||||
```lua
|
||||
{
|
||||
items = {
|
||||
['--flag'] = {
|
||||
description = 'this flag nukes the bri ish',
|
||||
alias = '--bye-bri-ish',
|
||||
display = lunacolors.format('--{blue}fl{red}ag')
|
||||
},
|
||||
['--flag2'] = {
|
||||
'make pizza', -- description
|
||||
'--pizzuh', -- alias
|
||||
display = lunacolors.yellow '--pizzuh'
|
||||
},
|
||||
'--flag3'
|
||||
},
|
||||
type = 'list'
|
||||
}
|
||||
```
|
||||
|
||||
# Completion Handler
|
||||
Like most parts of Hilbish, it's made to be extensible and
|
||||
customizable. The default handler for completions in general can
|
||||
|
@ -76,3 +76,8 @@ of an exact match.
|
||||
#### Default: `true`
|
||||
If this is enabled, when a background job is finished,
|
||||
a [notification](../notifications) will be sent.
|
||||
|
||||
### `processorSkipList`
|
||||
#### Value: `table`
|
||||
#### Default: `{}`
|
||||
A table listing the names of command processors to skip.
|
||||
|
@ -33,19 +33,6 @@ needs to run interactive input. For more detail, see the [API documentation](../
|
||||
The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode`
|
||||
and also provides the shell script and Lua runner functions that Hilbish itself uses.
|
||||
|
||||
A runner function is expected to return a table with the following values:
|
||||
- `exitCode` (number): Exit code of the command
|
||||
- `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
|
||||
more is requested.
|
||||
- `err` (string): A string that represents an error from the runner.
|
||||
This should only be set when, for example, there is a syntax error.
|
||||
It can be set to a few special values for Hilbish to throw the right
|
||||
hooks and have a better looking message.
|
||||
- `<command>: not-found` will throw a `command.not-found` hook
|
||||
based on what `<command>` is.
|
||||
- `<command>: not-executable` will throw a `command.not-executable` hook.
|
||||
- `continue` (boolean): Whether Hilbish should prompt the user for no input
|
||||
|
||||
## Functions
|
||||
These are the "low level" functions for the `hilbish.runner` interface.
|
||||
|
||||
|
@ -53,8 +53,39 @@ 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.
|
||||
Now we can get to customization!
|
||||
|
||||
If we closely examine a small snippet of the default config:
|
||||
```lua
|
||||
-- Default Hilbish config
|
||||
-- .. with some omitted code .. --
|
||||
|
||||
local function doPrompt(fail)
|
||||
hilbish.prompt(lunacolors.format(
|
||||
'{blue}%u {cyan}%d ' .. (fail and '{red}' or '{green}') .. '∆ '
|
||||
))
|
||||
end
|
||||
|
||||
doPrompt()
|
||||
|
||||
bait.catch('command.exit', function(code)
|
||||
doPrompt(code ~= 0)
|
||||
end)
|
||||
```
|
||||
|
||||
We see a whopping **three** Hilbish libraries being used in this part of code.
|
||||
First is of course, named after the shell itself, [`hilbish`](../api/hilbish). This is kind of a
|
||||
"catch-all" namespace for functions that directly related to shell functionality/settings.
|
||||
|
||||
And as we can see, the [hilbish.prompt](../api/hilbish/#prompt) function is used
|
||||
to change our prompt. Change our prompt to what, exactly?
|
||||
|
||||
The doc for the function states that the verbs `%u` and `%d`are used for username and current directory
|
||||
of the shell, respectively.
|
||||
|
||||
We wrap this in the [`lunacolors.format`](../lunacolors) function, to give
|
||||
our prompt some nice color.
|
||||
|
||||
But you might have also noticed that this is in the `doPrompt` function, which is called once,
|
||||
and then used again in a [bait](../api/bait) hook. Specifically, the `command.exit` hook,
|
||||
which is called after a command exits, so when it finishes running.
|
||||
|
@ -43,5 +43,29 @@ The notification. The properties are defined in the link above.
|
||||
|
||||
<hr>
|
||||
|
||||
+ `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.cd
|
||||
Sent when the current directory of the shell is changed (via interactive means.)
|
||||
If you are implementing a custom command that changes the directory of the shell,
|
||||
you must throw this hook manually for correctness.
|
||||
|
||||
#### Variables
|
||||
`string` **`path`**
|
||||
Absolute path of the directory that was changed to.
|
||||
|
||||
`string` **`oldPath`**
|
||||
Absolute path of the directory Hilbish *was* in.
|
||||
|
||||
<hr>
|
||||
|
||||
## hilbish.vimAction
|
||||
Sent when the user does a "vim action," being something like yanking or pasting text.
|
||||
See `doc vim-mode actions` for more info.
|
||||
|
||||
#### Variables
|
||||
`string` **`actionName`**
|
||||
Absolute path of the directory that was changed to.
|
||||
|
||||
`table` **`args`**
|
||||
Table of args relating to the Vim action.
|
||||
|
||||
<hr>
|
||||
|
@ -28,14 +28,17 @@ Colors:
|
||||
- magenta
|
||||
- cyan
|
||||
- white
|
||||
|
||||
Styles:
|
||||
- reset
|
||||
- bold
|
||||
- dim
|
||||
- italic
|
||||
- underline
|
||||
- invert
|
||||
|
||||
For the colors, there are background and bright variants. The background
|
||||
color variants have a suffix of `Bg` and bright has a prefix of `bright`.
|
||||
Note that appropriate camel casing has to be applied to them. So bright
|
||||
blue would be `brightBlue` and background cyan would be `cyanBg`.
|
||||
For the colors, there are background and bright variants. Background color
|
||||
variants have a `Bg` suffix, while bright variants use the `bright` prefix.
|
||||
These can also be combined. Note that appropriate camel casing must be applied.
|
||||
For example, bright blue would be written as `brightBlue`, a cyan background as
|
||||
`cyanBg`, and combining them would result in `brightBlueBg`.
|
||||
|
@ -1,40 +1,25 @@
|
||||
---
|
||||
title: Module dirs
|
||||
description: No description.
|
||||
description: internal directory management
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "Nature"
|
||||
---
|
||||
|
||||
<hr>
|
||||
<div id='setOld'>
|
||||
<h4 class='heading'>
|
||||
dirs.setOld(d)
|
||||
<a href="#setOld" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Sets the old directory string.
|
||||
#### Parameters
|
||||
`d` **`string`**
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='push'>
|
||||
<h4 class='heading'>
|
||||
dirs.push()
|
||||
<a href="#push" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Add `d` to the recent directories list.
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
## Introduction
|
||||
The dirs module defines a small set of functions to store and manage
|
||||
directories.
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#setOld">setOld(d)</a>|Sets the old directory string.|
|
||||
|<a href="#recent">recent(idx)</a>|Get entry from recent directories list based on index.|
|
||||
|<a href="#push">push(dir)</a>|Add `dir` to the recent directories list.|
|
||||
|<a href="#pop">pop(num)</a>|Remove the specified amount of dirs from the recent directories list.|
|
||||
|<a href="#peak">peak(num)</a>|Look at `num` amount of recent directories, starting from the latest.|
|
||||
<hr>
|
||||
<div id='peak'>
|
||||
<h4 class='heading'>
|
||||
@ -45,8 +30,11 @@ dirs.peak(num)
|
||||
</h4>
|
||||
|
||||
Look at `num` amount of recent directories, starting from the latest.
|
||||
This returns a table of recent directories, up to the `num` amount.
|
||||
#### Parameters
|
||||
`num` **`number`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
@ -61,6 +49,24 @@ dirs.pop(num)
|
||||
Remove the specified amount of dirs from the recent directories list.
|
||||
#### Parameters
|
||||
`num` **`number`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='push'>
|
||||
<h4 class='heading'>
|
||||
dirs.push(dir)
|
||||
<a href="#push" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Add `dir` to the recent directories list.
|
||||
#### Parameters
|
||||
`dir` **`string`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
@ -75,5 +81,23 @@ dirs.recent(idx)
|
||||
Get entry from recent directories list based on index.
|
||||
#### Parameters
|
||||
`idx` **`number`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='setOld'>
|
||||
<h4 class='heading'>
|
||||
dirs.setOld(d)
|
||||
<a href="#setOld" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Sets the old directory string.
|
||||
#### Parameters
|
||||
`d` **`string`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
76
docs/nature/doc.md
Normal file
76
docs/nature/doc.md
Normal file
@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Module doc
|
||||
description: command-line doc rendering
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "Nature"
|
||||
---
|
||||
|
||||
|
||||
## Introduction
|
||||
The doc module contains a small set of functions
|
||||
used by the Greenhouse pager to render parts of the documentation pages.
|
||||
This is only documented for the sake of it. It's only intended use
|
||||
is by the Greenhouse pager.
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#renderInfoBlock">renderInfoBlock(type, text)</a>|Renders an info block. An info block is a block of text with|
|
||||
|<a href="#renderCodeBlock">renderCodeBlock(text)</a>|Assembles and renders a code block. This returns|
|
||||
|<a href="#highlight">highlight(text)</a>|Performs basic Lua code highlighting.|
|
||||
<hr>
|
||||
<div id='highlight'>
|
||||
<h4 class='heading'>
|
||||
doc.highlight(text)
|
||||
<a href="#highlight" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Performs basic Lua code highlighting.
|
||||
#### Parameters
|
||||
`text` **`string`**
|
||||
Code/text to do highlighting on.
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='renderCodeBlock'>
|
||||
<h4 class='heading'>
|
||||
doc.renderCodeBlock(text)
|
||||
<a href="#renderCodeBlock" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Assembles and renders a code block. This returns
|
||||
the supplied text based on the number of command line columns,
|
||||
and styles it to resemble a code block.
|
||||
#### Parameters
|
||||
`text` **`string`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='renderInfoBlock'>
|
||||
<h4 class='heading'>
|
||||
doc.renderInfoBlock(type, text)
|
||||
<a href="#renderInfoBlock" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Renders an info block. An info block is a block of text with
|
||||
an icon and styled text block.
|
||||
#### Parameters
|
||||
`type` **`string`**
|
||||
Type of info block. The only one specially styled is the `warning`.
|
||||
|
||||
`text` **`string`**
|
||||
|
||||
|
||||
</div>
|
||||
|
108
editor.go
108
editor.go
@ -1,108 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
// #interface editor
|
||||
// interactions for Hilbish's line reader
|
||||
// The hilbish.editor interface provides functions to
|
||||
// directly interact with the line editor in use.
|
||||
func editorLoader(rtm *rt.Runtime) *rt.Table {
|
||||
exports := map[string]util.LuaExport{
|
||||
"insert": {editorInsert, 1, false},
|
||||
"setVimRegister": {editorSetRegister, 1, false},
|
||||
"getVimRegister": {editorGetRegister, 2, false},
|
||||
"getLine": {editorGetLine, 0, false},
|
||||
"readChar": {editorReadChar, 0, false},
|
||||
}
|
||||
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// insert(text)
|
||||
// Inserts text into the Hilbish command line.
|
||||
// #param text string
|
||||
func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lr.rl.Insert(text)
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// setVimRegister(register, text)
|
||||
// Sets the vim register at `register` to hold the passed text.
|
||||
// #aram register string
|
||||
// #param text string
|
||||
func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
register, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lr.rl.SetRegisterBuf(register, []rune(text))
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// getVimRegister(register) -> string
|
||||
// Returns the text that is at the register.
|
||||
// #param register string
|
||||
func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
register, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := lr.rl.GetFromRegister(register)
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// getLine() -> string
|
||||
// Returns the current input line.
|
||||
// #returns string
|
||||
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
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// getChar() -> string
|
||||
// Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
func editorReadChar(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
buf := lr.rl.ReadChar()
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
@ -11,4 +11,8 @@ function commander.deregister(name) end
|
||||
---
|
||||
function commander.register(name, cb) end
|
||||
|
||||
--- Returns all registered commanders. Returns a list of tables with the following keys:
|
||||
--- - `exec`: The function used to run the commander. Commanders require args and sinks to be passed.
|
||||
function commander.registry() end
|
||||
|
||||
return commander
|
||||
|
@ -34,6 +34,10 @@ function fs.join(...path) end
|
||||
---
|
||||
function fs.mkdir(name, recursive) end
|
||||
|
||||
--- Returns a pair of connected files, also known as a pipe.
|
||||
--- The type returned is a Lua file, same as returned from `io` functions.
|
||||
function fs.fpipe() end
|
||||
|
||||
--- Returns a list of all files and directories in the provided path.
|
||||
function fs.readdir(path) end
|
||||
|
||||
|
@ -7,27 +7,6 @@ local hilbish = {}
|
||||
--- @param cmd string
|
||||
function hilbish.aliases.add(alias, cmd) end
|
||||
|
||||
--- This is the same as the `hilbish.runnerMode` function.
|
||||
--- It takes a callback, which will be used to execute all interactive input.
|
||||
--- In normal cases, neither callbacks should be overrided by the user,
|
||||
--- as the higher level functions listed below this will handle it.
|
||||
function hilbish.runner.setMode(cb) end
|
||||
|
||||
--- Returns the current input line.
|
||||
function hilbish.editor.getLine() end
|
||||
|
||||
--- Returns the text that is at the register.
|
||||
function hilbish.editor.getVimRegister(register) end
|
||||
|
||||
--- Inserts text into the Hilbish command line.
|
||||
function hilbish.editor.insert(text) end
|
||||
|
||||
--- Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
function hilbish.editor.getChar() end
|
||||
|
||||
--- Sets the vim register at `register` to hold the passed text.
|
||||
function hilbish.editor.setVimRegister(register, text) end
|
||||
|
||||
--- Return binaries/executables based on the provided parameters.
|
||||
--- This function is meant to be used as a helper in a command completion handler.
|
||||
---
|
||||
@ -131,18 +110,6 @@ function hilbish.prompt(str, typ) end
|
||||
--- Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
|
||||
function hilbish.read(prompt) end
|
||||
|
||||
--- Runs `cmd` in Hilbish's shell script interpreter.
|
||||
function hilbish.run(cmd, returnOut) end
|
||||
|
||||
--- Sets the execution/runner mode for interactive Hilbish.
|
||||
--- This determines whether Hilbish wll try to run input as Lua
|
||||
--- and/or sh or only do one of either.
|
||||
--- Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||
--- sh, and lua. It also accepts a function, to which if it is passed one
|
||||
--- will call it to execute user input instead.
|
||||
--- Read [about runner mode](../features/runner-mode) for more information.
|
||||
function hilbish.runnerMode(mode) end
|
||||
|
||||
--- Executed the `cb` function after a period of `time`.
|
||||
--- This creates a Timer that starts ticking immediately.
|
||||
function hilbish.timeout(cb, time) end
|
||||
@ -162,28 +129,6 @@ function hilbish.jobs:foreground() end
|
||||
--- or `load`, but is appropriated for the runner interface.
|
||||
function hilbish.runner.lua(cmd) end
|
||||
|
||||
--- Sets/toggles the option of automatically flushing output.
|
||||
--- A call with no argument will toggle the value.
|
||||
--- @param auto boolean|nil
|
||||
function hilbish:autoFlush(auto) end
|
||||
|
||||
--- Flush writes all buffered input to the sink.
|
||||
function hilbish:flush() end
|
||||
|
||||
--- Reads a liine of input from the sink.
|
||||
--- @returns string
|
||||
function hilbish:read() end
|
||||
|
||||
--- Reads all input from the sink.
|
||||
--- @returns string
|
||||
function hilbish:readAll() end
|
||||
|
||||
--- Writes data to a sink.
|
||||
function hilbish:write(str) end
|
||||
|
||||
--- Writes data to a sink with a newline at the end.
|
||||
function hilbish:writeln(str) end
|
||||
|
||||
--- Starts running the job.
|
||||
function hilbish.jobs:start() end
|
||||
|
||||
@ -194,10 +139,6 @@ function hilbish.jobs:stop() end
|
||||
--- It will throw if any error occurs.
|
||||
function hilbish.module.load(path) end
|
||||
|
||||
--- Runs a command in Hilbish's shell script interpreter.
|
||||
--- This is the equivalent of using `source`.
|
||||
function hilbish.runner.sh(cmd) end
|
||||
|
||||
--- Starts a timer.
|
||||
function hilbish.timers:start() end
|
||||
|
||||
@ -256,4 +197,26 @@ function hilbish.timers.create(type, time, callback) end
|
||||
--- Retrieves a timer via its ID.
|
||||
function hilbish.timers.get(id) end
|
||||
|
||||
--- Sets/toggles the option of automatically flushing output.
|
||||
--- A call with no argument will toggle the value.
|
||||
--- @param auto boolean|nil
|
||||
function hilbish:autoFlush(auto) end
|
||||
|
||||
--- Flush writes all buffered input to the sink.
|
||||
function hilbish:flush() end
|
||||
|
||||
--- Reads a liine of input from the sink.
|
||||
--- @returns string
|
||||
function hilbish:read() end
|
||||
|
||||
--- Reads all input from the sink.
|
||||
--- @returns string
|
||||
function hilbish:readAll() end
|
||||
|
||||
--- Writes data to a sink.
|
||||
function hilbish:write(str) end
|
||||
|
||||
--- Writes data to a sink with a newline at the end.
|
||||
function hilbish:writeln(str) end
|
||||
|
||||
return hilbish
|
||||
|
32
emmyLuaDocs/readline.lua
Normal file
32
emmyLuaDocs/readline.lua
Normal file
@ -0,0 +1,32 @@
|
||||
--- @meta
|
||||
|
||||
local readline = {}
|
||||
|
||||
--- Deletes characters in the line by the given amount.
|
||||
function readline:deleteByAmount(amount) end
|
||||
|
||||
--- Returns the current input line.
|
||||
function readline:getLine() end
|
||||
|
||||
--- Returns the text that is at the register.
|
||||
function readline:getVimRegister(register) end
|
||||
|
||||
--- Inserts text into the Hilbish command line.
|
||||
function readline:insert(text) end
|
||||
|
||||
--- Prints a message *before* the prompt without it being interrupted by user input.
|
||||
function readline:log(text) end
|
||||
|
||||
--- Creates a new readline instance.
|
||||
function readline.new() end
|
||||
|
||||
--- Reads input from the user.
|
||||
function readline:read() end
|
||||
|
||||
--- Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
function readline:getChar() end
|
||||
|
||||
--- Sets the vim register at `register` to hold the passed text.
|
||||
function readline:setVimRegister(register, text) end
|
||||
|
||||
return readline
|
16
emmyLuaDocs/snail.lua
Normal file
16
emmyLuaDocs/snail.lua
Normal file
@ -0,0 +1,16 @@
|
||||
--- @meta
|
||||
|
||||
local snail = {}
|
||||
|
||||
--- Changes the directory of the snail instance.
|
||||
--- The interpreter keeps its set directory even when the Hilbish process changes
|
||||
--- directory, so this should be called on the `hilbish.cd` hook.
|
||||
function snail:dir(path) end
|
||||
|
||||
--- Creates a new Snail instance.
|
||||
function snail.new() end
|
||||
|
||||
--- Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
||||
function snail:run(command, streams) end
|
||||
|
||||
return snail
|
83
emmyLuaDocs/util.lua
Normal file
83
emmyLuaDocs/util.lua
Normal file
@ -0,0 +1,83 @@
|
||||
--- @meta
|
||||
|
||||
local util = {}
|
||||
|
||||
---
|
||||
function util.AbbrevHome changes the user's home directory in the path string to ~ (tilde) end
|
||||
|
||||
---
|
||||
function util. end
|
||||
|
||||
---
|
||||
function util.DoFile runs the contents of the file in the Lua runtime. end
|
||||
|
||||
---
|
||||
function util.DoString runs the code string in the Lua runtime. end
|
||||
|
||||
--- directory.
|
||||
function util.ExpandHome expands ~ (tilde) in the path, changing it to the user home end
|
||||
|
||||
---
|
||||
function util. end
|
||||
|
||||
---
|
||||
function util.ForEach loops through a Lua table. end
|
||||
|
||||
---
|
||||
function util. end
|
||||
|
||||
--- a string and a closure.
|
||||
function util.HandleStrCallback handles function parameters for Go functions which take end
|
||||
|
||||
---
|
||||
function util. end
|
||||
|
||||
---
|
||||
function util. end
|
||||
|
||||
---
|
||||
function util.SetExports puts the Lua function exports in the table. end
|
||||
|
||||
--- It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
||||
function util.SetField sets a field in a table, adding docs for it. end
|
||||
|
||||
--- is one which has a metatable proxy to ensure no overrides happen to it.
|
||||
--- It sets the field in the table and sets the __docProp metatable on the
|
||||
--- user facing table.
|
||||
function util.SetFieldProtected sets a field in a protected table. A protected table end
|
||||
|
||||
--- Sets/toggles the option of automatically flushing output.
|
||||
--- A call with no argument will toggle the value.
|
||||
--- @param auto boolean|nil
|
||||
function util:autoFlush(auto) end
|
||||
|
||||
--- Flush writes all buffered input to the sink.
|
||||
function util:flush() end
|
||||
|
||||
---
|
||||
function util. end
|
||||
|
||||
--- Reads a liine of input from the sink.
|
||||
--- @returns string
|
||||
function util:read() end
|
||||
|
||||
--- Reads all input from the sink.
|
||||
--- @returns string
|
||||
function util:readAll() end
|
||||
|
||||
--- Writes data to a sink.
|
||||
function util:write(str) end
|
||||
|
||||
--- Writes data to a sink with a newline at the end.
|
||||
function util:writeln(str) end
|
||||
|
||||
---
|
||||
function util. end
|
||||
|
||||
---
|
||||
function util. end
|
||||
|
||||
---
|
||||
function util. end
|
||||
|
||||
return util
|
9
emmyLuaDocs/yarn.lua
Normal file
9
emmyLuaDocs/yarn.lua
Normal file
@ -0,0 +1,9 @@
|
||||
--- @meta
|
||||
|
||||
local yarn = {}
|
||||
|
||||
--- Creates a new, fresh Yarn thread.
|
||||
--- `fun` is the function that will run in the thread.
|
||||
function yarn.thread(fun) end
|
||||
|
||||
return yarn
|
493
exec.go
493
exec.go
@ -1,201 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
//"github.com/yuin/gopher-lua/parse"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
)
|
||||
|
||||
var errNotExec = errors.New("not executable")
|
||||
var errNotFound = errors.New("not found")
|
||||
var runnerMode rt.Value = rt.StringValue("hybrid")
|
||||
|
||||
type execError struct{
|
||||
typ string
|
||||
cmd string
|
||||
code int
|
||||
colon bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (e execError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.cmd, e.typ)
|
||||
}
|
||||
|
||||
func (e execError) sprint() error {
|
||||
sep := " "
|
||||
if e.colon {
|
||||
sep = ": "
|
||||
}
|
||||
|
||||
return fmt.Errorf("hilbish: %s%s%s", e.cmd, sep, e.err.Error())
|
||||
}
|
||||
|
||||
func isExecError(err error) (execError, bool) {
|
||||
if exErr, ok := err.(execError); ok {
|
||||
return exErr, true
|
||||
}
|
||||
|
||||
fields := strings.Split(err.Error(), ": ")
|
||||
knownTypes := []string{
|
||||
"not-found",
|
||||
"not-executable",
|
||||
}
|
||||
|
||||
if len(fields) > 1 && contains(knownTypes, fields[1]) {
|
||||
var colon bool
|
||||
var e error
|
||||
switch fields[1] {
|
||||
case "not-found":
|
||||
e = errNotFound
|
||||
case "not-executable":
|
||||
colon = true
|
||||
e = errNotExec
|
||||
}
|
||||
|
||||
return execError{
|
||||
cmd: fields[0],
|
||||
typ: fields[1],
|
||||
colon: colon,
|
||||
err: e,
|
||||
}, true
|
||||
}
|
||||
|
||||
return execError{}, false
|
||||
}
|
||||
var runnerMode rt.Value = rt.NilValue
|
||||
|
||||
func runInput(input string, priv bool) {
|
||||
running = true
|
||||
cmdString := aliases.Resolve(input)
|
||||
hooks.Emit("command.preexec", input, cmdString)
|
||||
|
||||
rerun:
|
||||
var exitCode uint8
|
||||
var err error
|
||||
var cont bool
|
||||
// save incase it changes while prompting (For some reason)
|
||||
currentRunner := runnerMode
|
||||
if currentRunner.Type() == rt.StringType {
|
||||
switch currentRunner.AsString() {
|
||||
case "hybrid":
|
||||
_, _, err = handleLua(input)
|
||||
if err == nil {
|
||||
cmdFinish(0, input, priv)
|
||||
return
|
||||
}
|
||||
input, exitCode, cont, err = handleSh(input)
|
||||
case "hybridRev":
|
||||
_, _, _, err = handleSh(input)
|
||||
if err == nil {
|
||||
cmdFinish(0, input, priv)
|
||||
return
|
||||
}
|
||||
input, exitCode, err = handleLua(input)
|
||||
case "lua":
|
||||
input, exitCode, err = handleLua(input)
|
||||
case "sh":
|
||||
input, exitCode, cont, err = handleSh(input)
|
||||
}
|
||||
} else {
|
||||
// can only be a string or function so
|
||||
var runnerErr error
|
||||
input, exitCode, cont, runnerErr, err = runLuaRunner(currentRunner, input)
|
||||
runnerRun := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("run"))
|
||||
_, err := rt.Call1(l.MainThread(), runnerRun, rt.StringValue(input), rt.BoolValue(priv))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
cmdFinish(124, input, priv)
|
||||
return
|
||||
}
|
||||
// yep, we only use `err` to check for lua eval error
|
||||
// our actual error should only be a runner provided error at this point
|
||||
// command not found type, etc
|
||||
err = runnerErr
|
||||
}
|
||||
|
||||
if cont {
|
||||
input, err = reprompt(input)
|
||||
if err == nil {
|
||||
goto rerun
|
||||
} else if err == io.EOF {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if exErr, ok := isExecError(err); ok {
|
||||
hooks.Emit("command." + exErr.typ, exErr.cmd)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
}
|
||||
|
||||
func reprompt(input string) (string, error) {
|
||||
for {
|
||||
in, err := continuePrompt(strings.TrimSuffix(input, "\\"))
|
||||
if err != nil {
|
||||
lr.SetPrompt(fmtPrompt(prompt))
|
||||
return input, err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(in, "\\") {
|
||||
continue
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
}
|
||||
|
||||
func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8, continued bool, runnerErr, err error) {
|
||||
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false)
|
||||
err = rt.Call(l.MainThread(), runr, []rt.Value{rt.StringValue(userInput)}, term)
|
||||
if err != nil {
|
||||
return "", 124, false, nil, err
|
||||
}
|
||||
|
||||
var runner *rt.Table
|
||||
var ok bool
|
||||
runnerRet := term.Get(0)
|
||||
if runner, ok = runnerRet.TryTable(); !ok {
|
||||
fmt.Fprintln(os.Stderr, "runner did not return a table")
|
||||
exitCode = 125
|
||||
input = userInput
|
||||
return
|
||||
}
|
||||
|
||||
if code, ok := runner.Get(rt.StringValue("exitCode")).TryInt(); ok {
|
||||
exitCode = uint8(code)
|
||||
}
|
||||
|
||||
if inp, ok := runner.Get(rt.StringValue("input")).TryString(); ok {
|
||||
input = inp
|
||||
}
|
||||
|
||||
if errStr, ok := runner.Get(rt.StringValue("err")).TryString(); ok {
|
||||
runnerErr = fmt.Errorf("%s", errStr)
|
||||
}
|
||||
|
||||
if c, ok := runner.Get(rt.StringValue("continue")).TryBool(); ok {
|
||||
continued = c
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func handleLua(input string) (string, uint8, error) {
|
||||
@ -225,301 +50,13 @@ func handleLua(input string) (string, uint8, error) {
|
||||
return cmdString, 125, err
|
||||
}
|
||||
|
||||
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
|
||||
if syntax.IsIncomplete(err) {
|
||||
if !interactive {
|
||||
return cmdString, 126, false, err
|
||||
}
|
||||
return cmdString, 126, true, err
|
||||
} else {
|
||||
if code, ok := interp.IsExitStatus(err); ok {
|
||||
return cmdString, code, false, nil
|
||||
} else {
|
||||
return cmdString, 126, false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cmdString, 0, false, nil
|
||||
}
|
||||
|
||||
// Run command in sh interpreter
|
||||
func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
|
||||
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
runner, _ := interp.New()
|
||||
|
||||
var stdout io.Writer
|
||||
var stderr io.Writer
|
||||
if terminalOut {
|
||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr)(runner)
|
||||
} else {
|
||||
stdout = new(bytes.Buffer)
|
||||
stderr = new(bytes.Buffer)
|
||||
interp.StdIO(os.Stdin, stdout, stderr)(runner)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
printer := syntax.NewPrinter()
|
||||
|
||||
var bg bool
|
||||
for _, stmt := range file.Stmts {
|
||||
bg = false
|
||||
if stmt.Background {
|
||||
bg = true
|
||||
printer.Print(buf, stmt.Cmd)
|
||||
|
||||
stmtStr := buf.String()
|
||||
buf.Reset()
|
||||
jobs.add(stmtStr, []string{}, "")
|
||||
}
|
||||
|
||||
interp.ExecHandler(execHandle(bg))(runner)
|
||||
err = runner.Run(context.TODO(), stmt)
|
||||
if err != nil {
|
||||
return stdout, stderr, err
|
||||
}
|
||||
}
|
||||
|
||||
return stdout, stderr, nil
|
||||
}
|
||||
|
||||
func execHandle(bg bool) interp.ExecHandlerFunc {
|
||||
return func(ctx context.Context, args []string) error {
|
||||
_, argstring := splitInput(strings.Join(args, " "))
|
||||
// i dont really like this but it works
|
||||
if aliases.All()[args[0]] != "" {
|
||||
for i, arg := range args {
|
||||
if strings.Contains(arg, " ") {
|
||||
args[i] = fmt.Sprintf("\"%s\"", arg)
|
||||
}
|
||||
}
|
||||
_, argstring = splitInput(strings.Join(args, " "))
|
||||
|
||||
// If alias was found, use command alias
|
||||
argstring = aliases.Resolve(argstring)
|
||||
var err error
|
||||
args, err = shell.Fields(argstring, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If command is defined in Lua then run it
|
||||
luacmdArgs := rt.NewTable()
|
||||
for i, str := range args[1:] {
|
||||
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
|
||||
}
|
||||
|
||||
hc := interp.HandlerCtx(ctx)
|
||||
if commands[args[0]] != nil {
|
||||
stdin := newSinkInput(hc.Stdin)
|
||||
stdout := newSinkOutput(hc.Stdout)
|
||||
stderr := newSinkOutput(hc.Stderr)
|
||||
|
||||
sinks := rt.NewTable()
|
||||
sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.ud))
|
||||
sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.ud))
|
||||
sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.ud))
|
||||
|
||||
luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(commands[args[0]]), rt.TableValue(luacmdArgs), rt.TableValue(sinks))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
|
||||
return interp.NewExitStatus(1)
|
||||
}
|
||||
|
||||
var exitcode uint8
|
||||
|
||||
if code, ok := luaexitcode.TryInt(); ok {
|
||||
exitcode = uint8(code)
|
||||
} else if luaexitcode != rt.NilValue {
|
||||
// deregister commander
|
||||
delete(commands, args[0])
|
||||
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0])
|
||||
}
|
||||
|
||||
return interp.NewExitStatus(exitcode)
|
||||
}
|
||||
|
||||
err := lookpath(args[0])
|
||||
if err == errNotExec {
|
||||
return execError{
|
||||
typ: "not-executable",
|
||||
cmd: args[0],
|
||||
code: 126,
|
||||
colon: true,
|
||||
err: errNotExec,
|
||||
}
|
||||
} else if err != nil {
|
||||
return execError{
|
||||
typ: "not-found",
|
||||
cmd: args[0],
|
||||
code: 127,
|
||||
err: errNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
killTimeout := 2 * time.Second
|
||||
// from here is basically copy-paste of the default exec handler from
|
||||
// sh/interp but with our job handling
|
||||
path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
|
||||
if err != nil {
|
||||
fmt.Fprintln(hc.Stderr, err)
|
||||
return interp.NewExitStatus(127)
|
||||
}
|
||||
|
||||
env := hc.Env
|
||||
envList := make([]string, 0, 64)
|
||||
env.Each(func(name string, vr expand.Variable) bool {
|
||||
if !vr.IsSet() {
|
||||
// If a variable is set globally but unset in the
|
||||
// runner, we need to ensure it's not part of the final
|
||||
// list. Seems like zeroing the element is enough.
|
||||
// This is a linear search, but this scenario should be
|
||||
// rare, and the number of variables shouldn't be large.
|
||||
for i, kv := range envList {
|
||||
if strings.HasPrefix(kv, name+"=") {
|
||||
envList[i] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
if vr.Exported && vr.Kind == expand.String {
|
||||
envList = append(envList, name+"="+vr.String())
|
||||
}
|
||||
return true
|
||||
})
|
||||
cmd := exec.Cmd{
|
||||
Path: path,
|
||||
Args: args,
|
||||
Env: envList,
|
||||
Dir: hc.Dir,
|
||||
Stdin: hc.Stdin,
|
||||
Stdout: hc.Stdout,
|
||||
Stderr: hc.Stderr,
|
||||
}
|
||||
|
||||
var j *job
|
||||
if bg {
|
||||
j = jobs.getLatest()
|
||||
j.setHandle(&cmd)
|
||||
err = j.start()
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if done := ctx.Done(); done != nil {
|
||||
go func() {
|
||||
<-done
|
||||
|
||||
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
||||
cmd.Process.Signal(os.Kill)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: don't temporarily leak this goroutine
|
||||
// if the program stops itself with the
|
||||
// interrupt.
|
||||
go func() {
|
||||
time.Sleep(killTimeout)
|
||||
cmd.Process.Signal(os.Kill)
|
||||
}()
|
||||
cmd.Process.Signal(os.Interrupt)
|
||||
}()
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
}
|
||||
|
||||
exit := handleExecErr(err)
|
||||
|
||||
if bg {
|
||||
j.exitCode = int(exit)
|
||||
j.finish()
|
||||
}
|
||||
return interp.NewExitStatus(exit)
|
||||
}
|
||||
}
|
||||
|
||||
func handleExecErr(err error) (exit uint8) {
|
||||
ctx := context.TODO()
|
||||
|
||||
switch x := err.(type) {
|
||||
case *exec.ExitError:
|
||||
// started, but errored - default to 1 if OS
|
||||
// doesn't have exit statuses
|
||||
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
||||
if status.Signaled() {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
exit = uint8(128 + status.Signal())
|
||||
return
|
||||
}
|
||||
exit = uint8(status.ExitStatus())
|
||||
return
|
||||
}
|
||||
exit = 1
|
||||
return
|
||||
case *exec.Error:
|
||||
// did not start
|
||||
//fmt.Fprintf(hc.Stderr, "%v\n", err)
|
||||
exit = 127
|
||||
default: return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
func lookpath(file string) error { // custom lookpath function so we know if a command is found *and* is executable
|
||||
var skip []string
|
||||
if runtime.GOOS == "windows" {
|
||||
skip = []string{"./", "../", "~/", "C:"}
|
||||
} else {
|
||||
skip = []string{"./", "/", "../", "~/"}
|
||||
}
|
||||
for _, s := range skip {
|
||||
if strings.HasPrefix(file, s) {
|
||||
return findExecutable(file, false, false)
|
||||
}
|
||||
}
|
||||
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
||||
path := filepath.Join(dir, file)
|
||||
err := findExecutable(path, true, false)
|
||||
if err == errNotExec {
|
||||
return err
|
||||
} else if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return os.ErrNotExist
|
||||
}
|
||||
|
||||
func splitInput(input string) ([]string, string) {
|
||||
// end my suffering
|
||||
// TODO: refactor this garbage
|
||||
quoted := false
|
||||
startlastcmd := false
|
||||
lastcmddone := false
|
||||
cmdArgs := []string{}
|
||||
sb := &strings.Builder{}
|
||||
cmdstr := &strings.Builder{}
|
||||
lastcmd := "" //readline.GetHistory(readline.HistorySize() - 1)
|
||||
|
||||
for _, r := range input {
|
||||
if r == '"' {
|
||||
@ -535,22 +72,6 @@ func splitInput(input string) ([]string, string) {
|
||||
// if not quoted and there's a space then add to cmdargs
|
||||
cmdArgs = append(cmdArgs, sb.String())
|
||||
sb.Reset()
|
||||
} else if !quoted && r == '^' && startlastcmd && !lastcmddone {
|
||||
// if ^ is found, isnt in quotes and is
|
||||
// the second occurence of the character and is
|
||||
// the first time "^^" has been used
|
||||
cmdstr.WriteString(lastcmd)
|
||||
sb.WriteString(lastcmd)
|
||||
|
||||
startlastcmd = !startlastcmd
|
||||
lastcmddone = !lastcmddone
|
||||
|
||||
continue
|
||||
} else if !quoted && r == '^' && !lastcmddone {
|
||||
// if ^ is found, isnt in quotes and is the
|
||||
// first time of starting "^^"
|
||||
startlastcmd = !startlastcmd
|
||||
continue
|
||||
} else {
|
||||
sb.WriteRune(r)
|
||||
}
|
||||
@ -562,11 +83,3 @@ func splitInput(input string) ([]string, string) {
|
||||
|
||||
return cmdArgs, cmdstr.String()
|
||||
}
|
||||
|
||||
func cmdFinish(code uint8, cmdstr string, private bool) {
|
||||
util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)))
|
||||
// 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.Emit("command.exit", rt.IntValue(int64(code)), cmdstr, private)
|
||||
}
|
||||
|
33
go.mod
33
go.mod
@ -1,34 +1,37 @@
|
||||
module hilbish
|
||||
|
||||
go 1.17
|
||||
go 1.21
|
||||
|
||||
toolchain go1.22.2
|
||||
|
||||
require (
|
||||
github.com/arnodel/golua v0.0.0-20220221163911-dfcf252b6f86
|
||||
github.com/arnodel/golua v0.0.0-20230215163904-e0b5347eaaa1
|
||||
github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504
|
||||
github.com/blackfireio/osinfo v1.0.3
|
||||
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036
|
||||
github.com/blackfireio/osinfo v1.0.5
|
||||
github.com/maxlandon/readline v1.0.14
|
||||
github.com/pborman/getopt v1.1.0
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
|
||||
mvdan.cc/sh/v3 v3.5.1
|
||||
github.com/sahilm/fuzzy v0.1.1
|
||||
golang.org/x/sys v0.22.0
|
||||
golang.org/x/term v0.22.0
|
||||
mvdan.cc/sh/v3 v3.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||
github.com/arnodel/strftime v0.1.6 // indirect
|
||||
github.com/evilsocket/islazy v1.10.6 // indirect
|
||||
github.com/evilsocket/islazy v1.11.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
|
||||
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b
|
||||
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73
|
||||
|
||||
replace github.com/maxlandon/readline => ./readline
|
||||
replace github.com/maxlandon/readline => ./golibs/readline
|
||||
|
||||
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10
|
||||
|
||||
replace github.com/arnodel/golua => github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345
|
||||
replace github.com/arnodel/golua => github.com/Rosettea/golua v0.0.0-20241104031959-5551ea280f23
|
||||
|
87
go.sum
87
go.sum
@ -1,69 +1,46 @@
|
||||
github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345 h1:QNYjYDogUSiNUkffbhFSrSCtpZhofeiVYGFN2FI4wSs=
|
||||
github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
|
||||
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b h1:s5eDMhBk6H1BgipgLub/gv9qeyBaTuiHM0k3h2/9TSE=
|
||||
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs=
|
||||
github.com/Rosettea/golua v0.0.0-20241104031959-5551ea280f23 h1:mUZnT0gmDEmTkqXsbnDbuJ3CNil7DCOMiCQYgjbKIdI=
|
||||
github.com/Rosettea/golua v0.0.0-20241104031959-5551ea280f23/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
|
||||
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73 h1:zTTUJqNnrF2qf4LgygN8Oae5Uxn6ewH0hA8jyTCHfXw=
|
||||
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73/go.mod h1:YZalN5H7WNQw3DGij6IvHsEhn5YMW7M2FCwG6gnfKy4=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/arnodel/edit v0.0.0-20220202110212-dfc8d7a13890/go.mod h1:AcpttpuZBaL9xl8/CX+Em4fBTUbwIkJ66RiAsJlNrBk=
|
||||
github.com/arnodel/strftime v0.1.6 h1:0hc0pUvk8KhEMXE+htyaOUV42zNcf/csIbjzEFCJqsw=
|
||||
github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4KTKzJfWs=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504 h1:R1/AOzdMbopSliUTTEHvHbyNmnZ3YxY5GvdhTkpPsSY=
|
||||
github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504/go.mod h1:kHBCvAXJIatTX1pw6tLiOspjGc3MhUDRlog9yrCUS+k=
|
||||
github.com/blackfireio/osinfo v1.0.3 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c=
|
||||
github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
|
||||
github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/evilsocket/islazy v1.10.6 h1:MFq000a1ByoumoJWlytqg0qon0KlBeUfPsDjY0hK0bo=
|
||||
github.com/evilsocket/islazy v1.10.6/go.mod h1:OrwQGYg3DuZvXUfmH+KIZDjwTCbrjy48T24TUpGqVVw=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/blackfireio/osinfo v1.0.5 h1:6hlaWzfcpb87gRmznVf7wSdhysGqLRz9V/xuSdCEXrA=
|
||||
github.com/blackfireio/osinfo v1.0.5/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/evilsocket/islazy v1.11.0 h1:B5w6uuS6ki6iDG+aH/RFeoMb8ijQh/pGabewqp2UeJ0=
|
||||
github.com/evilsocket/islazy v1.11.0/go.mod h1:muYH4x5MB5YRdkxnrOtrXLIBX6LySj1uFIqys94LKdo=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw=
|
||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
|
||||
github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
|
||||
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
|
||||
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
|
||||
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
|
@ -47,7 +47,7 @@ type Recoverer func(event string, handler *Listener, err interface{})
|
||||
type Listener struct{
|
||||
typ listenerType
|
||||
once bool
|
||||
caller func(...interface{})
|
||||
caller func(...interface{}) rt.Value
|
||||
luaCaller *rt.Closure
|
||||
}
|
||||
|
||||
@ -73,10 +73,11 @@ func New(rtm *rt.Runtime) *Bait {
|
||||
}
|
||||
|
||||
// Emit throws an event.
|
||||
func (b *Bait) Emit(event string, args ...interface{}) {
|
||||
func (b *Bait) Emit(event string, args ...interface{}) []rt.Value {
|
||||
var returns []rt.Value
|
||||
handles := b.handlers[event]
|
||||
if handles == nil {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
for idx, handle := range handles {
|
||||
@ -97,28 +98,37 @@ func (b *Bait) Emit(event string, args ...interface{}) {
|
||||
}
|
||||
luaArgs = append(luaArgs, luarg)
|
||||
}
|
||||
_, err := rt.Call1(b.rtm.MainThread(), funcVal, luaArgs...)
|
||||
luaRet, err := rt.Call1(b.rtm.MainThread(), funcVal, luaArgs...)
|
||||
if err != nil {
|
||||
if event != "error" {
|
||||
b.Emit("error", event, handle.luaCaller, err.Error())
|
||||
return
|
||||
return nil
|
||||
}
|
||||
// if there is an error in an error event handler, panic instead
|
||||
// (calls the go recoverer function)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if luaRet != rt.NilValue {
|
||||
returns = append(returns, luaRet)
|
||||
}
|
||||
} else {
|
||||
handle.caller(args...)
|
||||
ret := handle.caller(args...)
|
||||
if ret != rt.NilValue {
|
||||
returns = append(returns, ret)
|
||||
}
|
||||
}
|
||||
|
||||
if handle.once {
|
||||
b.removeListener(event, idx)
|
||||
}
|
||||
}
|
||||
|
||||
return returns
|
||||
}
|
||||
|
||||
// On adds a Go function handler for an event.
|
||||
func (b *Bait) On(event string, handler func(...interface{})) *Listener {
|
||||
func (b *Bait) On(event string, handler func(...interface{}) rt.Value) *Listener {
|
||||
listener := &Listener{
|
||||
typ: goListener,
|
||||
caller: handler,
|
||||
@ -162,7 +172,7 @@ func (b *Bait) OffLua(event string, handler *rt.Closure) {
|
||||
}
|
||||
|
||||
// Once adds a Go function listener for an event that only runs once.
|
||||
func (b *Bait) Once(event string, handler func(...interface{})) *Listener {
|
||||
func (b *Bait) Once(event string, handler func(...interface{}) rt.Value) *Listener {
|
||||
listener := &Listener{
|
||||
typ: goListener,
|
||||
once: true,
|
||||
@ -226,27 +236,6 @@ func (b *Bait) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
||||
func handleHook(t *rt.Thread, c *rt.GoCont, name string, catcher *rt.Closure, args ...interface{}) {
|
||||
funcVal := rt.FunctionValue(catcher)
|
||||
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(t, funcVal, luaArgs...)
|
||||
if err != nil {
|
||||
e := rt.NewError(rt.StringValue(err.Error()))
|
||||
e = e.AddContext(c.Next(), 1)
|
||||
// panicking here won't actually cause hilbish to panic and instead will
|
||||
// print the error and remove the hook (look at emission recover from above)
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// catch(name, cb)
|
||||
// Catches an event. This function can be used to act on events.
|
||||
// #param name string The name of the hook.
|
||||
@ -370,7 +359,7 @@ func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
for i, v := range c.Etc() {
|
||||
ifaceSlice[i] = v
|
||||
}
|
||||
b.Emit(name, ifaceSlice...)
|
||||
ret := b.Emit(name, ifaceSlice...)
|
||||
|
||||
return c.Next(), nil
|
||||
return c.PushingNext(t.Runtime, ret...), nil
|
||||
}
|
||||
|
@ -17,8 +17,11 @@ In this example, a command with the name of `hello` is created
|
||||
that will print `Hello world!` to output. One question you may
|
||||
have is: What is the `sinks` parameter?
|
||||
|
||||
The `sinks` parameter is a table with 3 keys: `in`, `out`,
|
||||
and `err`. All of them are a @Sink.
|
||||
The `sinks` parameter is a table with 3 keys: `input`, `out`, and `err`.
|
||||
There is an `in` alias to `input`, but it requires using the string accessor syntax (`sinks['in']`)
|
||||
as `in` is also a Lua keyword, so `input` is preferred for use.
|
||||
All of them are a @Sink.
|
||||
In the future, `sinks.in` will be removed.
|
||||
|
||||
- `in` is the standard input.
|
||||
You may use the read functions on this sink to get input from the user.
|
||||
@ -40,11 +43,13 @@ import (
|
||||
type Commander struct{
|
||||
Events *bait.Bait
|
||||
Loader packagelib.Loader
|
||||
Commands map[string]*rt.Closure
|
||||
}
|
||||
|
||||
func New(rtm *rt.Runtime) Commander {
|
||||
c := Commander{
|
||||
func New(rtm *rt.Runtime) *Commander {
|
||||
c := &Commander{
|
||||
Events: bait.New(rtm),
|
||||
Commands: make(map[string]*rt.Closure),
|
||||
}
|
||||
c.Loader = packagelib.Loader{
|
||||
Load: c.loaderFunc,
|
||||
@ -58,6 +63,7 @@ func (c *Commander) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
exports := map[string]util.LuaExport{
|
||||
"register": util.LuaExport{c.cregister, 2, false},
|
||||
"deregister": util.LuaExport{c.cderegister, 1, false},
|
||||
"registry": util.LuaExport{c.cregistry, 0, false},
|
||||
}
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
@ -88,7 +94,7 @@ func (c *Commander) cregister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Events.Emit("commandRegister", cmdName, cmd)
|
||||
c.Commands[cmdName] = cmd
|
||||
|
||||
return ct.Next(), err
|
||||
}
|
||||
@ -105,7 +111,23 @@ func (c *Commander) cderegister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Events.Emit("commandDeregister", cmdName)
|
||||
delete(c.Commands, cmdName)
|
||||
|
||||
return ct.Next(), err
|
||||
}
|
||||
|
||||
// registry() -> table
|
||||
// Returns all registered commanders. Returns a list of tables with the following keys:
|
||||
// - `exec`: The function used to run the commander. Commanders require args and sinks to be passed.
|
||||
// #returns table
|
||||
func (c *Commander) cregistry(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
|
||||
registryLua := rt.NewTable()
|
||||
for cmdName, cmd := range c.Commands {
|
||||
cmdTbl := rt.NewTable()
|
||||
cmdTbl.Set(rt.StringValue("exec"), rt.FunctionValue(cmd))
|
||||
|
||||
registryLua.Set(rt.StringValue(cmdName), rt.TableValue(cmdTbl))
|
||||
}
|
||||
|
||||
return ct.PushingNext1(t.Runtime, rt.TableValue(registryLua)), nil
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
"github.com/arnodel/golua/lib/iolib"
|
||||
)
|
||||
|
||||
var Loader = packagelib.Loader{
|
||||
@ -36,6 +37,7 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
"dir": util.LuaExport{fdir, 1, false},
|
||||
"glob": util.LuaExport{fglob, 1, false},
|
||||
"join": util.LuaExport{fjoin, 0, true},
|
||||
"pipe": util.LuaExport{fpipe, 0, false},
|
||||
}
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
@ -94,12 +96,23 @@ func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
return nil, err
|
||||
}
|
||||
path = util.ExpandHome(strings.TrimSpace(path))
|
||||
oldWd, _ := os.Getwd()
|
||||
|
||||
abspath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.Chdir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
util.DoString(t.Runtime, fmt.Sprintf(`
|
||||
local bait = require 'bait'
|
||||
bait.throw('hilbish.cd', '%s', '%s')
|
||||
`, abspath, oldWd))
|
||||
|
||||
return c.Next(), err
|
||||
}
|
||||
|
||||
@ -226,6 +239,22 @@ func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
return c.Next(), err
|
||||
}
|
||||
|
||||
// fpipe() -> File, File
|
||||
// Returns a pair of connected files, also known as a pipe.
|
||||
// The type returned is a Lua file, same as returned from `io` functions.
|
||||
// #returns File
|
||||
// #returns File
|
||||
func fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
rf, wf, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rfLua := iolib.NewFile(rf, 0)
|
||||
wfLua := iolib.NewFile(wf, 0)
|
||||
|
||||
return c.PushingNext(t.Runtime, rfLua.Value(t.Runtime), wfLua.Value(t.Runtime)), nil
|
||||
}
|
||||
// readdir(path) -> table[string]
|
||||
// Returns a list of all files and directories in the provided path.
|
||||
// #param dir string
|
||||
|
@ -60,6 +60,8 @@ var (
|
||||
seqAltF = string([]byte{27, 102})
|
||||
seqAltR = string([]byte{27, 114}) // Used for alternative history
|
||||
seqAltBackspace = string([]byte{27, 127})
|
||||
seqPageUp = string([]byte{27, 91, 53, 126})
|
||||
seqPageDown = string([]byte{27, 91, 54, 126})
|
||||
)
|
||||
|
||||
const (
|
||||
@ -74,6 +76,8 @@ const (
|
||||
seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left
|
||||
|
||||
seqGetCursorPos = "\x1b6n" // response: "\x1b{Line};{Column}R"
|
||||
seqHideCursor = "\x1b[?25l"
|
||||
seqUnhideCursor = "\x1b[?25h"
|
||||
|
||||
seqCtrlLeftArrow = "\x1b[1;5D"
|
||||
seqCtrlRightArrow = "\x1b[1;5C"
|
||||
@ -139,52 +143,93 @@ const (
|
||||
|
||||
// TODO: return whether its actually a sequence or not
|
||||
// remedies the edge case of someone literally typing Ctrl-A for example.
|
||||
func (rl *Instance) ReadChar() string {
|
||||
func (rl *Readline) ReadChar() string {
|
||||
b := make([]byte, 1024)
|
||||
i, _ := os.Stdin.Read(b)
|
||||
r := []rune(string(b))
|
||||
s := string(r[:i])
|
||||
|
||||
switch b[0] {
|
||||
case charCtrlA: return "Ctrl-A"
|
||||
case charCtrlB: return "Ctrl-B"
|
||||
case charCtrlC: return "Ctrl-C"
|
||||
case charEOF: return "Ctrl-D"
|
||||
case charCtrlE: return "Ctrl-E"
|
||||
case charCtrlF: return "Ctrl-F"
|
||||
case charCtrlG: return "Ctrl-G"
|
||||
case charBackspace, charBackspace2: return "Backspace"
|
||||
case charTab: return "Tab"
|
||||
case charCtrlK: return "Ctrl-K"
|
||||
case charCtrlL: return "Ctrl-L"
|
||||
case charCtrlN: return "Ctrl-N"
|
||||
case charCtrlO: return "Ctrl-O"
|
||||
case charCtrlP: return "Ctrl-P"
|
||||
case charCtrlQ: return "Ctrl-Q"
|
||||
case charCtrlR: return "Ctrl-R"
|
||||
case charCtrlS: return "Ctrl-S"
|
||||
case charCtrlT: return "Ctrl-T"
|
||||
case charCtrlU: return "Ctrl-U"
|
||||
case charCtrlV: return "Ctrl-V"
|
||||
case charCtrlW: return "Ctrl-W"
|
||||
case charCtrlX: return "Ctrl-X"
|
||||
case charCtrlY: return "Ctrl-Y"
|
||||
case charCtrlZ: return "Ctrl-Z"
|
||||
case '\r': fallthrough
|
||||
case '\n': return "Enter"
|
||||
case charCtrlA:
|
||||
return "Ctrl-A"
|
||||
case charCtrlB:
|
||||
return "Ctrl-B"
|
||||
case charCtrlC:
|
||||
return "Ctrl-C"
|
||||
case charEOF:
|
||||
return "Ctrl-D"
|
||||
case charCtrlE:
|
||||
return "Ctrl-E"
|
||||
case charCtrlF:
|
||||
return "Ctrl-F"
|
||||
case charCtrlG:
|
||||
return "Ctrl-G"
|
||||
case charBackspace, charBackspace2:
|
||||
return "Backspace"
|
||||
case charTab:
|
||||
return "Tab"
|
||||
case charCtrlK:
|
||||
return "Ctrl-K"
|
||||
case charCtrlL:
|
||||
return "Ctrl-L"
|
||||
case charCtrlN:
|
||||
return "Ctrl-N"
|
||||
case charCtrlO:
|
||||
return "Ctrl-O"
|
||||
case charCtrlP:
|
||||
return "Ctrl-P"
|
||||
case charCtrlQ:
|
||||
return "Ctrl-Q"
|
||||
case charCtrlR:
|
||||
return "Ctrl-R"
|
||||
case charCtrlS:
|
||||
return "Ctrl-S"
|
||||
case charCtrlT:
|
||||
return "Ctrl-T"
|
||||
case charCtrlU:
|
||||
return "Ctrl-U"
|
||||
case charCtrlV:
|
||||
return "Ctrl-V"
|
||||
case charCtrlW:
|
||||
return "Ctrl-W"
|
||||
case charCtrlX:
|
||||
return "Ctrl-X"
|
||||
case charCtrlY:
|
||||
return "Ctrl-Y"
|
||||
case charCtrlZ:
|
||||
return "Ctrl-Z"
|
||||
case '\r':
|
||||
fallthrough
|
||||
case '\n':
|
||||
return "Enter"
|
||||
case charEscape:
|
||||
switch s {
|
||||
case string(charEscape): return "Escape"
|
||||
case seqUp: return "Up"
|
||||
case seqDown: return "Down"
|
||||
case seqBackwards: return "Left"
|
||||
case seqForwards: return "Right"
|
||||
case seqCtrlLeftArrow: return "Ctrl-Left"
|
||||
case seqCtrlRightArrow: return "Ctrl-Right"
|
||||
case seqCtrlDelete, seqCtrlDelete2: return "Ctrl-Delete"
|
||||
case seqHome, seqHomeSc: return "Home"
|
||||
case seqEnd, seqEndSc: return "End"
|
||||
case seqDelete, seqDelete2: return "Delete"
|
||||
case string(charEscape):
|
||||
return "Escape"
|
||||
case seqUp:
|
||||
return "Up"
|
||||
case seqDown:
|
||||
return "Down"
|
||||
case seqBackwards:
|
||||
return "Left"
|
||||
case seqForwards:
|
||||
return "Right"
|
||||
case seqCtrlLeftArrow:
|
||||
return "Ctrl-Left"
|
||||
case seqCtrlRightArrow:
|
||||
return "Ctrl-Right"
|
||||
case seqCtrlDelete, seqCtrlDelete2:
|
||||
return "Ctrl-Delete"
|
||||
case seqHome, seqHomeSc:
|
||||
return "Home"
|
||||
case seqEnd, seqEndSc:
|
||||
return "End"
|
||||
case seqDelete, seqDelete2:
|
||||
return "Delete"
|
||||
case seqPageUp:
|
||||
return "Page-Up"
|
||||
case seqPageDown:
|
||||
return "Page-Down"
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,13 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// initGrid - Grid display details. Called each time we want to be sure to have
|
||||
// a working completion group either immediately, or later on. Generally defered.
|
||||
func (g *CompletionGroup) initGrid(rl *Instance) {
|
||||
func (g *CompletionGroup) initGrid(rl *Readline) {
|
||||
|
||||
// Compute size of each completion item box
|
||||
tcMaxLength := 1
|
||||
@ -44,7 +45,7 @@ func (g *CompletionGroup) initGrid(rl *Instance) {
|
||||
}
|
||||
|
||||
// moveTabGridHighlight - Moves the highlighting for currently selected completion item (grid display)
|
||||
func (g *CompletionGroup) moveTabGridHighlight(rl *Instance, x, y int) (done bool, next bool) {
|
||||
func (g *CompletionGroup) moveTabGridHighlight(rl *Readline, x, y int) (done bool, next bool) {
|
||||
|
||||
g.tcPosX += x
|
||||
g.tcPosY += y
|
||||
@ -96,7 +97,7 @@ func (g *CompletionGroup) moveTabGridHighlight(rl *Instance, x, y int) (done boo
|
||||
}
|
||||
|
||||
// writeGrid - A grid completion string
|
||||
func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
|
||||
func (g *CompletionGroup) writeGrid(rl *Readline) (comp string) {
|
||||
|
||||
// If group title, print it and adjust offset.
|
||||
if g.Name != "" {
|
@ -14,6 +14,7 @@ type CompletionGroup struct {
|
||||
Suggestions []string
|
||||
Aliases map[string]string // A candidate has an alternative name (ex: --long, -l option flags)
|
||||
Descriptions map[string]string // Items descriptions
|
||||
ItemDisplays map[string]string // What to display the item as (can be used for styling items)
|
||||
DisplayType TabDisplayType // Map, list or normal
|
||||
MaxLength int // Each group can be limited in the number of comps offered
|
||||
|
||||
@ -48,7 +49,7 @@ type CompletionGroup struct {
|
||||
}
|
||||
|
||||
// init - The completion group computes and sets all its values, and is then ready to work.
|
||||
func (g *CompletionGroup) init(rl *Instance) {
|
||||
func (g *CompletionGroup) init(rl *Readline) {
|
||||
|
||||
// Details common to all displays
|
||||
g.checkCycle(rl) // Based on the number of groups given to the shell, allows cycling or not
|
||||
@ -69,7 +70,7 @@ func (g *CompletionGroup) init(rl *Instance) {
|
||||
// updateTabFind - When searching through all completion groups (whether it be command history or not),
|
||||
// we ask each of them to filter its own items and return the results to the shell for aggregating them.
|
||||
// The rx parameter is passed, as the shell already checked that the search pattern is valid.
|
||||
func (g *CompletionGroup) updateTabFind(rl *Instance) {
|
||||
func (g *CompletionGroup) updateTabFind(rl *Readline) {
|
||||
|
||||
suggs := rl.Searcher(rl.search, g.Suggestions)
|
||||
// We perform filter right here, so we create a new completion group, and populate it with our results.
|
||||
@ -96,7 +97,7 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) {
|
||||
}
|
||||
|
||||
// checkCycle - Based on the number of groups given to the shell, allows cycling or not
|
||||
func (g *CompletionGroup) checkCycle(rl *Instance) {
|
||||
func (g *CompletionGroup) checkCycle(rl *Readline) {
|
||||
if len(rl.tcGroups) == 1 {
|
||||
g.allowCycle = true
|
||||
}
|
||||
@ -107,7 +108,7 @@ func (g *CompletionGroup) checkCycle(rl *Instance) {
|
||||
}
|
||||
|
||||
// checkMaxLength - Based on the number of groups given to the shell, check/set MaxLength defaults
|
||||
func (g *CompletionGroup) checkMaxLength(rl *Instance) {
|
||||
func (g *CompletionGroup) checkMaxLength(rl *Readline) {
|
||||
|
||||
// This means the user forgot to set it
|
||||
if g.MaxLength == 0 {
|
||||
@ -146,7 +147,7 @@ func checkNilItems(groups []*CompletionGroup) (checked []*CompletionGroup) {
|
||||
|
||||
// writeCompletion - This function produces a formatted string containing all appropriate items
|
||||
// and according to display settings. This string is then appended to the main completion string.
|
||||
func (g *CompletionGroup) writeCompletion(rl *Instance) (comp string) {
|
||||
func (g *CompletionGroup) writeCompletion(rl *Readline) (comp string) {
|
||||
|
||||
// Avoids empty groups in suggestions
|
||||
if len(g.Suggestions) == 0 {
|
||||
@ -168,7 +169,7 @@ func (g *CompletionGroup) writeCompletion(rl *Instance) (comp string) {
|
||||
|
||||
// getCurrentCell - The completion groups computes the current cell value,
|
||||
// depending on its display type and its different parameters
|
||||
func (g *CompletionGroup) getCurrentCell(rl *Instance) string {
|
||||
func (g *CompletionGroup) getCurrentCell(rl *Readline) string {
|
||||
|
||||
switch g.DisplayType {
|
||||
case TabDisplayGrid:
|
@ -8,7 +8,7 @@ import (
|
||||
|
||||
// initList - List display details. Because of the way alternative completions
|
||||
// are handled, MaxLength cannot be set when there are alternative completions.
|
||||
func (g *CompletionGroup) initList(rl *Instance) {
|
||||
func (g *CompletionGroup) initList(rl *Readline) {
|
||||
|
||||
// We may only ever have two different
|
||||
// columns: (suggestions, and alternatives)
|
||||
@ -53,7 +53,7 @@ func (g *CompletionGroup) initList(rl *Instance) {
|
||||
|
||||
// moveTabListHighlight - Moves the highlighting for currently selected completion item (list display)
|
||||
// We don't care about the x, because only can have 2 columns of selectable choices (--long and -s)
|
||||
func (g *CompletionGroup) moveTabListHighlight(rl *Instance, x, y int) (done bool, next bool) {
|
||||
func (g *CompletionGroup) moveTabListHighlight(rl *Readline, x, y int) (done bool, next bool) {
|
||||
|
||||
// We dont' pass to x, because not managed by callers
|
||||
g.tcPosY += x
|
||||
@ -153,7 +153,7 @@ func (g *CompletionGroup) moveTabListHighlight(rl *Instance, x, y int) (done boo
|
||||
}
|
||||
|
||||
// writeList - A list completion string
|
||||
func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
|
||||
func (g *CompletionGroup) writeList(rl *Readline) (comp string) {
|
||||
|
||||
// Print group title and adjust offset if there is one.
|
||||
if g.Name != "" {
|
||||
@ -217,6 +217,11 @@ func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
|
||||
alt = strings.Repeat(" ", maxLengthAlt+1) // + 2 to keep account of spaces
|
||||
}
|
||||
|
||||
styledSugg, ok := g.ItemDisplays[item]
|
||||
if ok {
|
||||
sugg = fmt.Sprintf("\r%s%-"+cellWidth+"s", highlight(y, 0), fmtEscape(styledSugg))
|
||||
}
|
||||
|
||||
// Description
|
||||
description := g.Descriptions[g.Suggestions[i]]
|
||||
if len(description) > maxDescWidth {
|
||||
@ -244,7 +249,7 @@ func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) getListPad() (pad int) {
|
||||
func (rl *Readline) getListPad() (pad int) {
|
||||
for _, group := range rl.tcGroups {
|
||||
if group.DisplayType == TabDisplayList {
|
||||
for i := range group.Suggestions {
|
@ -7,7 +7,7 @@ import (
|
||||
|
||||
// initMap - Map display details. Called each time we want to be sure to have
|
||||
// a working completion group either immediately, or later on. Generally defered.
|
||||
func (g *CompletionGroup) initMap(rl *Instance) {
|
||||
func (g *CompletionGroup) initMap(rl *Readline) {
|
||||
|
||||
// We make the map anyway, especially if we need to use it later
|
||||
if g.Descriptions == nil {
|
||||
@ -35,7 +35,7 @@ func (g *CompletionGroup) initMap(rl *Instance) {
|
||||
}
|
||||
|
||||
// moveTabMapHighlight - Moves the highlighting for currently selected completion item (map display)
|
||||
func (g *CompletionGroup) moveTabMapHighlight(rl *Instance, x, y int) (done bool, next bool) {
|
||||
func (g *CompletionGroup) moveTabMapHighlight(rl *Readline, x, y int) (done bool, next bool) {
|
||||
|
||||
g.tcPosY += x
|
||||
g.tcPosY += y
|
||||
@ -72,7 +72,7 @@ func (g *CompletionGroup) moveTabMapHighlight(rl *Instance, x, y int) (done bool
|
||||
}
|
||||
|
||||
// writeMap - A map or list completion string
|
||||
func (g *CompletionGroup) writeMap(rl *Instance) (comp string) {
|
||||
func (g *CompletionGroup) writeMap(rl *Readline) (comp string) {
|
||||
|
||||
if g.Name != "" {
|
||||
// Print group title (changes with line returns depending on type)
|
@ -1,6 +1,7 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@ -27,7 +28,7 @@ func leftMost() []byte {
|
||||
|
||||
var rxRcvCursorPos = regexp.MustCompile("^\x1b([0-9]+);([0-9]+)R$")
|
||||
|
||||
func (rl *Instance) getCursorPos() (x int, y int) {
|
||||
func (rl *Readline) getCursorPos() (x int, y int) {
|
||||
if !rl.EnableGetCursorPos {
|
||||
return -1, -1
|
||||
}
|
||||
@ -68,6 +69,40 @@ func (rl *Instance) getCursorPos() (x int, y int) {
|
||||
// This means that they are not used to keep any reference point when
|
||||
// when we internally move around clearning and printing things
|
||||
|
||||
/*
|
||||
func moveCursorUpBuffered(i int) {
|
||||
if i < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(rl.bufferedOut, "\x1b[%dA", i)
|
||||
}
|
||||
|
||||
func moveCursorDownBuffered(i int) {
|
||||
if i < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(rl.bufferedOut, "\x1b[%dB", i)
|
||||
}
|
||||
|
||||
func moveCursorForwardsBuffered(i int) {
|
||||
if i < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(rl.bufferedOut, "\x1b[%dC", i)
|
||||
}
|
||||
|
||||
func moveCursorUpBuffered(i int) {
|
||||
if i < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(rl.bufferedOut, "\x1b[%dD", i)
|
||||
}
|
||||
*/
|
||||
|
||||
func moveCursorUp(i int) {
|
||||
if i < 1 {
|
||||
return
|
||||
@ -100,7 +135,15 @@ func moveCursorBackwards(i int) {
|
||||
printf("\x1b[%dD", i)
|
||||
}
|
||||
|
||||
func (rl *Instance) backspace(forward bool) {
|
||||
func hideCursor() {
|
||||
print(seqHideCursor)
|
||||
}
|
||||
|
||||
func unhideCursor() {
|
||||
print(seqUnhideCursor)
|
||||
}
|
||||
|
||||
func (rl *Readline) backspace(forward bool) {
|
||||
if len(rl.line) == 0 || rl.pos == 0 {
|
||||
return
|
||||
}
|
||||
@ -108,7 +151,7 @@ func (rl *Instance) backspace(forward bool) {
|
||||
rl.deleteBackspace(forward)
|
||||
}
|
||||
|
||||
func (rl *Instance) moveCursorByAdjust(adjust int) {
|
||||
func (rl *Readline) moveCursorByAdjust(adjust int) {
|
||||
switch {
|
||||
case adjust > 0:
|
||||
rl.pos += adjust
|
@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
// writeTempFile - This function optionally accepts a filename (generally specified with an extension).
|
||||
func (rl *Instance) writeTempFile(content []byte, filename string) (string, error) {
|
||||
func (rl *Readline) writeTempFile(content []byte, filename string) (string, error) {
|
||||
// The final path to the buffer on disk
|
||||
var path string
|
||||
|
@ -1,9 +1,10 @@
|
||||
//go:build plan9
|
||||
// +build plan9
|
||||
|
||||
package readline
|
||||
|
||||
import "errors"
|
||||
|
||||
func (rl *Instance) launchEditor(multiline []rune) ([]rune, error) {
|
||||
func (rl *Readline) launchEditor(multiline []rune) ([]rune, error) {
|
||||
return rl.line, errors.New("Not currently supported on Plan 9")
|
||||
}
|
@ -15,7 +15,7 @@ const defaultEditor = "vi"
|
||||
// depending on the actions taken by the user within it (eg: x or q! in Vim)
|
||||
// The filename parameter can be used to pass a specific filename.ext pattern,
|
||||
// which might be useful if the editor has builtin filetype plugin functionality.
|
||||
func (rl *Instance) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) {
|
||||
func (rl *Readline) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) {
|
||||
name, err := rl.writeTempFile([]byte(string(multiline)), filename)
|
||||
if err != nil {
|
||||
return multiline, err
|
@ -5,6 +5,6 @@ package readline
|
||||
import "errors"
|
||||
|
||||
// StartEditorWithBuffer - Not implemented on Windows platforms.
|
||||
func (rl *Instance) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) {
|
||||
func (rl *Readline) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) {
|
||||
return rl.line, errors.New("Not currently supported on Windows")
|
||||
}
|
@ -13,11 +13,11 @@ type EventReturn struct {
|
||||
}
|
||||
|
||||
// AddEvent registers a new keypress handler
|
||||
func (rl *Instance) AddEvent(keyPress string, callback func(string, []rune, int) *EventReturn) {
|
||||
func (rl *Readline) AddEvent(keyPress string, callback func(string, []rune, int) *EventReturn) {
|
||||
rl.evtKeyPress[keyPress] = callback
|
||||
}
|
||||
|
||||
// DelEvent deregisters an existing keypress handler
|
||||
func (rl *Instance) DelEvent(keyPress string) {
|
||||
func (rl *Readline) DelEvent(keyPress string) {
|
||||
delete(rl.evtKeyPress, keyPress)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
module github.com/maxlandon/readline
|
||||
|
||||
go 1.16
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
@ -5,13 +5,13 @@ import "regexp"
|
||||
// SetHintText - a nasty function to force writing a new hint text. It does not update helpers, it just renders
|
||||
// them, so the hint will survive until the helpers (thus including the hint) will be updated/recomputed.
|
||||
/*
|
||||
func (rl *Instance) SetHintText(s string) {
|
||||
func (rl *Readline) SetHintText(s string) {
|
||||
rl.hintText = []rune(s)
|
||||
rl.renderHelpers()
|
||||
}
|
||||
*/
|
||||
|
||||
func (rl *Instance) getHintText() {
|
||||
func (rl *Readline) getHintText() {
|
||||
|
||||
if !rl.modeAutoFind && !rl.modeTabFind {
|
||||
// Return if no hints provided by the user/engine
|
||||
@ -27,7 +27,7 @@ func (rl *Instance) getHintText() {
|
||||
}
|
||||
|
||||
// writeHintText - only writes the hint text and computes its offsets.
|
||||
func (rl *Instance) writeHintText() {
|
||||
func (rl *Readline) writeHintText() {
|
||||
if len(rl.hintText) == 0 {
|
||||
//rl.hintY = 0
|
||||
return
|
||||
@ -52,7 +52,14 @@ func (rl *Instance) writeHintText() {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) resetHintText() {
|
||||
func (rl *Readline) resetHintText() {
|
||||
//rl.hintY = 0
|
||||
rl.hintText = []rune{}
|
||||
}
|
||||
|
||||
func (rl *Readline) insertHintText() {
|
||||
if len(rl.hintText) != 0 {
|
||||
// fill in hint text
|
||||
rl.insert(rl.hintText)
|
||||
}
|
||||
}
|
@ -29,24 +29,24 @@ type History interface {
|
||||
}
|
||||
|
||||
// SetHistoryCtrlR - Set the history source triggered with Ctrl-r combination
|
||||
func (rl *Instance) SetHistoryCtrlR(name string, history History) {
|
||||
func (rl *Readline) SetHistoryCtrlR(name string, history History) {
|
||||
rl.mainHistName = name
|
||||
rl.mainHistory = history
|
||||
}
|
||||
|
||||
// GetHistoryCtrlR - Returns the history source triggered by Ctrl-r
|
||||
func (rl *Instance) GetHistoryCtrlR() History {
|
||||
func (rl *Readline) GetHistoryCtrlR() History {
|
||||
return rl.mainHistory
|
||||
}
|
||||
|
||||
// SetHistoryAltR - Set the history source triggered with Alt-r combination
|
||||
func (rl *Instance) SetHistoryAltR(name string, history History) {
|
||||
func (rl *Readline) SetHistoryAltR(name string, history History) {
|
||||
rl.altHistName = name
|
||||
rl.altHistory = history
|
||||
}
|
||||
|
||||
// GetHistoryAltR - Returns the history source triggered by Alt-r
|
||||
func (rl *Instance) GetHistoryAltR() History {
|
||||
func (rl *Readline) GetHistoryAltR() History {
|
||||
return rl.altHistory
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ func (h *NullHistory) Dump() interface{} {
|
||||
}
|
||||
|
||||
// Browse historic lines:
|
||||
func (rl *Instance) walkHistory(i int) {
|
||||
func (rl *Readline) walkHistory(i int) {
|
||||
var (
|
||||
old, new string
|
||||
dedup bool
|
||||
@ -128,15 +128,19 @@ func (rl *Instance) walkHistory(i int) {
|
||||
}
|
||||
|
||||
rl.histOffset += i
|
||||
historyLen := history.Len()
|
||||
if rl.histOffset == 0 {
|
||||
rl.line = []rune(rl.lineBuf)
|
||||
rl.pos = len(rl.lineBuf)
|
||||
} else if rl.histOffset <= -1 {
|
||||
rl.histOffset = 0
|
||||
} else if rl.histOffset > historyLen {
|
||||
// TODO: should this wrap around?s
|
||||
rl.histOffset = 0
|
||||
} else {
|
||||
dedup = true
|
||||
old = string(rl.line)
|
||||
new, err = history.GetLine(history.Len() - rl.histOffset)
|
||||
new, err = history.GetLine(historyLen - rl.histOffset)
|
||||
if err != nil {
|
||||
rl.resetHelpers()
|
||||
print("\r\n" + err.Error() + "\r\n")
|
||||
@ -164,7 +168,7 @@ func (rl *Instance) walkHistory(i int) {
|
||||
|
||||
// completeHistory - Populates a CompletionGroup with history and returns it the shell
|
||||
// we populate only one group, so as to pass it to the main completion engine.
|
||||
func (rl *Instance) completeHistory() (hist []*CompletionGroup) {
|
||||
func (rl *Readline) completeHistory() (hist []*CompletionGroup) {
|
||||
|
||||
hist = make([]*CompletionGroup, 1)
|
||||
hist[0] = &CompletionGroup{
|
@ -4,12 +4,12 @@ import "regexp"
|
||||
|
||||
// SetInfoText - a nasty function to force writing a new info text. It does not update helpers, it just renders
|
||||
// them, so the info will survive until the helpers (thus including the info) will be updated/recomputed.
|
||||
func (rl *Instance) SetInfoText(s string) {
|
||||
func (rl *Readline) SetInfoText(s string) {
|
||||
rl.infoText = []rune(s)
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) getInfoText() {
|
||||
func (rl *Readline) getInfoText() {
|
||||
|
||||
if !rl.modeAutoFind && !rl.modeTabFind {
|
||||
// Return if no infos provided by the user/engine
|
||||
@ -25,7 +25,7 @@ func (rl *Instance) getInfoText() {
|
||||
}
|
||||
|
||||
// writeInfoText - only writes the info text and computes its offsets.
|
||||
func (rl *Instance) writeInfoText() {
|
||||
func (rl *Readline) writeInfoText() {
|
||||
if len(rl.infoText) == 0 {
|
||||
rl.infoY = 0
|
||||
return
|
||||
@ -50,7 +50,7 @@ func (rl *Instance) writeInfoText() {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) resetInfoText() {
|
||||
func (rl *Readline) resetInfoText() {
|
||||
rl.infoY = 0
|
||||
rl.infoText = []rune{}
|
||||
}
|
@ -1,15 +1,20 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
)
|
||||
|
||||
// Instance is used to encapsulate the parameter group and run time of any given
|
||||
// readline instance so that you can reuse the readline API for multiple entry
|
||||
// captures without having to repeatedly unload configuration.
|
||||
type Instance struct {
|
||||
|
||||
// #type
|
||||
type Readline struct {
|
||||
|
||||
//
|
||||
// Input Modes -------------------------------------------------------------------------------
|
||||
@ -203,11 +208,15 @@ type Instance struct {
|
||||
ViActionCallback func(ViAction, []string)
|
||||
|
||||
RawInputCallback func([]rune) // called on all input
|
||||
|
||||
bufferedOut *bufio.Writer
|
||||
|
||||
Loader packagelib.Loader
|
||||
}
|
||||
|
||||
// NewInstance is used to create a readline instance and initialise it with sane defaults.
|
||||
func NewInstance() *Instance {
|
||||
rl := new(Instance)
|
||||
func NewInstance() *Readline {
|
||||
rl := new(Readline)
|
||||
|
||||
// Prompt
|
||||
rl.Multiline = false
|
||||
@ -237,12 +246,14 @@ func NewInstance() *Instance {
|
||||
var err error
|
||||
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
|
||||
if err != nil {
|
||||
rl.RefreshPromptLog(err.Error())
|
||||
//rl.RefreshPromptLog(err.Error())
|
||||
rl.infoText = []rune(Red("Failed to match search regexp"))
|
||||
}
|
||||
|
||||
for _, hay := range haystack {
|
||||
if rl.regexSearch == nil { continue }
|
||||
if rl.regexSearch == nil {
|
||||
continue
|
||||
}
|
||||
if rl.regexSearch.MatchString(hay) {
|
||||
suggs = append(suggs, hay)
|
||||
}
|
||||
@ -251,6 +262,13 @@ func NewInstance() *Instance {
|
||||
return suggs
|
||||
}
|
||||
|
||||
rl.bufferedOut = bufio.NewWriter(os.Stdout)
|
||||
|
||||
rl.Loader = packagelib.Loader{
|
||||
Name: "readline",
|
||||
Load: rl.luaLoader,
|
||||
}
|
||||
|
||||
// Registers
|
||||
rl.initRegisters()
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
// When the DelayedSyntaxWorker gives us a new line, we need to check if there
|
||||
// is any processing to be made, that all lines match in terms of content.
|
||||
func (rl *Instance) updateLine(line []rune) {
|
||||
func (rl *Readline) updateLine(line []rune) {
|
||||
if len(rl.currentComp) > 0 {
|
||||
|
||||
} else {
|
||||
@ -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 *Readline) GetLine() []rune {
|
||||
if len(rl.currentComp) > 0 {
|
||||
return rl.lineComp
|
||||
}
|
||||
@ -30,22 +30,23 @@ func (rl *Instance) GetLine() []rune {
|
||||
// function is only ever called once, and after having moved back to prompt position
|
||||
// and having printed the line: this is so that at any moment, everyone has the good
|
||||
// values for moving around, synchronized with the update input line.
|
||||
func (rl *Instance) echo() {
|
||||
func (rl *Readline) echo() {
|
||||
|
||||
// Then we print the prompt, and the line,
|
||||
hideCursor()
|
||||
switch {
|
||||
case rl.PasswordMask != 0:
|
||||
case rl.PasswordMask > 0:
|
||||
print(strings.Repeat(string(rl.PasswordMask), len(rl.line)) + " ")
|
||||
rl.bufprint(strings.Repeat(string(rl.PasswordMask), len(rl.line)) + " ")
|
||||
|
||||
default:
|
||||
|
||||
// Go back to prompt position, and clear everything below
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
moveCursorUp(rl.posY)
|
||||
print(seqClearScreenBelow)
|
||||
|
||||
// Print the prompt
|
||||
print(string(rl.realPrompt))
|
||||
rl.bufprint(string(rl.realPrompt))
|
||||
|
||||
// Assemble the line, taking virtual completions into account
|
||||
var line []rune
|
||||
@ -57,11 +58,14 @@ func (rl *Instance) echo() {
|
||||
|
||||
// Print the input line with optional syntax highlighting
|
||||
if rl.SyntaxHighlighter != nil {
|
||||
print(rl.SyntaxHighlighter(line))
|
||||
rl.bufprint(rl.SyntaxHighlighter(line))
|
||||
} else {
|
||||
print(string(line))
|
||||
rl.bufprint(string(line))
|
||||
}
|
||||
rl.bufprint(seqClearScreenBelow)
|
||||
|
||||
}
|
||||
rl.bufflush()
|
||||
|
||||
// Update references with new coordinates only now, because
|
||||
// the new line may be longer/shorter than the previous one.
|
||||
@ -72,9 +76,10 @@ func (rl *Instance) echo() {
|
||||
moveCursorUp(rl.fullY)
|
||||
moveCursorDown(rl.posY)
|
||||
moveCursorForwards(rl.posX)
|
||||
unhideCursor()
|
||||
}
|
||||
|
||||
func (rl *Instance) insert(r []rune) {
|
||||
func (rl *Readline) insert(r []rune) {
|
||||
for {
|
||||
// I don't really understand why `0` is creaping in at the end of the
|
||||
// array but it only happens with unicode characters.
|
||||
@ -107,11 +112,11 @@ func (rl *Instance) insert(r []rune) {
|
||||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) Insert(t string) {
|
||||
func (rl *Readline) Insert(t string) {
|
||||
rl.insert([]rune(t))
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteX() {
|
||||
func (rl *Readline) deleteX() {
|
||||
switch {
|
||||
case len(rl.line) == 0:
|
||||
return
|
||||
@ -129,7 +134,7 @@ func (rl *Instance) deleteX() {
|
||||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteBackspace(forward bool) {
|
||||
func (rl *Readline) deleteBackspace(forward bool) {
|
||||
switch {
|
||||
case len(rl.line) == 0:
|
||||
return
|
||||
@ -148,7 +153,7 @@ func (rl *Instance) deleteBackspace(forward bool) {
|
||||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) clearLine() {
|
||||
func (rl *Readline) clearLine() {
|
||||
if len(rl.line) == 0 {
|
||||
return
|
||||
}
|
||||
@ -159,7 +164,7 @@ func (rl *Instance) clearLine() {
|
||||
moveCursorForwards(rl.promptLen)
|
||||
|
||||
// Clear everything after & below the cursor
|
||||
print(seqClearScreenBelow)
|
||||
//print(seqClearScreenBelow)
|
||||
|
||||
// Real input line
|
||||
rl.line = []rune{}
|
||||
@ -174,21 +179,21 @@ func (rl *Instance) clearLine() {
|
||||
rl.clearVirtualComp()
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteToBeginning() {
|
||||
func (rl *Readline) deleteToBeginning() {
|
||||
rl.resetVirtualComp(false)
|
||||
// Keep the line length up until the cursor
|
||||
rl.line = rl.line[rl.pos:]
|
||||
rl.pos = 0
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteToEnd() {
|
||||
func (rl *Readline) deleteToEnd() {
|
||||
rl.resetVirtualComp(false)
|
||||
// Keep everything before the cursor
|
||||
rl.line = rl.line[:rl.pos]
|
||||
}
|
||||
|
||||
// @TODO(Renzix): move to emacs sepecific file
|
||||
func (rl *Instance) emacsForwardWord(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) emacsForwardWord(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
||||
@ -209,7 +214,7 @@ func (rl *Instance) emacsForwardWord(tokeniser tokeniser) (adjust int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) emacsBackwardWord(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) emacsBackwardWord(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
276
golibs/readline/lua.go
Normal file
276
golibs/readline/lua.go
Normal file
@ -0,0 +1,276 @@
|
||||
// line reader library
|
||||
// The readline module is responsible for reading input from the user.
|
||||
// The readline module is what Hilbish uses to read input from the user,
|
||||
// including all the interactive features of Hilbish like history search,
|
||||
// syntax highlighting, everything. The global Hilbish readline instance
|
||||
// is usable at `hilbish.editor`.
|
||||
package readline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var rlMetaKey = rt.StringValue("__readline")
|
||||
|
||||
func (rl *Readline) luaLoader(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
rlMethods := rt.NewTable()
|
||||
rlMethodss := map[string]util.LuaExport{
|
||||
"deleteByAmount": {rlDeleteByAmount, 2, false},
|
||||
"getLine": {rlGetLine, 1, false},
|
||||
"getVimRegister": {rlGetRegister, 2, false},
|
||||
"insert": {rlInsert, 2, false},
|
||||
"read": {rlRead, 1, false},
|
||||
"readChar": {rlReadChar, 1, false},
|
||||
"setVimRegister": {rlSetRegister, 3, false},
|
||||
"log": {rlLog, 2, false},
|
||||
}
|
||||
util.SetExports(rtm, rlMethods, rlMethodss)
|
||||
|
||||
rlMeta := rt.NewTable()
|
||||
rlIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
_, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arg := c.Arg(1)
|
||||
val := rlMethods.Get(arg)
|
||||
|
||||
return c.PushingNext1(t.Runtime, val), nil
|
||||
}
|
||||
|
||||
rlMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(rlIndex, "__index", 2, false)))
|
||||
rtm.SetRegistry(rlMetaKey, rt.TableValue(rlMeta))
|
||||
|
||||
rlFuncs := map[string]util.LuaExport{
|
||||
"new": {rlNew, 0, false},
|
||||
}
|
||||
|
||||
luaRl := rt.NewTable()
|
||||
util.SetExports(rtm, luaRl, rlFuncs)
|
||||
|
||||
return rt.TableValue(luaRl), nil
|
||||
}
|
||||
|
||||
// new() -> @Readline
|
||||
// Creates a new readline instance.
|
||||
func rlNew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
rl := NewInstance()
|
||||
ud := rlUserData(t.Runtime, rl)
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.UserDataValue(ud)), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// insert(text)
|
||||
// Inserts text into the Hilbish command line.
|
||||
// #param text string
|
||||
func rlInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl.insert([]rune(text))
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// read() -> string
|
||||
// Reads input from the user.
|
||||
func rlRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inp, err := rl.Readline()
|
||||
if err == EOF {
|
||||
fmt.Println("")
|
||||
return nil, io.EOF
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(inp)), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// setVimRegister(register, text)
|
||||
// Sets the vim register at `register` to hold the passed text.
|
||||
// #param register string
|
||||
// #param text string
|
||||
func rlSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
register, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text, err := c.StringArg(2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl.SetRegisterBuf(register, []rune(text))
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// getVimRegister(register) -> string
|
||||
// Returns the text that is at the register.
|
||||
// #param register string
|
||||
func rlGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
register, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := rl.GetFromRegister(register)
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// getLine() -> string
|
||||
// Returns the current input line.
|
||||
// #returns string
|
||||
func rlGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := rl.GetLine()
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// getChar() -> string
|
||||
// Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
func rlReadChar(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := rl.ReadChar()
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// deleteByAmount(amount)
|
||||
// Deletes characters in the line by the given amount.
|
||||
// #param amount number
|
||||
func rlDeleteByAmount(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
amount, err := c.IntArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl.DeleteByAmount(int(amount))
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// log(text)
|
||||
// Prints a message *before* the prompt without it being interrupted by user input.
|
||||
func rlLog(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logText, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl.RefreshPromptLog(logText)
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func rlArg(c *rt.GoCont, arg int) (*Readline, error) {
|
||||
j, ok := valueToRl(c.Arg(arg))
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("#%d must be a readline", arg+1)
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
func valueToRl(val rt.Value) (*Readline, bool) {
|
||||
u, ok := val.TryUserData()
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
j, ok := u.Value().(*Readline)
|
||||
return j, ok
|
||||
}
|
||||
|
||||
func rlUserData(rtm *rt.Runtime, rl *Readline) *rt.UserData {
|
||||
rlMeta := rtm.Registry(rlMetaKey)
|
||||
return rt.NewUserData(rl, rlMeta.AsTable())
|
||||
}
|
@ -8,20 +8,20 @@ import (
|
||||
|
||||
// SetPrompt will define the readline prompt string.
|
||||
// It also calculates the runes in the string as well as any non-printable escape codes.
|
||||
func (rl *Instance) SetPrompt(s string) {
|
||||
func (rl *Readline) SetPrompt(s string) {
|
||||
rl.mainPrompt = s
|
||||
rl.computePrompt()
|
||||
}
|
||||
|
||||
// SetRightPrompt sets the right prompt.
|
||||
func (rl *Instance) SetRightPrompt(s string) {
|
||||
func (rl *Readline) SetRightPrompt(s string) {
|
||||
rl.rightPrompt = s + " "
|
||||
rl.computePrompt()
|
||||
}
|
||||
|
||||
// RefreshPromptLog - A simple function to print a string message (a log, or more broadly,
|
||||
// an asynchronous event) without bothering the user, and by "pushing" the prompt below the message.
|
||||
func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
||||
func (rl *Readline) RefreshPromptLog(log string) (err error) {
|
||||
|
||||
// We adjust cursor movement, depending on which mode we're currently in.
|
||||
if !rl.modeTabCompletion {
|
||||
@ -48,7 +48,7 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
||||
rl.stillOnRefresh = true
|
||||
moveCursorUp(rl.infoY + rl.tcUsedY)
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
print("\r\n" + seqClearScreenBelow)
|
||||
//print("\r\n" + seqClearScreenBelow)
|
||||
|
||||
// Print the log
|
||||
fmt.Printf(log)
|
||||
@ -73,7 +73,7 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
||||
}
|
||||
|
||||
// RefreshPromptInPlace - Refreshes the prompt in the very same place he is.
|
||||
func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
||||
func (rl *Readline) RefreshPromptInPlace(prompt string) (err error) {
|
||||
// We adjust cursor movement, depending on which mode we're currently in.
|
||||
// Prompt data intependent
|
||||
if !rl.modeTabCompletion {
|
||||
@ -97,7 +97,7 @@ func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
||||
print(seqClearLine)
|
||||
moveCursorUp(rl.infoY + rl.tcUsedY)
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
print("\r\n" + seqClearScreenBelow)
|
||||
//print("\r\n" + seqClearScreenBelow)
|
||||
|
||||
// Add a new line if needed
|
||||
if rl.Multiline {
|
||||
@ -117,7 +117,7 @@ func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
||||
// @prompt => If not nil (""), will use this prompt instead of the currently set prompt.
|
||||
// @offset => Used to set the number of lines to go upward, before reprinting. Set to 0 if not used.
|
||||
// @clearLine => If true, will clean the current input line on the next refresh.
|
||||
func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine bool) (err error) {
|
||||
func (rl *Readline) RefreshPromptCustom(prompt string, offset int, clearLine bool) (err error) {
|
||||
|
||||
// We adjust cursor movement, depending on which mode we're currently in.
|
||||
if !rl.modeTabCompletion {
|
||||
@ -137,7 +137,7 @@ func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine boo
|
||||
moveCursorUp(offset)
|
||||
|
||||
// Then clear everything below our new position
|
||||
print(seqClearScreenBelow)
|
||||
//print(seqClearScreenBelow)
|
||||
|
||||
// Update the prompt if a special has been passed.
|
||||
if prompt != "" {
|
||||
@ -166,7 +166,7 @@ func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine boo
|
||||
|
||||
// computePrompt - At any moment, returns an (1st or 2nd line) actualized prompt,
|
||||
// considering all input mode parameters and prompt string values.
|
||||
func (rl *Instance) computePrompt() (prompt []rune) {
|
||||
func (rl *Readline) computePrompt() (prompt []rune) {
|
||||
if rl.Multiline {
|
||||
if rl.MultilinePrompt != "" {
|
||||
rl.realPrompt = []rune(rl.MultilinePrompt)
|
||||
@ -194,7 +194,7 @@ func (rl *Instance) computePrompt() (prompt []rune) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) colorizeVimPrompt(p []rune) (cp []rune) {
|
||||
func (rl *Readline) colorizeVimPrompt(p []rune) (cp []rune) {
|
||||
if rl.VimModeColorize {
|
||||
return []rune(fmt.Sprintf("%s%s%s", BOLD, string(p), RESET))
|
||||
}
|
||||
@ -211,7 +211,7 @@ func getRealLength(s string) (l int) {
|
||||
return getWidth([]rune(stripped))
|
||||
}
|
||||
|
||||
func (rl *Instance) echoRightPrompt() {
|
||||
func (rl *Readline) echoRightPrompt() {
|
||||
if rl.fullX < GetTermWidth()-rl.rightPromptLen-1 {
|
||||
moveCursorForwards(GetTermWidth())
|
||||
moveCursorBackwards(rl.rightPromptLen)
|
@ -2,16 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
package readline
|
||||
|
||||
import (
|
@ -2,18 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
package readline
|
||||
|
||||
import (
|
||||
@ -70,4 +61,3 @@ func GetSize(fd int) (width, height int, err error) {
|
||||
}
|
||||
return int(info.Size.X), int(info.Size.Y), nil
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ var rxMultiline = regexp.MustCompile(`[\r\n]+`)
|
||||
|
||||
// Readline displays the readline prompt.
|
||||
// It will return a string (user entered data) or an error.
|
||||
func (rl *Instance) Readline() (string, error) {
|
||||
func (rl *Readline) Readline() (string, error) {
|
||||
fd := int(os.Stdin.Fd())
|
||||
state, err := MakeRaw(fd)
|
||||
if err != nil {
|
||||
@ -183,7 +183,7 @@ func (rl *Instance) Readline() (string, error) {
|
||||
if rl.modeTabFind {
|
||||
rl.backspaceTabFind()
|
||||
} else {
|
||||
if (rl.pos < len(rl.line)) {
|
||||
if rl.pos < len(rl.line) {
|
||||
rl.deleteBackspace(true)
|
||||
}
|
||||
}
|
||||
@ -545,7 +545,7 @@ func (rl *Instance) Readline() (string, error) {
|
||||
// editorInput is an unexported function used to determine what mode of text
|
||||
// entry readline is currently configured for and then update the line entries
|
||||
// accordingly.
|
||||
func (rl *Instance) editorInput(r []rune) {
|
||||
func (rl *Readline) editorInput(r []rune) {
|
||||
if len(r) == 0 {
|
||||
return
|
||||
}
|
||||
@ -595,7 +595,7 @@ func (rl *Instance) editorInput(r []rune) {
|
||||
|
||||
// viEscape - In case th user is using Vim input, and the escape sequence has not
|
||||
// been handled by other cases, we dispatch it to Vim and handle a few cases here.
|
||||
func (rl *Instance) viEscape(r []rune) {
|
||||
func (rl *Readline) viEscape(r []rune) {
|
||||
|
||||
// Sometimes the escape sequence is interleaved with another one,
|
||||
// but key strokes might be in the wrong order, so we double check
|
||||
@ -611,7 +611,7 @@ func (rl *Instance) viEscape(r []rune) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) escapeSeq(r []rune) {
|
||||
func (rl *Readline) escapeSeq(r []rune) {
|
||||
switch string(r) {
|
||||
// Vim escape sequences & dispatching --------------------------------------------------------
|
||||
case string(charEscape):
|
||||
@ -707,6 +707,9 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||
rl.renderHelpers()
|
||||
return
|
||||
}
|
||||
|
||||
rl.insertHintText()
|
||||
|
||||
if (rl.modeViMode == VimInsert && rl.pos < len(rl.line)) ||
|
||||
(rl.modeViMode != VimInsert && rl.pos < len(rl.line)-1) {
|
||||
rl.moveCursorByAdjust(1)
|
||||
@ -756,7 +759,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||
if rl.modeTabFind {
|
||||
rl.backspaceTabFind()
|
||||
} else {
|
||||
if (rl.pos < len(rl.line)) {
|
||||
if rl.pos < len(rl.line) {
|
||||
rl.deleteBackspace(true)
|
||||
}
|
||||
}
|
||||
@ -868,7 +871,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !rl.mainHist {
|
||||
if !rl.mainHist && rl.altHistory != nil {
|
||||
line, err = rl.altHistory.GetLine(rl.altHistory.Len() - 1)
|
||||
if err != nil {
|
||||
return
|
||||
@ -887,7 +890,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) carridgeReturn() {
|
||||
func (rl *Readline) carridgeReturn() {
|
||||
rl.moveCursorByAdjust(len(rl.line))
|
||||
rl.updateHelpers()
|
||||
rl.clearHelpers()
|
||||
@ -921,7 +924,7 @@ func isMultiline(r []rune) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (rl *Instance) allowMultiline(data []byte) bool {
|
||||
func (rl *Readline) allowMultiline(data []byte) bool {
|
||||
rl.clearHelpers()
|
||||
printf("\r\nWARNING: %d bytes of multiline data was dumped into the shell!", len(data))
|
||||
for {
|
@ -23,7 +23,7 @@ type registers struct {
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func (rl *Instance) initRegisters() {
|
||||
func (rl *Readline) initRegisters() {
|
||||
rl.registers = ®isters{
|
||||
num: make(map[int][]rune, 10),
|
||||
alpha: make(map[string][]rune, 52),
|
||||
@ -36,7 +36,7 @@ func (rl *Instance) initRegisters() {
|
||||
// the number of Vim iterations and we save the resulting string to the appropriate buffer.
|
||||
// It's the same as saveToRegisterTokenize, but without the need to generate tokenized &
|
||||
// cursor-pos-actualized versions of the input line.
|
||||
func (rl *Instance) saveToRegister(adjust int) {
|
||||
func (rl *Readline) saveToRegister(adjust int) {
|
||||
|
||||
// Get the current cursor position and go the length specified.
|
||||
var begin = rl.pos
|
||||
@ -66,7 +66,7 @@ func (rl *Instance) saveToRegister(adjust int) {
|
||||
// saveToRegisterTokenize - Passing a function that will move around the line in the desired way, we get
|
||||
// the number of Vim iterations and we save the resulting string to the appropriate buffer. Because we
|
||||
// need the cursor position to be really moved around between calls to the jumper, we also need the tokeniser.
|
||||
func (rl *Instance) saveToRegisterTokenize(tokeniser tokeniser, jumper func(tokeniser) int, vii int) {
|
||||
func (rl *Readline) saveToRegisterTokenize(tokeniser tokeniser, jumper func(tokeniser) int, vii int) {
|
||||
|
||||
// The register is going to have to heavily manipulate the cursor position.
|
||||
// Remember the original one first, for the end.
|
||||
@ -104,11 +104,11 @@ func (rl *Instance) saveToRegisterTokenize(tokeniser tokeniser, jumper func(toke
|
||||
// saveBufToRegister - Instead of computing the buffer ourselves based on an adjust,
|
||||
// let the caller pass directly this buffer, yet relying on the register system to
|
||||
// determine which register will store the buffer.
|
||||
func (rl *Instance) saveBufToRegister(buffer []rune) {
|
||||
func (rl *Readline) saveBufToRegister(buffer []rune) {
|
||||
rl.SetRegisterBuf(string(rl.registers.currentRegister), buffer)
|
||||
}
|
||||
|
||||
func (rl *Instance) SetRegisterBuf(reg string, buffer []rune) {
|
||||
func (rl *Readline) SetRegisterBuf(reg string, buffer []rune) {
|
||||
// We must make an immutable version of the buffer first.
|
||||
buf := string(buffer)
|
||||
|
||||
@ -141,7 +141,7 @@ func (rl *Instance) SetRegisterBuf(reg string, buffer []rune) {
|
||||
|
||||
// The user asked to paste a buffer onto the line, so we check from which register
|
||||
// we are supposed to select the buffer, and return it to the caller for insertion.
|
||||
func (rl *Instance) pasteFromRegister() (buffer []rune) {
|
||||
func (rl *Readline) pasteFromRegister() (buffer []rune) {
|
||||
|
||||
// When exiting this function the currently selected register is dropped,
|
||||
defer rl.registers.resetRegister()
|
||||
@ -155,7 +155,7 @@ func (rl *Instance) pasteFromRegister() (buffer []rune) {
|
||||
return rl.GetFromRegister(activeRegister)
|
||||
}
|
||||
|
||||
func (rl *Instance) GetFromRegister(reg string) []rune {
|
||||
func (rl *Readline) GetFromRegister(reg string) []rune {
|
||||
// Find the active register, and return its content.
|
||||
num, err := strconv.Atoi(reg)
|
||||
|
||||
@ -264,7 +264,7 @@ func (r *registers) resetRegister() {
|
||||
}
|
||||
|
||||
// The user can show registers completions and insert, no matter the cursor position.
|
||||
func (rl *Instance) completeRegisters() (groups []*CompletionGroup) {
|
||||
func (rl *Readline) completeRegisters() (groups []*CompletionGroup) {
|
||||
|
||||
// We set the info exceptionally
|
||||
info := BLUE + "-- registers --" + RESET
|
@ -2,7 +2,7 @@ package readline
|
||||
|
||||
// syntaxCompletion - applies syntax highlighting to the current input line.
|
||||
// nothing special to note here, nor any changes envisioned.
|
||||
func (rl *Instance) syntaxCompletion() {
|
||||
func (rl *Readline) syntaxCompletion() {
|
||||
if rl.SyntaxCompleter == nil {
|
||||
return
|
||||
}
|
@ -2,13 +2,14 @@ package readline
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// insertCandidateVirtual - When a completion candidate is selected, we insert it virtually in the input line:
|
||||
// this will not trigger further firltering against the other candidates. Each time this function
|
||||
// is called, any previous candidate is dropped, after being used for moving the cursor around.
|
||||
func (rl *Instance) insertCandidateVirtual(candidate []rune) {
|
||||
func (rl *Readline) insertCandidateVirtual(candidate []rune) {
|
||||
for {
|
||||
// I don't really understand why `0` is creaping in at the end of the
|
||||
// array but it only happens with unicode characters.
|
||||
@ -57,7 +58,7 @@ func (rl *Instance) insertCandidateVirtual(candidate []rune) {
|
||||
// Insert the current completion candidate into the input line.
|
||||
// This candidate might either be the currently selected one (white frame),
|
||||
// or the only candidate available, if the total number of candidates is 1.
|
||||
func (rl *Instance) insertCandidate() {
|
||||
func (rl *Readline) insertCandidate() {
|
||||
|
||||
cur := rl.getCurrentGroup()
|
||||
|
||||
@ -83,7 +84,7 @@ func (rl *Instance) insertCandidate() {
|
||||
}
|
||||
|
||||
// updateVirtualComp - Either insert the current completion candidate virtually, or on the real line.
|
||||
func (rl *Instance) updateVirtualComp() {
|
||||
func (rl *Readline) updateVirtualComp() {
|
||||
cur := rl.getCurrentGroup()
|
||||
if cur != nil {
|
||||
|
||||
@ -118,7 +119,7 @@ func (rl *Instance) updateVirtualComp() {
|
||||
// resetVirtualComp - This function is called before most of our readline key handlers,
|
||||
// and makes sure that the current completion (virtually inserted) is either inserted or dropped,
|
||||
// and that all related parameters are reinitialized.
|
||||
func (rl *Instance) resetVirtualComp(drop bool) {
|
||||
func (rl *Readline) resetVirtualComp(drop bool) {
|
||||
|
||||
// If we don't have a current virtual completion, there's nothing to do.
|
||||
// IMPORTANT: this MUST be first, to avoid nil problems with empty comps.
|
||||
@ -196,7 +197,7 @@ func trimTrailing(comp string) (trimmed string, hadSlash bool) {
|
||||
}
|
||||
|
||||
// viDeleteByAdjustVirtual - Same as viDeleteByAdjust, but for our virtually completed input line.
|
||||
func (rl *Instance) viDeleteByAdjustVirtual(adjust int) {
|
||||
func (rl *Readline) viDeleteByAdjustVirtual(adjust int) {
|
||||
var (
|
||||
newLine []rune
|
||||
backOne bool
|
||||
@ -235,7 +236,7 @@ func (rl *Instance) viDeleteByAdjustVirtual(adjust int) {
|
||||
}
|
||||
|
||||
// viJumpEVirtual - Same as viJumpE, but for our virtually completed input line.
|
||||
func (rl *Instance) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, int)) (adjust int) {
|
||||
func (rl *Readline) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, int)) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.lineComp, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
||||
@ -258,7 +259,7 @@ func (rl *Instance) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, i
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteVirtual() {
|
||||
func (rl *Readline) deleteVirtual() {
|
||||
switch {
|
||||
case len(rl.lineComp) == 0:
|
||||
return
|
||||
@ -274,7 +275,7 @@ func (rl *Instance) deleteVirtual() {
|
||||
|
||||
// We are done with the current virtual completion candidate.
|
||||
// Get ready for the next one
|
||||
func (rl *Instance) clearVirtualComp() {
|
||||
func (rl *Readline) clearVirtualComp() {
|
||||
rl.line = rl.lineComp
|
||||
rl.currentComp = []rune{}
|
||||
rl.compAddSpace = false
|
@ -28,7 +28,7 @@ const (
|
||||
|
||||
// getTabCompletion - This root function sets up all completion items and engines,
|
||||
// dealing with all search and completion modes. But it does not perform printing.
|
||||
func (rl *Instance) getTabCompletion() {
|
||||
func (rl *Readline) getTabCompletion() {
|
||||
|
||||
// Populate registers if requested.
|
||||
if rl.modeAutoFind && rl.searchMode == RegisterFind {
|
||||
@ -53,7 +53,7 @@ func (rl *Instance) getTabCompletion() {
|
||||
}
|
||||
|
||||
// getRegisterCompletion - Populates and sets up completion for Vim registers.
|
||||
func (rl *Instance) getRegisterCompletion() {
|
||||
func (rl *Readline) getRegisterCompletion() {
|
||||
|
||||
rl.tcGroups = rl.completeRegisters()
|
||||
if len(rl.tcGroups) == 0 {
|
||||
@ -84,7 +84,7 @@ func (rl *Instance) getRegisterCompletion() {
|
||||
}
|
||||
|
||||
// getTabSearchCompletion - Populates and sets up completion for completion search.
|
||||
func (rl *Instance) getTabSearchCompletion() {
|
||||
func (rl *Readline) getTabSearchCompletion() {
|
||||
|
||||
// Get completions from the engine, and make sure there is a current group.
|
||||
rl.getCompletions()
|
||||
@ -107,7 +107,7 @@ func (rl *Instance) getTabSearchCompletion() {
|
||||
}
|
||||
|
||||
// getHistorySearchCompletion - Populates and sets up completion for command history search
|
||||
func (rl *Instance) getHistorySearchCompletion() {
|
||||
func (rl *Readline) getHistorySearchCompletion() {
|
||||
|
||||
// Refresh full list each time
|
||||
rl.tcGroups = rl.completeHistory()
|
||||
@ -142,7 +142,7 @@ func (rl *Instance) getHistorySearchCompletion() {
|
||||
|
||||
// getNormalCompletion - Populates and sets up completion for normal comp mode.
|
||||
// Will automatically cancel the completion mode if there are no candidates.
|
||||
func (rl *Instance) getNormalCompletion() {
|
||||
func (rl *Readline) getNormalCompletion() {
|
||||
|
||||
// Get completions groups, pass delayedTabContext and check nils
|
||||
rl.getCompletions()
|
||||
@ -172,7 +172,7 @@ func (rl *Instance) getNormalCompletion() {
|
||||
// getCompletions - Calls the completion engine/function to yield a list of 0 or more completion groups,
|
||||
// sets up a delayed tab context and passes it on to the tab completion engine function, and ensure no
|
||||
// nil groups/items will pass through. This function is called by different comp search/nav modes.
|
||||
func (rl *Instance) getCompletions() {
|
||||
func (rl *Readline) getCompletions() {
|
||||
|
||||
// If there is no wired tab completion engine, nothing we can do.
|
||||
if rl.TabCompleter == nil {
|
||||
@ -214,7 +214,7 @@ func (rl *Instance) getCompletions() {
|
||||
|
||||
// moveTabCompletionHighlight - This function is in charge of
|
||||
// computing the new position in the current completions liste.
|
||||
func (rl *Instance) moveTabCompletionHighlight(x, y int) {
|
||||
func (rl *Readline) moveTabCompletionHighlight(x, y int) {
|
||||
rl.completionOpen = true
|
||||
g := rl.getCurrentGroup()
|
||||
|
||||
@ -253,7 +253,7 @@ func (rl *Instance) moveTabCompletionHighlight(x, y int) {
|
||||
}
|
||||
|
||||
// writeTabCompletion - Prints all completion groups and their items
|
||||
func (rl *Instance) writeTabCompletion() {
|
||||
func (rl *Readline) writeTabCompletion() {
|
||||
|
||||
// The final completions string to print.
|
||||
var completions string
|
||||
@ -276,19 +276,20 @@ func (rl *Instance) writeTabCompletion() {
|
||||
// than what their MaxLength allows them to, cycling sometimes occur,
|
||||
// but does not fully clears itself: some descriptions are messed up with.
|
||||
// We always clear the screen as a result, between writings.
|
||||
print(seqClearScreenBelow)
|
||||
//rl.bufprint(seqClearScreenBelow)
|
||||
|
||||
// Crop the completions so that it fits within our MaxTabCompleterRows
|
||||
completions, rl.tcUsedY = rl.cropCompletions(completions)
|
||||
|
||||
// Then we print all of them.
|
||||
fmt.Printf(completions)
|
||||
rl.bufprintF(completions)
|
||||
rl.bufflush()
|
||||
}
|
||||
|
||||
// cropCompletions - When the user cycles through a completion list longer
|
||||
// than the console MaxTabCompleterRows value, we crop the completions string
|
||||
// so that "global" cycling (across all groups) is printed correctly.
|
||||
func (rl *Instance) cropCompletions(comps string) (cropped string, usedY int) {
|
||||
func (rl *Readline) cropCompletions(comps string) (cropped string, usedY int) {
|
||||
|
||||
// If we actually fit into the MaxTabCompleterRows, return the comps
|
||||
if rl.tcUsedY < rl.MaxTabCompleterRows {
|
||||
@ -365,7 +366,7 @@ func (rl *Instance) cropCompletions(comps string) (cropped string, usedY int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) getAbsPos() int {
|
||||
func (rl *Readline) getAbsPos() int {
|
||||
var prev int
|
||||
var foundCurrent bool
|
||||
for _, grp := range rl.tcGroups {
|
||||
@ -389,7 +390,7 @@ func (rl *Instance) getAbsPos() int {
|
||||
|
||||
// We pass a special subset of the current input line, so that
|
||||
// completions are available no matter where the cursor is.
|
||||
func (rl *Instance) getCompletionLine() (line []rune, pos int) {
|
||||
func (rl *Readline) getCompletionLine() (line []rune, pos int) {
|
||||
|
||||
pos = rl.pos - len(rl.currentComp)
|
||||
if pos < 0 {
|
||||
@ -408,7 +409,7 @@ func (rl *Instance) getCompletionLine() (line []rune, pos int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) getCurrentGroup() (group *CompletionGroup) {
|
||||
func (rl *Readline) getCurrentGroup() (group *CompletionGroup) {
|
||||
for _, g := range rl.tcGroups {
|
||||
if g.isCurrent && len(g.Suggestions) > 0 {
|
||||
return g
|
||||
@ -430,7 +431,7 @@ func (rl *Instance) getCurrentGroup() (group *CompletionGroup) {
|
||||
|
||||
// cycleNextGroup - Finds either the first non-empty group,
|
||||
// or the next non-empty group after the current one.
|
||||
func (rl *Instance) cycleNextGroup() {
|
||||
func (rl *Readline) cycleNextGroup() {
|
||||
for i, g := range rl.tcGroups {
|
||||
if g.isCurrent {
|
||||
g.isCurrent = false
|
||||
@ -451,7 +452,7 @@ func (rl *Instance) cycleNextGroup() {
|
||||
}
|
||||
|
||||
// cyclePreviousGroup - Same as cycleNextGroup but reverse
|
||||
func (rl *Instance) cyclePreviousGroup() {
|
||||
func (rl *Readline) cyclePreviousGroup() {
|
||||
for i, g := range rl.tcGroups {
|
||||
if g.isCurrent {
|
||||
g.isCurrent = false
|
||||
@ -470,7 +471,7 @@ func (rl *Instance) cyclePreviousGroup() {
|
||||
}
|
||||
|
||||
// Check if we have a single completion candidate
|
||||
func (rl *Instance) hasOneCandidate() bool {
|
||||
func (rl *Readline) hasOneCandidate() bool {
|
||||
if len(rl.tcGroups) == 0 {
|
||||
return false
|
||||
}
|
||||
@ -508,7 +509,7 @@ func (rl *Instance) hasOneCandidate() bool {
|
||||
// - The user-specified max completion length
|
||||
// - The terminal lengh
|
||||
// we use this function to prompt for confirmation before printing comps.
|
||||
func (rl *Instance) promptCompletionConfirm(sentence string) {
|
||||
func (rl *Readline) promptCompletionConfirm(sentence string) {
|
||||
rl.infoText = []rune(sentence)
|
||||
|
||||
rl.compConfirmWait = true
|
||||
@ -517,7 +518,7 @@ func (rl *Instance) promptCompletionConfirm(sentence string) {
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) getCompletionCount() (comps int, lines int, adjusted int) {
|
||||
func (rl *Readline) getCompletionCount() (comps int, lines int, adjusted int) {
|
||||
for _, group := range rl.tcGroups {
|
||||
comps += len(group.Suggestions)
|
||||
// if group.Name != "" {
|
||||
@ -534,7 +535,7 @@ func (rl *Instance) getCompletionCount() (comps int, lines int, adjusted int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) resetTabCompletion() {
|
||||
func (rl *Readline) resetTabCompletion() {
|
||||
rl.modeTabCompletion = false
|
||||
rl.tabCompletionSelect = false
|
||||
rl.compConfirmWait = false
|
@ -12,7 +12,7 @@ const (
|
||||
RegisterFind
|
||||
)
|
||||
|
||||
func (rl *Instance) backspaceTabFind() {
|
||||
func (rl *Readline) backspaceTabFind() {
|
||||
if len(rl.tfLine) > 0 {
|
||||
rl.tfLine = rl.tfLine[:len(rl.tfLine)-1]
|
||||
}
|
||||
@ -21,7 +21,7 @@ func (rl *Instance) backspaceTabFind() {
|
||||
|
||||
// Filter and refresh (print) a list of completions. The caller should have reset
|
||||
// the virtual completion system before, so that should not clash with this.
|
||||
func (rl *Instance) updateTabFind(r []rune) {
|
||||
func (rl *Readline) updateTabFind(r []rune) {
|
||||
|
||||
rl.tfLine = append(rl.tfLine, r...)
|
||||
|
||||
@ -29,12 +29,12 @@ func (rl *Instance) updateTabFind(r []rune) {
|
||||
rl.search = string(rl.tfLine)
|
||||
|
||||
// We update and print
|
||||
rl.clearHelpers()
|
||||
//rl.clearHelpers()
|
||||
rl.getTabCompletion()
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) resetTabFind() {
|
||||
func (rl *Readline) resetTabFind() {
|
||||
rl.modeTabFind = false
|
||||
// rl.modeAutoFind = false // Added, because otherwise it gets stuck on search completions
|
||||
|
@ -7,12 +7,12 @@ import (
|
||||
|
||||
// DelayedTabContext is a custom context interface for async updates to the tab completions
|
||||
type DelayedTabContext struct {
|
||||
rl *Instance
|
||||
rl *Readline
|
||||
Context context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func delayedSyntaxTimer(rl *Instance, i int64) {
|
||||
func delayedSyntaxTimer(rl *Readline, i int64) {
|
||||
if rl.PasswordMask != 0 || rl.DelayedSyntaxWorker == nil {
|
||||
return
|
||||
}
|
@ -5,7 +5,7 @@ type undoItem struct {
|
||||
pos int
|
||||
}
|
||||
|
||||
func (rl *Instance) undoAppendHistory() {
|
||||
func (rl *Readline) undoAppendHistory() {
|
||||
defer func() { rl.viUndoSkipAppend = false }()
|
||||
|
||||
if rl.viUndoSkipAppend {
|
||||
@ -18,7 +18,7 @@ func (rl *Instance) undoAppendHistory() {
|
||||
})
|
||||
}
|
||||
|
||||
func (rl *Instance) undoLast() {
|
||||
func (rl *Readline) undoLast() {
|
||||
var undo undoItem
|
||||
for {
|
||||
if len(rl.viUndoHistory) == 0 {
|
@ -1,6 +1,7 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/width"
|
||||
@ -9,8 +10,8 @@ import (
|
||||
// updateHelpers is a key part of the whole refresh process:
|
||||
// it should coordinate reprinting the input line, any Infos and completions
|
||||
// and manage to get back to the current (computed) cursor coordinates
|
||||
func (rl *Instance) updateHelpers() {
|
||||
|
||||
func (rl *Readline) updateHelpers() {
|
||||
print(seqHideCursor)
|
||||
// Load all Infos & completions before anything.
|
||||
// Thus overwrites anything having been dirtily added/forced/modified, like rl.SetInfoText()
|
||||
rl.getInfoText()
|
||||
@ -18,7 +19,9 @@ func (rl *Instance) updateHelpers() {
|
||||
if rl.modeTabCompletion && !rl.completionOpen {
|
||||
rl.getTabCompletion()
|
||||
} else {
|
||||
if rl.completionOpen { rl.completionOpen = false }
|
||||
if rl.completionOpen {
|
||||
rl.completionOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
// We clear everything
|
||||
@ -27,6 +30,7 @@ func (rl *Instance) updateHelpers() {
|
||||
// We are at the prompt line (with the latter
|
||||
// not printed yet), then reprint everything
|
||||
rl.renderHelpers()
|
||||
print(seqUnhideCursor)
|
||||
}
|
||||
|
||||
const tabWidth = 4
|
||||
@ -47,7 +51,7 @@ func getWidth(x []rune) int {
|
||||
}
|
||||
|
||||
// Update reference should be called only once in a "loop" (not Readline(), but key control loop)
|
||||
func (rl *Instance) updateReferences() {
|
||||
func (rl *Readline) updateReferences() {
|
||||
|
||||
// We always need to work with clean data,
|
||||
// since we will have incrementers all around
|
||||
@ -101,7 +105,7 @@ func (rl *Instance) updateReferences() {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) resetHelpers() {
|
||||
func (rl *Readline) resetHelpers() {
|
||||
rl.modeAutoFind = false
|
||||
|
||||
// Now reset all below-input helpers
|
||||
@ -111,7 +115,7 @@ func (rl *Instance) resetHelpers() {
|
||||
|
||||
// clearHelpers - Clears everything: prompt, input, Infos & comps,
|
||||
// and comes back at the prompt.
|
||||
func (rl *Instance) clearHelpers() {
|
||||
func (rl *Readline) clearHelpers() {
|
||||
|
||||
// Now go down to the last line of input
|
||||
moveCursorDown(rl.fullY - rl.posY)
|
||||
@ -130,7 +134,7 @@ func (rl *Instance) clearHelpers() {
|
||||
// renderHelpers - pritns all components (prompt, line, Infos & comps)
|
||||
// and replaces the cursor to its current position. This function never
|
||||
// computes or refreshes any value, except from inside the echo function.
|
||||
func (rl *Instance) renderHelpers() {
|
||||
func (rl *Readline) renderHelpers() {
|
||||
|
||||
// when the instance is in this state we want it to be "below" the user's
|
||||
// input for it to be aligned properly
|
||||
@ -194,3 +198,15 @@ func (rl *Instance) renderHelpers() {
|
||||
moveCursorUp(rl.fullY - rl.posY)
|
||||
moveCursorForwards(rl.posX)
|
||||
}
|
||||
|
||||
func (rl *Readline) bufprintF(format string, a ...any) {
|
||||
fmt.Fprintf(rl.bufferedOut, format, a...)
|
||||
}
|
||||
|
||||
func (rl *Readline) bufprint(text string) {
|
||||
fmt.Fprint(rl.bufferedOut, text)
|
||||
}
|
||||
|
||||
func (rl *Readline) bufflush() {
|
||||
rl.bufferedOut.Flush()
|
||||
}
|
@ -34,6 +34,7 @@ var (
|
||||
)
|
||||
|
||||
type ViAction int
|
||||
|
||||
const (
|
||||
VimActionYank = iota
|
||||
VimActionPaste
|
||||
@ -52,7 +53,7 @@ var (
|
||||
// vi - Apply a key to a Vi action. Note that as in the rest of the code, all cursor movements
|
||||
// have been moved away, and only the rl.pos is adjusted: when echoing the input line, the shell
|
||||
// will compute the new cursor pos accordingly.
|
||||
func (rl *Instance) vi(r rune) {
|
||||
func (rl *Readline) vi(r rune) {
|
||||
activeRegister := string(rl.registers.currentRegister)
|
||||
|
||||
// Check if we are in register mode. If yes, and for some characters,
|
||||
@ -384,7 +385,7 @@ func (rl *Instance) vi(r rune) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) getViIterations() int {
|
||||
func (rl *Readline) getViIterations() int {
|
||||
i, _ := strconv.Atoi(rl.viIteration)
|
||||
if i < 1 {
|
||||
i = 1
|
||||
@ -393,7 +394,7 @@ func (rl *Instance) getViIterations() int {
|
||||
return i
|
||||
}
|
||||
|
||||
func (rl *Instance) refreshVimStatus() {
|
||||
func (rl *Readline) refreshVimStatus() {
|
||||
rl.ViModeCallback(rl.modeViMode)
|
||||
rl.computePrompt()
|
||||
rl.updateHelpers()
|
||||
@ -401,7 +402,7 @@ func (rl *Instance) refreshVimStatus() {
|
||||
|
||||
// viInfoMessage - lmorg's way of showing Vim status is to overwrite the info.
|
||||
// Currently not used, as there is a possibility to show the current Vim mode in the prompt.
|
||||
func (rl *Instance) viInfoMessage() {
|
||||
func (rl *Readline) viInfoMessage() {
|
||||
switch rl.modeViMode {
|
||||
case VimKeys:
|
||||
rl.infoText = []rune("-- VIM KEYS -- (press `i` to return to normal editing mode)")
|
||||
@ -421,7 +422,7 @@ func (rl *Instance) viInfoMessage() {
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpB(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) viJumpB(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
switch {
|
||||
case len(split) == 0:
|
||||
@ -436,7 +437,7 @@ func (rl *Instance) viJumpB(tokeniser tokeniser) (adjust int) {
|
||||
return adjust * -1
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpE(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) viJumpE(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
||||
@ -459,7 +460,7 @@ func (rl *Instance) viJumpE(tokeniser tokeniser) (adjust int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpW(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) viJumpW(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
switch {
|
||||
case len(split) == 0:
|
||||
@ -472,7 +473,7 @@ func (rl *Instance) viJumpW(tokeniser tokeniser) (adjust int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpPreviousBrace() (adjust int) {
|
||||
func (rl *Readline) viJumpPreviousBrace() (adjust int) {
|
||||
if rl.pos == 0 {
|
||||
return 0
|
||||
}
|
||||
@ -486,7 +487,7 @@ func (rl *Instance) viJumpPreviousBrace() (adjust int) {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpNextBrace() (adjust int) {
|
||||
func (rl *Readline) viJumpNextBrace() (adjust int) {
|
||||
if rl.pos >= len(rl.line)-1 {
|
||||
return 0
|
||||
}
|
||||
@ -500,7 +501,7 @@ func (rl *Instance) viJumpNextBrace() (adjust int) {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpBracket() (adjust int) {
|
||||
func (rl *Readline) viJumpBracket() (adjust int) {
|
||||
split, index, pos := tokeniseBrackets(rl.line, rl.pos)
|
||||
switch {
|
||||
case len(split) == 0:
|
@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
// vimDelete -
|
||||
func (rl *Instance) viDelete(r rune) {
|
||||
func (rl *Readline) viDelete(r rune) {
|
||||
|
||||
// We are allowed to type iterations after a delete ('d') command.
|
||||
// in which case we don't exit the delete mode. The next thing typed
|
||||
@ -91,7 +91,7 @@ func (rl *Instance) viDelete(r rune) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) viDeleteByAdjust(adjust int) {
|
||||
func (rl *Readline) viDeleteByAdjust(adjust int) {
|
||||
var (
|
||||
newLine []rune
|
||||
backOne bool
|
||||
@ -142,7 +142,11 @@ func (rl *Instance) viDeleteByAdjust(adjust int) {
|
||||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) vimDeleteToken(r rune) bool {
|
||||
func (rl *Readline) DeleteByAmount(adjust int) {
|
||||
rl.viDeleteByAdjust(adjust)
|
||||
}
|
||||
|
||||
func (rl *Readline) vimDeleteToken(r rune) bool {
|
||||
tokens, _, _ := tokeniseSplitSpaces(rl.line, 0)
|
||||
pos := int(r) - 48 // convert ASCII to integer
|
||||
if pos > len(tokens) {
|
221
golibs/snail/lua.go
Normal file
221
golibs/snail/lua.go
Normal file
@ -0,0 +1,221 @@
|
||||
// shell script interpreter library
|
||||
/*
|
||||
The snail library houses Hilbish's Lua wrapper of its shell script interpreter.
|
||||
It's not very useful other than running shell scripts, which can be done with other
|
||||
Hilbish functions.
|
||||
*/
|
||||
package snail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
"github.com/arnodel/golua/lib/iolib"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
var snailMetaKey = rt.StringValue("hshsnail")
|
||||
var Loader = packagelib.Loader{
|
||||
Load: loaderFunc,
|
||||
Name: "snail",
|
||||
}
|
||||
|
||||
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
snailMeta := rt.NewTable()
|
||||
snailMethods := rt.NewTable()
|
||||
snailFuncs := map[string]util.LuaExport{
|
||||
"run": {snailrun, 3, false},
|
||||
"dir": {snaildir, 2, false},
|
||||
}
|
||||
util.SetExports(rtm, snailMethods, snailFuncs)
|
||||
|
||||
snailIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
arg := c.Arg(1)
|
||||
val := snailMethods.Get(arg)
|
||||
|
||||
return c.PushingNext1(t.Runtime, val), nil
|
||||
}
|
||||
snailMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(snailIndex, "__index", 2, false)))
|
||||
rtm.SetRegistry(snailMetaKey, rt.TableValue(snailMeta))
|
||||
|
||||
exports := map[string]util.LuaExport{
|
||||
"new": util.LuaExport{snailnew, 0, false},
|
||||
}
|
||||
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
||||
// new() -> @Snail
|
||||
// Creates a new Snail instance.
|
||||
func snailnew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
s := New(t.Runtime)
|
||||
return c.PushingNext1(t.Runtime, rt.UserDataValue(snailUserData(s))), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// run(command, streams)
|
||||
// Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
||||
// #param command string
|
||||
// #param streams table
|
||||
func snailrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := snailArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
streams := &util.Streams{}
|
||||
thirdArg := c.Arg(2)
|
||||
switch thirdArg.Type() {
|
||||
case rt.TableType:
|
||||
args := thirdArg.AsTable()
|
||||
|
||||
if luastreams, ok := args.Get(rt.StringValue("sinks")).TryTable(); ok {
|
||||
handleStream(luastreams.Get(rt.StringValue("out")), streams, false, false)
|
||||
handleStream(luastreams.Get(rt.StringValue("err")), streams, true, false)
|
||||
handleStream(luastreams.Get(rt.StringValue("input")), streams, false, true)
|
||||
}
|
||||
case rt.NilType: // noop
|
||||
default:
|
||||
return nil, errors.New("expected 3rd arg to be a table")
|
||||
}
|
||||
|
||||
var newline bool
|
||||
var cont bool
|
||||
var luaErr rt.Value = rt.NilValue
|
||||
exitCode := 0
|
||||
bg, _, _, err := s.Run(cmd, streams)
|
||||
if err != nil {
|
||||
if syntax.IsIncomplete(err) {
|
||||
/*
|
||||
if !interactive {
|
||||
return cmdString, 126, false, false, err
|
||||
}
|
||||
*/
|
||||
if strings.Contains(err.Error(), "unclosed here-document") {
|
||||
newline = true
|
||||
}
|
||||
cont = true
|
||||
} else {
|
||||
if code, ok := interp.IsExitStatus(err); ok {
|
||||
exitCode = int(code)
|
||||
} else {
|
||||
if exErr, ok := util.IsExecError(err); ok {
|
||||
exitCode = exErr.Code
|
||||
}
|
||||
luaErr = rt.StringValue(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
runnerRet := rt.NewTable()
|
||||
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("newline"), rt.BoolValue(newline))
|
||||
runnerRet.Set(rt.StringValue("err"), luaErr)
|
||||
|
||||
runnerRet.Set(rt.StringValue("bg"), rt.BoolValue(bg))
|
||||
return c.PushingNext1(t.Runtime, rt.TableValue(runnerRet)), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// dir(path)
|
||||
// Changes the directory of the snail instance.
|
||||
// The interpreter keeps its set directory even when the Hilbish process changes
|
||||
// directory, so this should be called on the `hilbish.cd` hook.
|
||||
// #param path string Has to be an absolute path.
|
||||
func snaildir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := snailArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dir, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interp.Dir(dir)(s.runner)
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func handleStream(v rt.Value, strms *util.Streams, errStream, inStream bool) error {
|
||||
if v == rt.NilValue {
|
||||
return nil
|
||||
}
|
||||
|
||||
ud, ok := v.TryUserData()
|
||||
if !ok {
|
||||
return errors.New("expected metatable argument")
|
||||
}
|
||||
|
||||
val := ud.Value()
|
||||
var varstrm io.ReadWriter
|
||||
if f, ok := val.(*iolib.File); ok {
|
||||
varstrm = f.Handle()
|
||||
}
|
||||
|
||||
if f, ok := val.(*util.Sink); ok {
|
||||
varstrm = f.Rw
|
||||
}
|
||||
|
||||
if varstrm == nil {
|
||||
return errors.New("expected either a sink or file")
|
||||
}
|
||||
|
||||
if errStream {
|
||||
strms.Stderr = varstrm
|
||||
} else if inStream {
|
||||
strms.Stdin = varstrm
|
||||
} else {
|
||||
strms.Stdout = varstrm
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func snailArg(c *rt.GoCont, arg int) (*Snail, error) {
|
||||
s, ok := valueToSnail(c.Arg(arg))
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("#%d must be a snail", arg + 1)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func valueToSnail(val rt.Value) (*Snail, bool) {
|
||||
u, ok := val.TryUserData()
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
s, ok := u.Value().(*Snail)
|
||||
return s, ok
|
||||
}
|
||||
|
||||
func snailUserData(s *Snail) *rt.UserData {
|
||||
snailMeta := s.runtime.Registry(snailMetaKey)
|
||||
return rt.NewUserData(s, snailMeta.AsTable())
|
||||
}
|
304
golibs/snail/snail.go
Normal file
304
golibs/snail/snail.go
Normal file
@ -0,0 +1,304 @@
|
||||
package snail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
//"github.com/yuin/gopher-lua/parse"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
)
|
||||
|
||||
// #type
|
||||
// A Snail is a shell script interpreter instance.
|
||||
type Snail struct{
|
||||
runner *interp.Runner
|
||||
runtime *rt.Runtime
|
||||
}
|
||||
|
||||
func New(rtm *rt.Runtime) *Snail {
|
||||
runner, _ := interp.New()
|
||||
|
||||
return &Snail{
|
||||
runner: runner,
|
||||
runtime: rtm,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Snail) Run(cmd string, strms *util.Streams) (bool, io.Writer, io.Writer, error){
|
||||
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
|
||||
if err != nil {
|
||||
return false, nil, nil, err
|
||||
}
|
||||
|
||||
if strms == nil {
|
||||
strms = &util.Streams{}
|
||||
}
|
||||
|
||||
if strms.Stdout == nil {
|
||||
strms.Stdout = os.Stdout
|
||||
}
|
||||
|
||||
if strms.Stderr == nil {
|
||||
strms.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
if strms.Stdin == nil {
|
||||
strms.Stdin = os.Stdin
|
||||
}
|
||||
|
||||
interp.StdIO(strms.Stdin, strms.Stdout, strms.Stderr)(s.runner)
|
||||
interp.Env(nil)(s.runner)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
//printer := syntax.NewPrinter()
|
||||
|
||||
replacer := strings.NewReplacer("[", "\\[", "]", "\\]")
|
||||
|
||||
var bg bool
|
||||
for _, stmt := range file.Stmts {
|
||||
bg = false
|
||||
if stmt.Background {
|
||||
bg = true
|
||||
//printer.Print(buf, stmt.Cmd)
|
||||
|
||||
//stmtStr := buf.String()
|
||||
buf.Reset()
|
||||
//jobs.add(stmtStr, []string{}, "")
|
||||
}
|
||||
|
||||
interp.ExecHandler(func(ctx context.Context, args []string) error {
|
||||
_, argstring := splitInput(strings.Join(args, " "))
|
||||
// i dont really like this but it works
|
||||
aliases := make(map[string]string)
|
||||
aliasesLua, _ := util.DoString(s.runtime, "return hilbish.aliases.list()")
|
||||
util.ForEach(aliasesLua.AsTable(), func(k, v rt.Value) {
|
||||
aliases[k.AsString()] = v.AsString()
|
||||
})
|
||||
if aliases[args[0]] != "" {
|
||||
for i, arg := range args {
|
||||
if strings.Contains(arg, " ") {
|
||||
args[i] = fmt.Sprintf("\"%s\"", arg)
|
||||
}
|
||||
}
|
||||
_, argstring = splitInput(strings.Join(args, " "))
|
||||
|
||||
// If alias was found, use command alias
|
||||
argstring = util.MustDoString(s.runtime, fmt.Sprintf(`return hilbish.aliases.resolve [[%s]]`, replacer.Replace(argstring))).AsString()
|
||||
|
||||
var err error
|
||||
args, err = shell.Fields(argstring, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If command is defined in Lua then run it
|
||||
luacmdArgs := rt.NewTable()
|
||||
for i, str := range args[1:] {
|
||||
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
|
||||
}
|
||||
|
||||
hc := interp.HandlerCtx(ctx)
|
||||
|
||||
cmds := make(map[string]*rt.Closure)
|
||||
luaCmds := util.MustDoString(s.runtime, "local commander = require 'commander'; return commander.registry()").AsTable()
|
||||
util.ForEach(luaCmds, func(k, v rt.Value) {
|
||||
cmds[k.AsString()] = v.AsTable().Get(rt.StringValue("exec")).AsClosure()
|
||||
})
|
||||
if cmd := cmds[args[0]]; cmd != nil {
|
||||
stdin := util.NewSinkInput(s.runtime, hc.Stdin)
|
||||
stdout := util.NewSinkOutput(s.runtime, hc.Stdout)
|
||||
stderr := util.NewSinkOutput(s.runtime, hc.Stderr)
|
||||
|
||||
sinks := rt.NewTable()
|
||||
sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.UserData))
|
||||
sinks.Set(rt.StringValue("input"), rt.UserDataValue(stdin.UserData))
|
||||
sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.UserData))
|
||||
sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.UserData))
|
||||
|
||||
t := rt.NewThread(s.runtime)
|
||||
sig := make(chan os.Signal)
|
||||
exit := make(chan bool)
|
||||
|
||||
luaexitcode := rt.IntValue(63)
|
||||
var err error
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
exit <- true
|
||||
}
|
||||
}()
|
||||
|
||||
signal.Notify(sig, os.Interrupt)
|
||||
select {
|
||||
case <-sig:
|
||||
t.KillContext()
|
||||
return
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
go func() {
|
||||
luaexitcode, err = rt.Call1(t, rt.FunctionValue(cmd), rt.TableValue(luacmdArgs), rt.TableValue(sinks))
|
||||
exit <- true
|
||||
}()
|
||||
|
||||
<-exit
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
|
||||
return interp.NewExitStatus(1)
|
||||
}
|
||||
|
||||
var exitcode uint8
|
||||
|
||||
if code, ok := luaexitcode.TryInt(); ok {
|
||||
exitcode = uint8(code)
|
||||
} else if luaexitcode != rt.NilValue {
|
||||
// deregister commander
|
||||
delete(cmds, args[0])
|
||||
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0])
|
||||
}
|
||||
|
||||
return interp.NewExitStatus(exitcode)
|
||||
}
|
||||
|
||||
path, err := util.LookPath(args[0])
|
||||
if err == util.ErrNotExec {
|
||||
return util.ExecError{
|
||||
Typ: "not-executable",
|
||||
Cmd: args[0],
|
||||
Code: 126,
|
||||
Colon: true,
|
||||
Err: util.ErrNotExec,
|
||||
}
|
||||
} else if err != nil {
|
||||
return util.ExecError{
|
||||
Typ: "not-found",
|
||||
Cmd: args[0],
|
||||
Code: 127,
|
||||
Err: util.ErrNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
killTimeout := 2 * time.Second
|
||||
// from here is basically copy-paste of the default exec handler from
|
||||
// sh/interp but with our job handling
|
||||
|
||||
env := hc.Env
|
||||
envList := os.Environ()
|
||||
env.Each(func(name string, vr expand.Variable) bool {
|
||||
if vr.Exported && vr.Kind == expand.String {
|
||||
envList = append(envList, name+"="+vr.String())
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
cmd := exec.Cmd{
|
||||
Path: path,
|
||||
Args: args,
|
||||
Env: envList,
|
||||
Dir: hc.Dir,
|
||||
Stdin: hc.Stdin,
|
||||
Stdout: hc.Stdout,
|
||||
Stderr: hc.Stderr,
|
||||
}
|
||||
|
||||
//var j *job
|
||||
if bg {
|
||||
/*
|
||||
j = jobs.getLatest()
|
||||
j.setHandle(&cmd)
|
||||
err = j.start()
|
||||
*/
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if done := ctx.Done(); done != nil {
|
||||
go func() {
|
||||
<-done
|
||||
|
||||
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
||||
cmd.Process.Signal(os.Kill)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: don't temporarily leak this goroutine
|
||||
// if the program stops itself with the
|
||||
// interrupt.
|
||||
go func() {
|
||||
time.Sleep(killTimeout)
|
||||
cmd.Process.Signal(os.Kill)
|
||||
}()
|
||||
cmd.Process.Signal(os.Interrupt)
|
||||
}()
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
}
|
||||
|
||||
exit := util.HandleExecErr(err)
|
||||
|
||||
if bg {
|
||||
//j.exitCode = int(exit)
|
||||
//j.finish()
|
||||
}
|
||||
return interp.NewExitStatus(exit)
|
||||
})(s.runner)
|
||||
err = s.runner.Run(context.TODO(), stmt)
|
||||
if err != nil {
|
||||
return bg, strms.Stdout, strms.Stderr, err
|
||||
}
|
||||
}
|
||||
|
||||
return bg, strms.Stdout, strms.Stderr, nil
|
||||
}
|
||||
|
||||
func splitInput(input string) ([]string, string) {
|
||||
// end my suffering
|
||||
// TODO: refactor this garbage
|
||||
quoted := false
|
||||
cmdArgs := []string{}
|
||||
sb := &strings.Builder{}
|
||||
cmdstr := &strings.Builder{}
|
||||
|
||||
for _, r := range input {
|
||||
if r == '"' {
|
||||
// start quoted input
|
||||
// this determines if other runes are replaced
|
||||
quoted = !quoted
|
||||
// dont add back quotes
|
||||
//sb.WriteRune(r)
|
||||
} else if !quoted && r == '~' {
|
||||
// if not in quotes and ~ is found then make it $HOME
|
||||
sb.WriteString(os.Getenv("HOME"))
|
||||
} else if !quoted && r == ' ' {
|
||||
// if not quoted and there's a space then add to cmdargs
|
||||
cmdArgs = append(cmdArgs, sb.String())
|
||||
sb.Reset()
|
||||
} else {
|
||||
sb.WriteRune(r)
|
||||
}
|
||||
cmdstr.WriteRune(r)
|
||||
}
|
||||
if sb.Len() > 0 {
|
||||
cmdArgs = append(cmdArgs, sb.String())
|
||||
}
|
||||
|
||||
return cmdArgs, cmdstr.String()
|
||||
}
|
147
golibs/yarn/yarn.go
Normal file
147
golibs/yarn/yarn.go
Normal file
@ -0,0 +1,147 @@
|
||||
// multi threading library
|
||||
// Yarn is a simple multithreading library. Threads are individual Lua states,
|
||||
// so they do NOT share the same environment as the code that runs the thread.
|
||||
// Bait and Commanders are shared though, so you *can* throw hooks from 1 thread to another.
|
||||
/*
|
||||
Example:
|
||||
|
||||
```lua
|
||||
local yarn = require 'yarn'
|
||||
|
||||
-- calling t will run the yarn thread.
|
||||
local t = yarn.thread(print)
|
||||
t 'printing from another lua state!'
|
||||
```
|
||||
*/
|
||||
package yarn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hilbish/util"
|
||||
"os"
|
||||
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var yarnMetaKey = rt.StringValue("hshyarn")
|
||||
var globalSpool *Yarn
|
||||
|
||||
type Yarn struct {
|
||||
initializer func(*rt.Runtime)
|
||||
Loader packagelib.Loader
|
||||
}
|
||||
|
||||
// #type
|
||||
type Thread struct {
|
||||
rtm *rt.Runtime
|
||||
f rt.Callable
|
||||
}
|
||||
|
||||
func New(init func(*rt.Runtime)) *Yarn {
|
||||
yrn := &Yarn{
|
||||
initializer: init,
|
||||
}
|
||||
yrn.Loader = packagelib.Loader{
|
||||
Load: yrn.loaderFunc,
|
||||
Name: "yarn",
|
||||
}
|
||||
|
||||
globalSpool = yrn
|
||||
|
||||
return yrn
|
||||
}
|
||||
|
||||
func (y *Yarn) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
yarnMeta := rt.NewTable()
|
||||
yarnMeta.Set(rt.StringValue("__call"), rt.FunctionValue(rt.NewGoFunction(yarnrun, "__call", 1, true)))
|
||||
rtm.SetRegistry(yarnMetaKey, rt.TableValue(yarnMeta))
|
||||
|
||||
exports := map[string]util.LuaExport{
|
||||
"thread": {
|
||||
Function: yarnthread,
|
||||
ArgNum: 1,
|
||||
Variadic: false,
|
||||
},
|
||||
}
|
||||
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
||||
func (y *Yarn) init(th *Thread) {
|
||||
y.initializer(th.rtm)
|
||||
}
|
||||
|
||||
// thread(fun) -> @Thread
|
||||
// Creates a new, fresh Yarn thread.
|
||||
// `fun` is the function that will run in the thread.
|
||||
func yarnthread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fun, err := c.CallableArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
yrn := &Thread{
|
||||
rtm: rt.New(os.Stdout),
|
||||
f: fun,
|
||||
}
|
||||
globalSpool.init(yrn)
|
||||
|
||||
return c.PushingNext(t.Runtime, rt.UserDataValue(yarnUserData(t.Runtime, yrn))), nil
|
||||
}
|
||||
|
||||
func yarnrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
yrn, err := yarnArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
yrn.Run(c.Etc())
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func (y *Thread) Run(args []rt.Value) {
|
||||
go func() {
|
||||
term := rt.NewTerminationWith(y.rtm.MainThread().CurrentCont(), 0, true)
|
||||
err := rt.Call(y.rtm.MainThread(), rt.FunctionValue(y.f), args, term)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func yarnArg(c *rt.GoCont, arg int) (*Thread, error) {
|
||||
j, ok := valueToYarn(c.Arg(arg))
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("#%d must be a yarn thread", arg+1)
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
func valueToYarn(val rt.Value) (*Thread, bool) {
|
||||
u, ok := val.TryUserData()
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
j, ok := u.Value().(*Thread)
|
||||
return j, ok
|
||||
}
|
||||
|
||||
func yarnUserData(rtm *rt.Runtime, t *Thread) *rt.UserData {
|
||||
yarnMeta := rtm.Registry(yarnMetaKey)
|
||||
return rt.NewUserData(t, yarnMeta.AsTable())
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
|
6
job.go
6
job.go
@ -56,8 +56,8 @@ func (j *job) start() error {
|
||||
}
|
||||
j.setHandle(&cmd)
|
||||
}
|
||||
// bgProcAttr is defined in execfile_<os>.go, it holds a procattr struct
|
||||
// in a simple explanation, it makes signals from hilbish (sigint)
|
||||
// bgProcAttr is defined in job_<os>.go, it holds a procattr struct
|
||||
// in a simple explanation, it makes signals from hilbish (like sigint)
|
||||
// not go to it (child process)
|
||||
j.handle.SysProcAttr = bgProcAttr
|
||||
// reset output buffers
|
||||
@ -136,7 +136,7 @@ func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
|
||||
if !j.running {
|
||||
err := j.start()
|
||||
exit := handleExecErr(err)
|
||||
exit := util.HandleExecErr(err)
|
||||
j.exitCode = int(exit)
|
||||
j.finish()
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// +build darwin linux
|
||||
//go:build unix
|
||||
|
||||
package main
|
||||
|
||||
@ -10,6 +10,10 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
|
||||
func (j *job) foreground() error {
|
||||
if jobs.foreground {
|
||||
return errors.New("(another) job already foregrounded")
|
||||
|
@ -1,11 +1,16 @@
|
||||
// +build windows
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
||||
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||
}
|
||||
|
||||
func (j *job) foreground() error {
|
||||
return errors.New("not supported on windows")
|
||||
}
|
||||
|
111
lua.go
111
lua.go
@ -3,69 +3,30 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"hilbish/util"
|
||||
"hilbish/golibs/bait"
|
||||
"hilbish/golibs/commander"
|
||||
"hilbish/golibs/fs"
|
||||
"hilbish/golibs/snail"
|
||||
"hilbish/golibs/terminal"
|
||||
"hilbish/golibs/yarn"
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib"
|
||||
"github.com/arnodel/golua/lib/debuglib"
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var minimalconf = `hilbish.prompt '& '`
|
||||
|
||||
func luaInit() {
|
||||
l = rt.New(os.Stdout)
|
||||
l.PushContext(rt.RuntimeContextDef{
|
||||
MessageHandler: debuglib.Traceback,
|
||||
})
|
||||
lib.LoadAll(l)
|
||||
setupSinkType(l)
|
||||
|
||||
lib.LoadLibs(l, hilbishLoader)
|
||||
// yes this is stupid, i know
|
||||
util.DoString(l, "hilbish = require 'hilbish'")
|
||||
loadLibs(l)
|
||||
|
||||
// Add fs and terminal module module to Lua
|
||||
lib.LoadLibs(l, fs.Loader)
|
||||
lib.LoadLibs(l, terminal.Loader)
|
||||
|
||||
cmds := commander.New(l)
|
||||
// When a command from Lua is added, register it for use
|
||||
cmds.Events.On("commandRegister", func(args ...interface{}) {
|
||||
cmdName := args[0].(string)
|
||||
cmd := args[1].(*rt.Closure)
|
||||
|
||||
commands[cmdName] = cmd
|
||||
})
|
||||
cmds.Events.On("commandDeregister", func(args ...interface{}) {
|
||||
cmdName := args[0].(string)
|
||||
|
||||
delete(commands, cmdName)
|
||||
})
|
||||
lib.LoadLibs(l, cmds.Loader)
|
||||
|
||||
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.On("signal.sigint", func(...interface{}) {
|
||||
if !interactive {
|
||||
os.Exit(0)
|
||||
}
|
||||
})
|
||||
|
||||
lr.rl.RawInputCallback = func(r []rune) {
|
||||
hooks.Emit("hilbish.rawInput", string(r))
|
||||
}
|
||||
yarnPool := yarn.New(yarnloadLibs)
|
||||
lib.LoadLibs(l, yarnPool.Loader)
|
||||
|
||||
// Add more paths that Lua can require from
|
||||
_, err := util.DoString(l, "package.path = package.path .. "+requirePaths)
|
||||
@ -75,7 +36,7 @@ func luaInit() {
|
||||
|
||||
err1 := util.DoFile(l, "nature/init.lua")
|
||||
if err1 != nil {
|
||||
err2 := util.DoFile(l, preloadPath)
|
||||
err2 := util.DoFile(l, filepath.Join(dataDir, "nature", "init.lua"))
|
||||
if err2 != nil {
|
||||
fmt.Fprintln(os.Stderr, "Missing nature module, some functionality and builtins will be missing.")
|
||||
fmt.Fprintln(os.Stderr, "local error:", err1)
|
||||
@ -84,6 +45,60 @@ func luaInit() {
|
||||
}
|
||||
}
|
||||
|
||||
func loadLibs(r *rt.Runtime) {
|
||||
r.PushContext(rt.RuntimeContextDef{
|
||||
MessageHandler: debuglib.Traceback,
|
||||
})
|
||||
lib.LoadAll(r)
|
||||
|
||||
lib.LoadLibs(r, hilbishLoader)
|
||||
// yes this is stupid, i know
|
||||
util.DoString(r, "hilbish = require 'hilbish'")
|
||||
|
||||
hooks = bait.New(r)
|
||||
hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) {
|
||||
fmt.Println("Error in `error` hook handler:", err)
|
||||
hooks.Off(event, handler)
|
||||
})
|
||||
lib.LoadLibs(r, hooks.Loader)
|
||||
|
||||
// Add Ctrl-C handler
|
||||
hooks.On("signal.sigint", func(...interface{}) rt.Value {
|
||||
if !interactive {
|
||||
os.Exit(0)
|
||||
}
|
||||
return rt.NilValue
|
||||
})
|
||||
|
||||
lr.rl.RawInputCallback = func(rn []rune) {
|
||||
hooks.Emit("hilbish.rawInput", string(rn))
|
||||
}
|
||||
|
||||
lib.LoadLibs(r, fs.Loader)
|
||||
lib.LoadLibs(r, terminal.Loader)
|
||||
lib.LoadLibs(r, snail.Loader)
|
||||
|
||||
cmds = commander.New(r)
|
||||
lib.LoadLibs(r, cmds.Loader)
|
||||
lib.LoadLibs(l, lr.rl.Loader)
|
||||
}
|
||||
|
||||
func yarnloadLibs(r *rt.Runtime) {
|
||||
r.PushContext(rt.RuntimeContextDef{
|
||||
MessageHandler: debuglib.Traceback,
|
||||
})
|
||||
lib.LoadAll(r)
|
||||
|
||||
lib.LoadLibs(r, hilbishLoader)
|
||||
lib.LoadLibs(r, hooks.Loader)
|
||||
lib.LoadLibs(r, fs.Loader)
|
||||
lib.LoadLibs(r, terminal.Loader)
|
||||
lib.LoadLibs(r, snail.Loader)
|
||||
lib.LoadLibs(r, cmds.Loader)
|
||||
lib.LoadLibs(l, lr.rl.Loader)
|
||||
|
||||
}
|
||||
|
||||
func runConfig(confpath string) {
|
||||
if !interactive {
|
||||
return
|
||||
|
74
main.go
74
main.go
@ -2,16 +2,20 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"hilbish/util"
|
||||
"hilbish/golibs/bait"
|
||||
"hilbish/golibs/commander"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/pborman/getopt"
|
||||
@ -23,7 +27,6 @@ var (
|
||||
l *rt.Runtime
|
||||
lr *lineReader
|
||||
|
||||
commands = map[string]*rt.Closure{}
|
||||
luaCompletions = map[string]*rt.Closure{}
|
||||
|
||||
confDir string
|
||||
@ -31,16 +34,30 @@ var (
|
||||
curuser *user.User
|
||||
|
||||
hooks *bait.Bait
|
||||
cmds *commander.Commander
|
||||
defaultConfPath string
|
||||
defaultHistPath string
|
||||
)
|
||||
|
||||
func main() {
|
||||
if runtime.GOOS == "linux" {
|
||||
// dataDir should only be empty on linux to allow XDG_DATA_DIRS searching.
|
||||
// but since it might be set on some distros (nixos) we should still check if its really is empty.
|
||||
if dataDir == "" {
|
||||
searchableDirs := getenv("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")
|
||||
dataDir = "."
|
||||
for _, path := range strings.Split(searchableDirs, ":") {
|
||||
_, err := os.Stat(filepath.Join(path, "hilbish", ".hilbishrc.lua"))
|
||||
if err == nil {
|
||||
dataDir = filepath.Join(path, "hilbish")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
curuser, _ = user.Current()
|
||||
homedir := curuser.HomeDir
|
||||
confDir, _ = os.UserConfigDir()
|
||||
preloadPath = strings.Replace(preloadPath, "~", homedir, 1)
|
||||
sampleConfPath = strings.Replace(sampleConfPath, "~", homedir, 1)
|
||||
|
||||
// i honestly dont know what directories to use for this
|
||||
switch runtime.GOOS {
|
||||
@ -88,7 +105,7 @@ func main() {
|
||||
interactive = true
|
||||
}
|
||||
|
||||
if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
|
||||
if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 || !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
interactive = false
|
||||
}
|
||||
|
||||
@ -112,7 +129,13 @@ func main() {
|
||||
|
||||
// Set $SHELL if the user wants to
|
||||
if *setshflag {
|
||||
os.Setenv("SHELL", os.Args[0])
|
||||
os.Setenv("SHELL", "hilbish")
|
||||
|
||||
path, err := exec.LookPath("hilbish")
|
||||
if err == nil {
|
||||
os.Setenv("SHELL", path)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lr = newLineReader("", false)
|
||||
@ -128,10 +151,11 @@ func main() {
|
||||
confpath := ".hilbishrc.lua"
|
||||
if err != nil {
|
||||
// If it wasnt found, go to the real sample conf
|
||||
_, err = os.ReadFile(sampleConfPath)
|
||||
confpath = sampleConfPath
|
||||
sampleConfigPath := filepath.Join(dataDir, ".hilbishrc.lua")
|
||||
_, err = os.ReadFile(sampleConfigPath)
|
||||
confpath = sampleConfigPath
|
||||
if err != nil {
|
||||
fmt.Println("could not find .hilbishrc.lua or", sampleConfPath)
|
||||
fmt.Println("could not find .hilbishrc.lua or", sampleConfigPath)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -189,8 +213,12 @@ input:
|
||||
} else {
|
||||
// If we get a completely random error, print
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
if errors.Is(err, syscall.ENOTTY) {
|
||||
// what are we even doing here?
|
||||
panic("not a tty")
|
||||
}
|
||||
<-make(chan struct{})
|
||||
}
|
||||
// TODO: Halt if any other error occurs
|
||||
continue
|
||||
}
|
||||
var priv bool
|
||||
@ -206,8 +234,9 @@ input:
|
||||
}
|
||||
|
||||
if strings.HasSuffix(input, "\\") {
|
||||
print("\n")
|
||||
for {
|
||||
input, err = continuePrompt(input)
|
||||
input, err = continuePrompt(strings.TrimSuffix(input, "\\") + "\n", false)
|
||||
if err != nil {
|
||||
running = true
|
||||
lr.SetPrompt(fmtPrompt(prompt))
|
||||
@ -231,16 +260,24 @@ input:
|
||||
exit(0)
|
||||
}
|
||||
|
||||
func continuePrompt(prev string) (string, error) {
|
||||
func continuePrompt(prev string, newline bool) (string, error) {
|
||||
hooks.Emit("multiline", nil)
|
||||
lr.SetPrompt(multilinePrompt)
|
||||
|
||||
cont, err := lr.Read()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cont = strings.TrimSpace(cont)
|
||||
|
||||
return prev + strings.TrimSuffix(cont, "\n"), nil
|
||||
if newline {
|
||||
cont = "\n" + cont
|
||||
}
|
||||
|
||||
if strings.HasSuffix(cont, "\\") {
|
||||
cont = strings.TrimSuffix(cont, "\\") + "\n"
|
||||
}
|
||||
|
||||
return prev + cont, nil
|
||||
}
|
||||
|
||||
// This semi cursed function formats our prompt (obviously)
|
||||
@ -287,15 +324,6 @@ func removeDupes(slice []string) []string {
|
||||
return newSlice
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if strings.ToLower(a) == strings.ToLower(e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func exit(code int) {
|
||||
jobs.stopAll()
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user