diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 547673bf..88a78ae1 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -1,10 +1,8 @@ name: Build website on: - push: - branches: - - master - - docs-refactor + - push + - pull_request jobs: deploy: @@ -21,11 +19,29 @@ jobs: hugo-version: 'latest' extended: true + - name: Set branch name + id: branch + run: echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> "$GITHUB_ENV" + + - name: Fix base URL + if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea' + run: sed -i "s%baseURL = 'https://rosettea.github.io/Hilbish/'%baseURL = 'https://rosettea.github.io/Hilbish/versions/${{ env.BRANCH_NAME }}'%" website/config.toml + - name: Build run: 'cd website && hugo --minify' - name: Deploy + if: env.BRANCH_NAME == 'master' && github.repository_owner == 'Rosettea' + uses: peaceiris/actions-gh-pages@v3 + 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 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./website/public + destination_dir: versions/${{ env.BRANCH_NAME }} + keep_files: true diff --git a/.hilbishrc.lua b/.hilbishrc.lua index 5d6382b0..249f97ed 100644 --- a/.hilbishrc.lua +++ b/.hilbishrc.lua @@ -1,18 +1,39 @@ -- Default Hilbish config +local hilbish = require 'hilbish' local lunacolors = require 'lunacolors' local bait = require 'bait' local ansikit = require 'ansikit' +local unreadCount = 0 +local running = false local function doPrompt(fail) hilbish.prompt(lunacolors.format( '{blue}%u {cyan}%d ' .. (fail and '{red}' or '{green}') .. '∆ ' )) end +local function doNotifyPrompt() + if running or unreadCount == hilbish.messages.unreadCount() then return end + + local notifPrompt = string.format('• %s unread notification%s', hilbish.messages.unreadCount(), hilbish.messages.unreadCount() > 1 and 's' or '') + unreadCount = hilbish.messages.unreadCount() + hilbish.prompt(lunacolors.blue(notifPrompt), 'right') + + hilbish.timeout(function() + hilbish.prompt('', 'right') + end, 3000) +end + doPrompt() +bait.catch('command.preexec', function() + running = true +end) + bait.catch('command.exit', function(code) + running = false doPrompt(code ~= 0) + doNotifyPrompt() end) bait.catch('hilbish.vimMode', function(mode) @@ -22,3 +43,7 @@ bait.catch('hilbish.vimMode', function(mode) ansikit.cursorStyle(ansikit.lineCursor) end end) + +bait.catch('hilbish.notification', function(notif) + doNotifyPrompt() +end) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec8db207..d8f5cf93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,41 @@ # 🎀 Changelog ## Unreleased +### Added +- Made a few additions to the sink type: + - `read()` method for retrieving input (so now the `in` sink of commanders is useful) + - `flush()` and `autoFlush()` related to flushing outputs + - `pipe` property to check if a sink with input is a pipe (like stdin) +- Add fuzzy search to history search (enable via `hilbish.opts.fuzzy = true`) +- Show indexes on cdr list +- Fix doc command not displaying correct subdocs when using shorthand api doc access (`doc api hilbish.jobs` as an example) +- `hilbish.messages` interface (details in [#219]) +- `hilbish.notification` signal when a message/notification is sent +- `notifyJobFinish` opt to send a notification when background jobs are +completed. +- Allow numbered arg substitutions in aliases. + - Example: `hilbish.alias('hello', 'echo %1 says hello')` allows the user to run `hello hilbish` + which will output `hilbish says hello`. +- Greenhouse + - Greenhouse is a pager library and program. Basic usage is `greenhouse ` + - Using this also brings enhancements to the `doc` command like easy + navigation of neighboring doc files. + ### Fixed +- Fix infinite loop when navigating history without any history. [#252](https://github.com/Rosettea/Hilbish/issues/252) +- Return the prefix when calling `hilbish.completions.call`. [#219](https://github.com/Rosettea/Hilbish/issues/219) - Replaced `sed` in-place editing with `grep` and `mv` for compatibility with BSD utils +## [2.1.2] - 2022-04-10 +### Removed +- Bad april fools code ;( + +## [2.1.1] - 2022-04-01 +### Added +- Validation checks for command input +- Improved runtime performance +- Validate Lua code + ## [2.1.0] - 2022-02-10 ### Added - Documented custom userdata types (Job and Timer Objects) diff --git a/README.md b/README.md index 3a3dbbb1..469630bf 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,35 @@ -
-
-
-
- 🌺 The flower shell. A comfy and nice little shell for Lua fans! -
-

- GitHub commit activity - GitHub commits since latest release (by date) - GitHub contributors
- help wanted - GitHub license - Discord -

-
+
+
+🌓 The Moon-powered shell! A comfy and extensible shell for Lua fans! 🌺 ✨ +
-Hilbish is a extensible shell (framework). It was made to be very customizable -via the Lua programming language. It aims to be easy to use for the casual -people but powerful for those who want to tinker more with their shell, -the thing used to interface with most of the system. +GitHub commit activityGitHub commits since latest release (by date)GitHub contributors
+help wanted +GitHub license +Discord +
+ +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. 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! -# Table of Contents -- [Screenshots](#Screenshots) -- [Getting Hilbish](#Getting-Hilbish) -- [Contributing](#Contributing) - # Screenshots
-

-

+
# Getting Hilbish **NOTE:** Hilbish is not guaranteed to work properly on Windows, starting from the 2.0 version. It will still be able to compile, but functionality -may be lacking. +may be lacking. If you want to contribute to make the situation better, +comment on the Windows discussion. You can check the [install page](https://rosettea.github.io/Hilbish/install/) on the website for distributed binaries from GitHub or other package repositories. diff --git a/Taskfile.yaml b/Taskfile.yaml index 5c5caaec..264e7d51 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -13,12 +13,22 @@ vars: tasks: default: + cmds: + - go build {{.GOFLAGS}} + vars: + GOFLAGS: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}} -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"' + + default-nocgo: cmds: - CGO_ENABLED=0 go build {{.GOFLAGS}} vars: GOFLAGS: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}} -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"' build: + cmds: + - go build {{.GOFLAGS}} + + build-nocgo: cmds: - CGO_ENABLED=0 go build {{.GOFLAGS}} diff --git a/aliases.go b/aliases.go index bfacc43d..8b815b32 100644 --- a/aliases.go +++ b/aliases.go @@ -1,6 +1,8 @@ package main import ( + "regexp" + "strconv" "strings" "sync" @@ -46,9 +48,32 @@ func (a *aliasModule) Resolve(cmdstr string) string { a.mu.RLock() defer a.mu.RUnlock() - args := strings.Split(cmdstr, " ") + arg, _ := regexp.Compile(`[\\]?%\d+`) + + args, _ := splitInput(cmdstr) + if len(args) == 0 { + // this shouldnt reach but...???? + return cmdstr + } + for a.aliases[args[0]] != "" { alias := a.aliases[args[0]] + alias = arg.ReplaceAllStringFunc(alias, func(a string) string { + idx, _ := strconv.Atoi(a[1:]) + if strings.HasPrefix(a, "\\") || idx == 0 { + return strings.TrimPrefix(a, "\\") + } + + if idx + 1 > len(args) { + return a + } + val := args[idx] + args = cut(args, idx) + cmdstr = strings.Join(args, " ") + + return val + }) + cmdstr = alias + strings.TrimPrefix(cmdstr, args[0]) cmdArgs, _ := splitInput(cmdstr) args = cmdArgs diff --git a/api.go b/api.go index 3920e6f2..142e4106 100644 --- a/api.go +++ b/api.go @@ -2,6 +2,7 @@ // The Hilbish module includes the core API, containing // interfaces and functions which directly relate to shell functionality. // #field ver The version of Hilbish +// #field goVersion The version of Go that Hilbish was compiled with // #field user Username of the user // #field host Hostname of the machine // #field dataDir Directory for Hilbish data files, including the docs and default modules @@ -110,6 +111,7 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) { } 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)) @@ -164,6 +166,9 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) { util.SetField(rtm, versionModule, "release", rt.StringValue(releaseName)) mod.Set(rt.StringValue("version"), rt.TableValue(versionModule)) + pluginModule := moduleLoader(rtm) + mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule)) + return rt.TableValue(fakeMod), nil } @@ -639,6 +644,14 @@ func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { // reality could set the input of the prompt to *display* anything. The // callback is passed the current line and is expected to return a line that // will be used as the input display. +// Note that to set a highlighter, one has to override this function. +// Example: +// ``` +// function hilbish.highlighter(line) +// return line:gsub('"%w+"', function(c) return lunacolors.green(c) end) +// end +// ``` +// This code will highlight all double quoted strings in green. // --- @param line string func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil diff --git a/assets/hilbish-flower.png b/assets/hilbish-flower.png index b4fb0f72..866e57ee 100644 Binary files a/assets/hilbish-flower.png and b/assets/hilbish-flower.png differ diff --git a/assets/hilbish-logo-and-text.png b/assets/hilbish-logo-and-text.png new file mode 100644 index 00000000..325034c6 Binary files /dev/null and b/assets/hilbish-logo-and-text.png differ diff --git a/assets/hilbish-text.png b/assets/hilbish-text.png deleted file mode 100644 index 16412c4e..00000000 Binary files a/assets/hilbish-text.png and /dev/null differ diff --git a/cmd/docgen/docgen.go b/cmd/docgen/docgen.go index aae62028..cbe6baae 100644 --- a/cmd/docgen/docgen.go +++ b/cmd/docgen/docgen.go @@ -417,7 +417,7 @@ func main() { f, _ := os.Create(docPath) f.WriteString(fmt.Sprintf(header, modOrIface, modname, modu.ShortDescription)) - typeTag, _ := regexp.Compile(`@\w+`) + typeTag, _ := regexp.Compile(`\B@\w+`) modDescription := typeTag.ReplaceAllStringFunc(strings.Replace(modu.Description, "<", `\<`, -1), func(typ string) string { typName := typ[1:] typLookup := typeTable[strings.ToLower(typName)] diff --git a/complete.go b/complete.go index 51b426fa..0c70e072 100644 --- a/complete.go +++ b/complete.go @@ -253,15 +253,16 @@ func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { } // we must keep the holy 80 cols - completerReturn, err := rt.Call1(l.MainThread(), - rt.FunctionValue(completecb), rt.StringValue(query), - rt.StringValue(ctx), rt.TableValue(fields)) + cont := c.Next() + err = rt.Call(l.MainThread(), rt.FunctionValue(completecb), + []rt.Value{rt.StringValue(query), rt.StringValue(ctx), rt.TableValue(fields)}, + cont) if err != nil { return nil, err } - return c.PushingNext1(t.Runtime, completerReturn), nil + return cont, nil } // #interface completions diff --git a/docs/api/hilbish/_index.md b/docs/api/hilbish/_index.md index 81ca9935..a683172b 100644 --- a/docs/api/hilbish/_index.md +++ b/docs/api/hilbish/_index.md @@ -13,6 +13,7 @@ interfaces and functions which directly relate to shell functionality. ## Interface fields - `ver`: The version of Hilbish +- `goVersion`: The version of Go that Hilbish was compiled with - `user`: Username of the user - `host`: Hostname of the machine - `dataDir`: Directory for Hilbish data files, including the docs and default modules @@ -49,6 +50,14 @@ Line highlighter handler. This is mainly for syntax highlighting, but in reality could set the input of the prompt to *display* anything. The callback is passed the current line and is expected to return a line that will be used as the input display. +Note that to set a highlighter, one has to override this function. +Example: +``` +function hilbish.highlighter(line) + return line:gsub('"%w+"', function(c) return lunacolors.green(c) end) +end +``` +This code will highlight all double quoted strings in green. ### hinter(line, pos) The command line hint handler. It gets called on every key insert to @@ -109,6 +118,19 @@ A sink is a structure that has input and/or output to/from a desination. ### Methods +#### autoFlush(auto) +Sets/toggles the option of automatically flushing output. +A call with no argument will toggle the value. + +#### flush() +Flush writes all buffered input to the sink. + +#### read() -> string +Reads a liine of input from the sink. + +#### readAll() -> string +Reads all input from the sink. + #### write(str) Writes data to a sink. diff --git a/docs/api/hilbish/hilbish.editor.md b/docs/api/hilbish/hilbish.editor.md index 30a3842d..d75d4c2e 100644 --- a/docs/api/hilbish/hilbish.editor.md +++ b/docs/api/hilbish/hilbish.editor.md @@ -21,6 +21,10 @@ Returns the text that is at the register. ### insert(text) Inserts text into the line. +### 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. diff --git a/docs/api/hilbish/hilbish.module.md b/docs/api/hilbish/hilbish.module.md new file mode 100644 index 00000000..e88ac2c6 --- /dev/null +++ b/docs/api/hilbish/hilbish.module.md @@ -0,0 +1,53 @@ +--- +title: Interface hilbish.module +description: native module loading +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction + +The hilbish.module interface provides a function to load +Hilbish plugins/modules. Hilbish modules are Go-written +plugins (see https://pkg.go.dev/plugin) that are used to add functionality +to Hilbish that cannot be written in Lua for any reason. + +Note that you don't ever need to use the load function that is here as +modules can be loaded with a `require` call like Lua C modules, and the +search paths can be changed with the `paths` property here. + +To make a valid native module, the Go plugin has to export a Loader function +with a signature like so: `func(*rt.Runtime) rt.Value`. + +`rt` in this case refers to the Runtime type at +https://pkg.go.dev/github.com/arnodel/golua@master/runtime#Runtime + +Hilbish uses this package as its Lua runtime. You will need to read +it to use it for a native plugin. + +Here is some code for an example plugin: +```go +package main + +import ( + rt "github.com/arnodel/golua/runtime" +) + +func Loader(rtm *rt.Runtime) rt.Value { + return rt.StringValue("hello world!") +} +``` + +This can be compiled with `go build -buildmode=plugin plugin.go`. +If you attempt to require and print the result (`print(require 'plugin')`), it will show "hello world!" + +## Interface fields +- `paths`: A list of paths to search when loading native modules. This is in the style of Lua search paths and will be used when requiring native modules. Example: `?.so;?/?.so` + +## Functions +### load(path) +Loads a module at the designated `path`. +It will throw if any error occurs. + diff --git a/docs/hooks/command.md b/docs/hooks/command.md index cd1ae3ce..2d29f4bf 100644 --- a/docs/hooks/command.md +++ b/docs/hooks/command.md @@ -1,3 +1,8 @@ ++ `command.preexec` -> input, cmdStr > Thrown before a command +is executed. The `input` is the user written command, while `cmdStr` +is what will be executed (`input` will have aliases while `cmdStr` +will have alias resolved input). + + `command.exit` -> code, cmdStr > Thrown when a command exits. `code` is the exit code of the command, and `cmdStr` is the command that was run. diff --git a/docs/hooks/hilbish.md b/docs/hooks/hilbish.md index 3d6d2ea6..71189014 100644 --- a/docs/hooks/hilbish.md +++ b/docs/hooks/hilbish.md @@ -7,3 +7,6 @@ like yanking or pasting text. See `doc vim-mode actions` for more info. + `hilbish.cancel` > Sent when the user cancels their input with Ctrl-C. + ++ `hilbish.notification` -> message > Sent when a message is +sent. diff --git a/editor.go b/editor.go index 3038f074..d720a419 100644 --- a/editor.go +++ b/editor.go @@ -16,6 +16,7 @@ func editorLoader(rtm *rt.Runtime) *rt.Table { "setVimRegister": {editorSetRegister, 1, false}, "getVimRegister": {editorGetRegister, 2, false}, "getLine": {editorGetLine, 0, false}, + "readChar": {editorReadChar, 0, false}, } mod := rt.NewTable() @@ -94,3 +95,13 @@ func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 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 +} diff --git a/emmyLuaDocs/hilbish.lua b/emmyLuaDocs/hilbish.lua index 8b20583d..2ee93ed1 100644 --- a/emmyLuaDocs/hilbish.lua +++ b/emmyLuaDocs/hilbish.lua @@ -40,6 +40,10 @@ function hilbish.editor.getVimRegister(register) end --- Inserts text into the 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. --- @param register string --- @param text string @@ -79,6 +83,14 @@ function hilbish.goro(fn) end --- reality could set the input of the prompt to *display* anything. The --- callback is passed the current line and is expected to return a line that --- will be used as the input display. +--- Note that to set a highlighter, one has to override this function. +--- Example: +--- ``` +--- function hilbish.highlighter(line) +--- return line:gsub('"%w+"', function(c) return lunacolors.green(c) end) +--- end +--- ``` +--- This code will highlight all double quoted strings in green. --- @param line string function hilbish.highlighter(line) end @@ -180,6 +192,22 @@ function hilbish.jobs:foreground() end --- @param cmd string 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 @@ -192,6 +220,10 @@ function hilbish.jobs:start() end --- Stops the job from running. function hilbish.jobs:stop() end +--- Loads a module at the designated `path`. +--- 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`. --- @param cmd string diff --git a/go.mod b/go.mod index 52b274aa..1549f76f 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.17 require ( github.com/arnodel/golua v0.0.0-20220221163911-dfcf252b6f86 github.com/blackfireio/osinfo v1.0.3 - github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036 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 @@ -17,6 +17,7 @@ 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/kylelemons/godebug v1.1.0 // 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 diff --git a/go.sum b/go.sum index 74a351be..7c36fa00 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,5 @@ -github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac h1:dtXrgjch8PQyf7C90anZUquB5U3dr8AcMGJofeuirrI= -github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE= -github.com/Rosettea/golua v0.0.0-20220518005949-116371948fe3 h1:I/wWr40FFLFF9pbT3wLb1FAEZhKb/hUWE+nJ5uHBK2g= -github.com/Rosettea/golua v0.0.0-20220518005949-116371948fe3/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE= -github.com/Rosettea/golua v0.0.0-20220621002945-b05143999437 h1:6lWu4YVLeKuZ8jR9xwHONhkHBsrIbw5dpfG1gtOVw0A= -github.com/Rosettea/golua v0.0.0-20220621002945-b05143999437/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE= 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.20220306140409-795a84b00b4e h1:P2XupP8SaylWaudD1DqbWtZ3mIa8OsE9635LmR+Q+lg= -github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220306140409-795a84b00b4e/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs= 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/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -18,8 +10,6 @@ github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4 github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/blackfireio/osinfo v1.0.3 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c= github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA= -github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4= -github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs= 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= @@ -36,6 +26,8 @@ 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/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/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw= @@ -49,7 +41,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ 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= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +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= @@ -58,18 +51,13 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w 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-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/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-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/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 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 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= diff --git a/lua.go b/lua.go index 0a7c1157..e46d27b1 100644 --- a/lua.go +++ b/lua.go @@ -68,7 +68,7 @@ func luaInit() { } // Add more paths that Lua can require from - err := util.DoString(l, "package.path = package.path .. " + requirePaths) + _, err := util.DoString(l, "package.path = package.path .. " + requirePaths) if err != nil { fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.") } diff --git a/main.go b/main.go index 4fa321c7..90caa47b 100644 --- a/main.go +++ b/main.go @@ -106,7 +106,7 @@ func main() { } if *verflag { - fmt.Printf("Hilbish %s\n", getVersion()) + fmt.Printf("Hilbish %s\nCompiled with %s\n", getVersion(), runtime.Version()) os.Exit(0) } @@ -289,7 +289,7 @@ func removeDupes(slice []string) []string { func contains(s []string, e string) bool { for _, a := range s { - if a == e { + if strings.ToLower(a) == strings.ToLower(e) { return true } } @@ -324,3 +324,7 @@ func getVersion() string { return v.String() } + +func cut(slice []string, idx int) []string { + return append(slice[:idx], slice[idx + 1:]...) +} diff --git a/module.go b/module.go new file mode 100644 index 00000000..2ab55e8d --- /dev/null +++ b/module.go @@ -0,0 +1,92 @@ +package main + +import ( + "plugin" + + "hilbish/util" + + rt "github.com/arnodel/golua/runtime" +) + +// #interface module +// native module loading +// #field paths A list of paths to search when loading native modules. This is in the style of Lua search paths and will be used when requiring native modules. Example: `?.so;?/?.so` +/* +The hilbish.module interface provides a function to load +Hilbish plugins/modules. Hilbish modules are Go-written +plugins (see https://pkg.go.dev/plugin) that are used to add functionality +to Hilbish that cannot be written in Lua for any reason. + +Note that you don't ever need to use the load function that is here as +modules can be loaded with a `require` call like Lua C modules, and the +search paths can be changed with the `paths` property here. + +To make a valid native module, the Go plugin has to export a Loader function +with a signature like so: `func(*rt.Runtime) rt.Value`. + +`rt` in this case refers to the Runtime type at +https://pkg.go.dev/github.com/arnodel/golua@master/runtime#Runtime + +Hilbish uses this package as its Lua runtime. You will need to read +it to use it for a native plugin. + +Here is some code for an example plugin: +```go +package main + +import ( + rt "github.com/arnodel/golua/runtime" +) + +func Loader(rtm *rt.Runtime) rt.Value { + return rt.StringValue("hello world!") +} +``` + +This can be compiled with `go build -buildmode=plugin plugin.go`. +If you attempt to require and print the result (`print(require 'plugin')`), it will show "hello world!" +*/ +func moduleLoader(rtm *rt.Runtime) *rt.Table { + exports := map[string]util.LuaExport{ + "load": {moduleLoad, 2, false}, + } + + mod := rt.NewTable() + util.SetExports(rtm, mod, exports) + + return mod +} + +// #interface module +// load(path) +// Loads a module at the designated `path`. +// It will throw if any error occurs. +func moduleLoad(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.CheckNArgs(1); err != nil { + return nil, err + } + + path, err := c.StringArg(0) + if err != nil { + return nil, err + } + + p, err := plugin.Open(path) + if err != nil { + return nil, err + } + + value, err := p.Lookup("Loader") + if err != nil { + return nil, err + } + + loader, ok := value.(func(*rt.Runtime) rt.Value) + if !ok { + return nil, nil + } + + val := loader(t.Runtime) + + return c.PushingNext1(t.Runtime, val), nil +} diff --git a/nature/commands/cdr.lua b/nature/commands/cdr.lua index 288ae220..e6aba366 100644 --- a/nature/commands/cdr.lua +++ b/nature/commands/cdr.lua @@ -10,7 +10,7 @@ cdr: change directory to one which has been recently visied usage: cdr -to get a list of recent directories, use {green}{underline}cdr list{reset}]]) +to get a list of recent directories, use {green}cdr list{reset}]]) return end @@ -20,7 +20,10 @@ to get a list of recent directories, use {green}{underline}cdr list{reset}]]) sinks.out:writeln 'No directories have been visited.' return 1 end - sinks.out:writeln(table.concat(recentDirs, '\n')) + for idx, d in ipairs(dirs.recentDirs) do + if d:find(hilbish.home, 1, true) then d = fs.join('~', d:sub(hilbish.home:len() + 1)) end + sinks.out:writeln(lunacolors.format(string.format('{cyan}%d{reset} %s', idx, d))) + end return end diff --git a/nature/commands/doc.lua b/nature/commands/doc.lua index d37e6770..ee1e37cc 100644 --- a/nature/commands/doc.lua +++ b/nature/commands/doc.lua @@ -1,6 +1,9 @@ +local ansikit = require 'ansikit' local commander = require 'commander' local fs = require 'fs' local lunacolors = require 'lunacolors' +local Greenhouse = require 'nature.greenhouse' +local Page = require 'nature.greenhouse.page' commander.register('doc', function(args, sinks) local moddocPath = hilbish.dataDir .. '/docs/' @@ -9,11 +12,6 @@ commander.register('doc', function(args, sinks) -- hilbish git moddocPath = './docs/' end - local apidocHeader = [[ -# %s -{grayBg} {white}{italic}%s {reset} - -]] local modules = table.map(fs.readdir(moddocPath), function(f) return lunacolors.underline(lunacolors.blue(string.gsub(f, '.md', ''))) @@ -25,25 +23,51 @@ to Hilbish. Usage: doc
[subdoc] Available sections: ]] .. table.concat(modules, ', ') + local f + local function handleYamlInfo(d) + local vals = {} + local docs = d + + local valsStr = docs:match '%-%-%-\n([^%-%-%-]+)\n' + print(valsStr) + if valsStr then + docs = docs:sub(valsStr:len() + 10, #docs) + + -- parse vals + local lines = string.split(valsStr, '\n') + for _, line in ipairs(lines) do + local key = line:match '(%w+): ' + local val = line:match '^%w+: (.-)$' + + if key then + vals[key] = val + end + end + end + + --docs = docs:sub(1, #docs - 1) + return docs, vals + end + if #args > 0 then local mod = args[1] - local f = io.open(moddocPath .. mod .. '.md', 'rb') + f = io.open(moddocPath .. mod .. '.md', 'rb') local funcdocs = nil local subdocName = args[2] if not f then - -- assume subdir - -- dataDir/docs//.md moddocPath = moddocPath .. mod .. '/' if not subdocName then subdocName = '_index' end f = io.open(moddocPath .. subdocName .. '.md', 'rb') + local oldmoddocPath = moddocPath if not f then - f = io.open(moddocPath .. subdocName:match '%w+' .. '/' .. subdocName .. '.md', 'rb') + moddocPath = moddocPath .. subdocName:match '%w+' .. '/' + f = io.open(moddocPath .. subdocName .. '.md', 'rb') end if not f then - moddocPath = moddocPath .. subdocName .. '/' + moddocPath = oldmoddocPath .. subdocName .. '/' subdocName = args[3] or '_index' f = io.open(moddocPath .. subdocName .. '.md', 'rb') end @@ -52,49 +76,81 @@ Available sections: ]] .. table.concat(modules, ', ') return 1 end end - funcdocs = f:read '*a':gsub('-([%d]+)', '%1') - local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= '_index.md' and f ~= 'index.md' end) - local subdocs = table.map(moddocs, function(fname) - return lunacolors.underline(lunacolors.blue(string.gsub(fname, '.md', ''))) - end) - if #moddocs ~= 0 then - funcdocs = funcdocs .. '\nSubdocs: ' .. table.concat(subdocs, ', ') - end - local valsStr = funcdocs:match '%-%-%-\n([^%-%-%-]+)\n' - local vals = {} - if valsStr then - local _, endpos = funcdocs:find('---\n' .. valsStr .. '\n---\n\n', 1, true) - funcdocs = funcdocs:sub(endpos + 1, #funcdocs) + end - -- parse vals - local lines = string.split(valsStr, '\n') - for _, line in ipairs(lines) do - local key = line:match '(%w+): ' - local val = line:match '^%w+: (.-)$' + local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= '_index.md' and f ~= 'index.md' end) + local subdocs = table.map(moddocs, function(fname) + return lunacolors.underline(lunacolors.blue(string.gsub(fname, '.md', ''))) + end) - if key then - vals[key] = val - end - end + local gh = Greenhouse(sinks.out) + function gh:resize() + local size = terminal.size() + self.region = { + width = size.width, + height = size.height - 3 + } + end + gh:resize() + + function gh:render() + local workingPage = self.pages[self.curPage] + local offset = self.offset + if self.isSpecial then + offset = self.specialOffset + workingPage = self.specialPage end - if mod == 'api' then - funcdocs = string.format(apidocHeader, vals.title, vals.description or 'no description.') .. funcdocs + + self.sink:write(ansikit.getCSI(self.region.height + 2 .. ';1', 'H')) + if not self.isSpecial then + if args[1] == 'api' then + self.sink:writeln(lunacolors.reset(string.format('%s', workingPage.title))) + self.sink:write(lunacolors.format(string.format('{grayBg} ↳ {white}{italic}%s {reset}', workingPage.description or 'No description.'))) + else + self.sink:write(lunacolors.reset(string.format('Viewing doc page %s', moddocPath))) + end end - doc = funcdocs:sub(1, #funcdocs - 1) - f:close() end - local backtickOccurence = 0 - sinks.out:writeln(lunacolors.format(doc:gsub('`', function() - backtickOccurence = backtickOccurence + 1 - if backtickOccurence % 2 == 0 then - return '{reset}' - else - return '{underline}{green}' + local function formatDocText(d) + return lunacolors.format(d:gsub('`', function() + backtickOccurence = backtickOccurence + 1 + if backtickOccurence % 2 == 0 then + return '{reset}' + else + return '{underline}{green}' + end + end):gsub('\n#+.-\n', function(t) + local signature = t:gsub('<.->(.-)', '{underline}%1'):gsub('\\', '<') + return '{bold}{yellow}' .. signature .. '{reset}' + end)) + end + + + local doc, vals = handleYamlInfo(#args == 0 and doc or formatDocText(f:read '*a':gsub('-([%d]+)', '%1'))) + if #moddocs ~= 0 and f then + doc = doc .. '\nSubdocs: ' .. table.concat(subdocs, ', ') .. '\n\n' + end + if f then f:close() end + + local page = Page(vals.title, doc) + page.description = vals.description + gh:addPage(page) + + -- add subdoc pages + for _, sdName in ipairs(moddocs) do + local sdFile = fs.join(sdName, '_index.md') + if sdName:match '.md$' then + sdFile = sdName end - end):gsub('\n#+.-\n', function(t) - local signature = t:gsub('<.->(.-)', '{underline}%1'):gsub('\\', '<') - return '{bold}{yellow}' .. signature .. '{reset}' - end))) + + local f = io.open(moddocPath .. sdFile, 'rb') + local doc, vals = handleYamlInfo(f:read '*a':gsub('-([%d]+)', '%1')) + local page = Page(vals.title, formatDocText(doc)) + page.description = vals.description + gh:addPage(page) + end + ansikit.hideCursor() + gh:initUi() end) diff --git a/nature/commands/greenhouse.lua b/nature/commands/greenhouse.lua new file mode 100644 index 00000000..9c155b0d --- /dev/null +++ b/nature/commands/greenhouse.lua @@ -0,0 +1,124 @@ +local ansikit = require 'ansikit' +local bait = require 'bait' +local commander = require 'commander' +local hilbish = require 'hilbish' +local lunacolors = require 'lunacolors' +local terminal = require 'terminal' +local Greenhouse = require 'nature.greenhouse' +local Page = require 'nature.greenhouse.page' + +commander.register('greenhouse', function(args, sinks) + local gh = Greenhouse(sinks.out) + + local buffer = '' + local display = '' + local command = false + local commands = { + q = function() + gh.keybinds['Ctrl-D'](gh) + end, + ['goto'] = function(args) + if not args[1] then + return 'nuh uh' + end + gh:jump(tonumber(args[1])) + end + } + + function gh:resize() + local size = terminal.size() + self.region = { + width = size.width, + height = size.height - 2 + } + end + + function gh:render() + local workingPage = self.pages[self.curPage] + local offset = self.offset + if self.isSpecial then + offset = self.specialOffset + workingPage = self.specialPage + end + + self.sink:write(ansikit.getCSI(self.region.height + 1 .. ';1', 'H')) + if not self.isSpecial then + self.sink:writeln(lunacolors.format(string.format('{grayBg} ↳ Page %d%s{reset}', self.curPage, workingPage.title and ' — ' .. workingPage.title .. ' ' or ''))) + end + self.sink:write(buffer == '' and display or buffer) + end + function gh:input(c) + -- command handling + if c == ':' and not command then + command = true + end + if c == 'Escape' then + if command then + command = false + buffer = '' + else + if self.isSpecial then gh:special() end + end + elseif c == 'Backspace' then + buffer = buffer:sub(0, -2) + if buffer == '' then + command = false + else + goto update + end + end + + if command then + ansikit.showCursor() + if buffer:match '^:' then buffer = buffer .. c else buffer = c end + else + ansikit.hideCursor() + end + + ::update:: + gh:update() + end + gh:resize() + + gh:keybind('Enter', function(self) + if self.isSpecial then + self:jump(self.specialPageIdx) + self:special(false) + else + if buffer:len() < 2 then return end + + local splitBuf = string.split(buffer, " ") + local command = commands[splitBuf[1]:sub(2)] + if command then + table.remove(splitBuf, 1) + buffer = command(splitBuf) or '' + end + self:update() + end + end) + + if sinks['in'].pipe then + local page = Page('stdin', sinks['in']:readAll()) + gh:addPage(page) + end + + for _, name in ipairs(args) do + local f = io.open(name, 'r') + if not f then + sinks.err:writeln(string.format('could not open file %s', name)) + end + local page = Page(name, f:read '*a') + gh:addPage(page) + end + + if #gh.pages == 0 then + sinks.out:writeln [[greenhouse is the Hilbish pager library and command! +usage: greenhouse ... + +example: greenhouse hello.md]] + return 1 + end + + ansikit.hideCursor() + gh:initUi() +end) diff --git a/nature/greenhouse/init.lua b/nature/greenhouse/init.lua new file mode 100644 index 00000000..d5877e82 --- /dev/null +++ b/nature/greenhouse/init.lua @@ -0,0 +1,328 @@ +-- Greenhouse is a simple text scrolling handler for terminal programs. +-- The idea is that it can be set a region to do its scrolling and paging +-- job and then the user can draw whatever outside it. +-- This reduces code duplication for the message viewer +-- and flowerbook. + +local ansikit = require 'ansikit' +local lunacolors = require 'lunacolors' +local terminal = require 'terminal' +local Page = require 'nature.greenhouse.page' +local Object = require 'nature.object' + +local Greenhouse = Object:extend() + +function Greenhouse:new(sink) + local size = terminal.size() + self.region = size + self.contents = nil -- or can be a table + self.start = 1 -- where to start drawing from (should replace with self.region.y) + self.offset = 1 -- vertical text offset + self.sink = sink + self.pages = {} + self.curPage = 1 + self.keybinds = { + ['Up'] = function(self) self:scroll 'up' end, + ['Down'] = function(self) self:scroll 'down' end, + ['Ctrl-Left'] = self.previous, + ['Ctrl-Right'] = self.next, + ['Ctrl-N'] = function(self) self:toc(true) end, + ['Enter'] = function(self) + if self.isSpecial then + self:jump(self.specialPageIdx) + self:special(false) + end + end + } + self.isSpecial = false + self.specialPage = nil + self.specialPageIdx = 1 + self.specialOffset = 1 + + return self +end + +function Greenhouse:addPage(page) + table.insert(self.pages, page) +end + +function Greenhouse:updateCurrentPage(text) + local page = self.pages[self.curPage] + page:setText(text) +end + +local function sub(str, limit) + local overhead = 0 + local function addOverhead(s) + overhead = overhead + string.len(s) + end + + local s = str:gsub('\x1b%[%d+;%d+;%d+;%d+;%d+%w', addOverhead) + :gsub('\x1b%[%d+;%d+;%d+;%d+%w', addOverhead) + :gsub('\x1b%[%d+;%d+;%d+%w',addOverhead) + :gsub('\x1b%[%d+;%d+%w', addOverhead) + :gsub('\x1b%[%d+%w', addOverhead) + + return s:sub(0, limit + overhead) +end + +function Greenhouse:draw() + local workingPage = self.pages[self.curPage] + local offset = self.offset + if self.isSpecial then + offset = self.specialOffset + workingPage = self.specialPage + end + + if workingPage.lazy and not workingPage.loaded then + workingPage.initialize() + end + + local lines = workingPage.lines + self.sink:write(ansikit.getCSI(self.start .. ';1', 'H')) + self.sink:write(ansikit.getCSI(2, 'J')) + + for i = offset, offset + self.region.height - 1 do + if i > #lines then break end + + local writer = self.sink.writeln + if i == offset + self.region.height - 1 then writer = self.sink.write end + + writer(self.sink, sub(lines[i]:gsub('\t', ' '), self.region.width)) + end + self:render() +end + +function Greenhouse:render() +end + +function Greenhouse:scroll(direction) + if self.isSpecial then + if direction == 'down' then + self:next(true) + elseif direction == 'up' then + self:previous(true) + end + return + end + + local lines = self.pages[self.curPage].lines + + local oldOffset = self.offset + if direction == 'down' then + self.offset = math.min(self.offset + 1, math.max(1, #lines - self.region.height)) + elseif direction == 'up' then + self.offset = math.max(self.offset - 1, 1) + end + + if self.offset ~= oldOffset then self:draw() end +end + +function Greenhouse:update() + self:resize() + if self.isSpecial then + self:updateSpecial() + end + + self:draw() +end + + +function Greenhouse:special(val) + self.isSpecial = val + self:update() +end + +function Greenhouse:toggleSpecial() + self:special(not self.isSpecial) +end + +--- This function will be called when the special page +--- is on and needs to be updated. +function Greenhouse:updateSpecial() +end + +function Greenhouse:contents() +end + +function Greenhouse:toc(toggle) + if not self.isSpecial then + self.specialPageIdx = self.curPage + end + if toggle then self.isSpecial = not self.isSpecial end + -- Generate a special page for our table of contents + local tocText = string.format([[ +%s + +]], lunacolors.cyan(lunacolors.bold '―― Table of Contents ――')) + + local genericPageCount = 1 + local contents = self:contents() + if contents then + for i, c in ipairs(contents) do + local title = c.title + if c.active then + title = lunacolors.invert(title) + end + + tocText = tocText .. title .. '\n' + end + else + for i, page in ipairs(self.pages) do + local title = page.title + if title == 'Page' then + title = 'Page #' .. genericPageCount + genericPageCount = genericPageCount + 1 + end + if i == self.specialPageIdx then + title = lunacolors.invert(title) + end + + tocText = tocText .. title .. '\n' + end + end + self.specialPage = Page('TOC', tocText) + function self:updateSpecial() + self:toc() + end + self:draw() +end + +function Greenhouse:resize() + local size = terminal.size() + self.region = size +end + +function Greenhouse:next(special) + local oldCurrent = special and self.specialPageIdx or self.curPage + local pageIdx = math.min(oldCurrent + 1, #self.pages) + + if special then + self.specialPageIdx = pageIdx + else + self.curPage = pageIdx + end + + if pageIdx ~= oldCurrent then + self.offset = 1 + self:update() + end +end + +function Greenhouse:previous(special) + local oldCurrent = special and self.specialPageIdx or self.curPage + local pageIdx = math.max(self.curPage - 1, 1) + + if special then + self.specialPageIdx = pageIdx + else + self.curPage = pageIdx + end + + if pageIdx ~= oldCurrent then + self.offset = 1 + self:update() + end +end + +function Greenhouse:jump(idx) + if idx ~= self.curPage then + self.offset = 1 + end + self.curPage = idx + self:update() +end + +function Greenhouse:keybind(key, callback) + self.keybinds[key] = callback +end + +function Greenhouse:input(char) +end + +function Greenhouse:initUi() + local ansikit = require 'ansikit' + local bait = require 'bait' + local commander = require 'commander' + local hilbish = require 'hilbish' + local terminal = require 'terminal' + local Page = require 'nature.greenhouse.page' + local done = false + + bait.catch('signal.sigint', function() + ansikit.clear() + done = true + end) + + bait.catch('signal.resize', function() + self:update() + end) + + ansikit.screenAlt() + ansikit.clear(true) + self:draw() + + hilbish.goro(function() + while not done do + local c = read() + self:keybind('Ctrl-D', function() + done = true + end) + + if self.keybinds[c] then + self.keybinds[c](self) + else + self:input(c) + end + + --[[ + if c == 27 then + local c1 = read() + if c1 == 91 then + local c2 = read() + if c2 == 66 then -- arrow down + self:scroll 'down' + elseif c2 == 65 then -- arrow up + self:scroll 'up' + end + + if c2 == 49 then + local c3 = read() + if c3 == 59 then + local c4 = read() + if c4 == 53 then + local c5 = read() + if c5 == 67 then + self:next() + elseif c5 == 68 then + self:previous() + end + end + end + end + end + goto continue + end + ]]-- + + ::continue:: + end + end) + + while not done do + -- + end + ansikit.showCursor() + ansikit.screenMain() +end + +function read() + terminal.saveState() + terminal.setRaw() + local c = hilbish.editor.readChar() + + terminal.restoreState() + return c +end + +return Greenhouse diff --git a/nature/greenhouse/page.lua b/nature/greenhouse/page.lua new file mode 100644 index 00000000..51d1440f --- /dev/null +++ b/nature/greenhouse/page.lua @@ -0,0 +1,32 @@ +local Object = require 'nature.object' + +local Page = Object:extend() + +function Page:new(title, text) + self:setText(text) + self.title = title or 'Page' + self.lazy = false + self.loaded = true + self.children = {} +end + +function Page:setText(text) + self.lines = string.split(text, '\n') +end + +function Page:setTitle(title) + self.title = title +end + +function Page:dynamic(initializer) + self.initializer = initializer + self.lazy = true + self.loaded = false +end + +function Page:initialize() + self.initializer() + self.loaded = true +end + +return Page diff --git a/nature/hummingbird.lua b/nature/hummingbird.lua new file mode 100644 index 00000000..581e92c1 --- /dev/null +++ b/nature/hummingbird.lua @@ -0,0 +1,84 @@ +local bait = require 'bait' +local commander = require 'commander' +local lunacolors = require 'lunacolors' + +local M = {} +local counter = 0 +local unread = 0 +M._messages = {} +M.icons = { + INFO = '', + SUCCESS = '', + WARN = '', + ERROR = '' +} + +hilbish.messages = {} + +--- Represents a Hilbish message. +--- @class hilbish.message +--- @field icon string Unicode (preferably standard emoji) icon for the message notification. +--- @field title string Title of the message (like an email subject). +--- @field text string Contents of the message. +--- @field channel string Short identifier of the message. `hilbish` and `hilbish.*` is preserved for internal Hilbish messages. +--- @field summary string A short summary of the message. +--- @field read boolean Whether the full message has been read or not. + +function expect(tbl, field) + if not tbl[field] or tbl[field] == '' then + error(string.format('expected field %s in message')) + end +end + +--- Sends a message. +--- @param message hilbish.message +function hilbish.messages.send(message) + expect(message, 'text') + expect(message, 'title') + counter = counter + 1 + unread = unread + 1 + message.index = counter + message.read = false + + M._messages[message.index] = message + bait.throw('hilbish.notification', message) +end + +function hilbish.messages.read(idx) + local msg = M._messages[idx] + if msg then + M._messages[idx].read = true + unread = unread - 1 + end +end + +function hilbish.messages.readAll(idx) + for _, msg in ipairs(hilbish.messages.all()) do + hilbish.messages.read(msg.index) + end +end + +function hilbish.messages.unreadCount() + return unread +end + +function hilbish.messages.delete(idx) + local msg = M._messages[idx] + if not msg then + error(string.format('invalid message index %d', idx or -1)) + end + + M._messages[idx] = nil +end + +function hilbish.messages.clear() + for _, msg in ipairs(hilbish.messages.all()) do + hilbish.messages.delete(msg.index) + end +end + +function hilbish.messages.all() + return M._messages +end + +return M diff --git a/nature/init.lua b/nature/init.lua index d1f919cc..a0579d72 100644 --- a/nature/init.lua +++ b/nature/init.lua @@ -6,11 +6,24 @@ local fs = require 'fs' package.path = package.path .. ';' .. hilbish.dataDir .. '/?/init.lua' .. ';' .. hilbish.dataDir .. '/?/?.lua' .. ";" .. hilbish.dataDir .. '/?.lua' +hilbish.module.paths = '?.so;?/?.so;' +.. hilbish.userDir.data .. 'hilbish/libs/?/?.so' +.. ";" .. hilbish.userDir.data .. 'hilbish/libs/?.so' + +table.insert(package.searchers, function(module) + local path = package.searchpath(module, hilbish.module.paths) + if not path then return nil end + + -- it didnt work normally, idk + return function() return hilbish.module.load(path) end, path +end) + require 'nature.commands' require 'nature.completions' require 'nature.opts' require 'nature.vim' require 'nature.runner' +require 'nature.hummingbird' local shlvl = tonumber(os.getenv 'SHLVL') if shlvl ~= nil then diff --git a/nature/object.lua b/nature/object.lua new file mode 100644 index 00000000..053be4a9 --- /dev/null +++ b/nature/object.lua @@ -0,0 +1,59 @@ +---@class nature.object +---@field super nature.object +local Object = {} +Object.__index = Object + +---Can be overrided by child objects to implement a constructor. +function Object:new() end + +---@return nature.object +function Object:extend() + local cls = {} + for k, v in pairs(self) do + if k:find("__") == 1 then + cls[k] = v + end + end + cls.__index = cls + cls.super = self + setmetatable(cls, self) + return cls +end + +---Check if the object is strictly of the given type. +---@param T any +---@return boolean +function Object:is(T) + return getmetatable(self) == T +end + +---Check if the object inherits from the given type. +---@param T any +---@return boolean +function Object:extends(T) + local mt = getmetatable(self) + while mt do + if mt == T then + return true + end + mt = getmetatable(mt) + end + return false +end + +---Metamethod to get a string representation of an object. +---@return string +function Object:__tostring() + return "Object" +end + +---Methamethod to allow using the object call as a constructor. +---@return nature.object +function Object:__call(...) + local obj = setmetatable({}, self) + obj:new(...) + return obj +end + + +return Object diff --git a/nature/opts/init.lua b/nature/opts/init.lua index ae95ee1e..56c34ba4 100644 --- a/nature/opts/init.lua +++ b/nature/opts/init.lua @@ -16,7 +16,7 @@ setmetatable(hilbish.opts, { local function setupOpt(name, default) opts[name] = default - require('nature.opts.' .. name) + pcall(require, 'nature.opts.' .. name) end local defaultOpts = { @@ -25,7 +25,9 @@ local defaultOpts = { greeting = string.format([[Welcome to {magenta}Hilbish{reset}, {cyan}%s{reset}. The nice lil shell for {blue}Lua{reset} fanatics! ]], hilbish.user), - motd = true + motd = true, + fuzzy = false, + notifyJobFinish = true } for optsName, default in pairs(defaultOpts) do diff --git a/nature/opts/notifyJobFinish.lua b/nature/opts/notifyJobFinish.lua new file mode 100644 index 00000000..a8841a11 --- /dev/null +++ b/nature/opts/notifyJobFinish.lua @@ -0,0 +1,23 @@ +local bait = require 'bait' +local lunacolors = require 'lunacolors' + +bait.catch('job.done', function(job) + if not hilbish.opts.notifyJobFinish then return end + local notifText = string.format(lunacolors.format [[ +Background job with ID#%d has exited (PID %d). +Command string: {bold}{yellow}%s{reset}]], job.id, job.pid, job.cmd) + + if job.stdout ~= '' then + notifText = notifText .. '\n\nStandard output:\n' .. job.stdout + end + if job.stderr ~= '' then + notifText = notifText .. '\n\nStandard error:\n' .. job.stderr + end + + hilbish.messages.send { + channel = 'jobNotify', + title = string.format('Job ID#%d Exited', job.id), + summary = string.format(lunacolors.format 'Background job with command {bold}{yellow}%s{reset} has finished running!', job.cmd), + text = notifText + } +end) diff --git a/readline/codes.go b/readline/codes.go index 492bc721..dd8495d0 100644 --- a/readline/codes.go +++ b/readline/codes.go @@ -1,5 +1,7 @@ package readline +import "os" + // Character codes const ( charCtrlA = iota + 1 @@ -134,3 +136,57 @@ const ( const ( seqCtermFg255 = "\033[48;5;255m" ) + +// 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 { + 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 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" + } + } + + return s +} diff --git a/readline/comp-group.go b/readline/comp-group.go index 0c53ed19..b2ee4b89 100644 --- a/readline/comp-group.go +++ b/readline/comp-group.go @@ -71,10 +71,9 @@ func (g *CompletionGroup) init(rl *Instance) { // The rx parameter is passed, as the shell already checked that the search pattern is valid. func (g *CompletionGroup) updateTabFind(rl *Instance) { - suggs := make([]string, 0) - + 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. - for i := range g.Suggestions { + /*for i := range g.Suggestions { if rl.regexSearch == nil { continue } if rl.regexSearch.MatchString(g.Suggestions[i]) { suggs = append(suggs, g.Suggestions[i]) @@ -82,7 +81,7 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) { // this is a list so lets also check the descriptions suggs = append(suggs, g.Suggestions[i]) } - } + }*/ // We overwrite the group's items, (will be refreshed as soon as something is typed in the search) g.Suggestions = suggs diff --git a/readline/history.go b/readline/history.go index 91dccb94..77637a31 100644 --- a/readline/history.go +++ b/readline/history.go @@ -167,8 +167,8 @@ func (rl *Instance) walkHistory(i int) { rl.updateHelpers() // In order to avoid having to type j/k twice each time for history navigation, - // we walk once again. This only ever happens when we aren't out of bounds. - if dedup && old == new { + // we walk once again. This only ever happens when we aren't out of bounds and the last history item was not a empty string. + if new != "" && dedup && old == new { rl.walkHistory(i) } } diff --git a/readline/instance.go b/readline/instance.go index 039f0401..a4772466 100644 --- a/readline/instance.go +++ b/readline/instance.go @@ -112,8 +112,10 @@ type Instance struct { modeAutoFind bool // for when invoked via ^R or ^F outside of [tab] searchMode FindMode // Used for varying hints, and underlying functions called regexSearch *regexp.Regexp // Holds the current search regex match + search string mainHist bool // Which history stdin do we want histInfo []rune // We store a piece of hist info, for dual history sources + Searcher func(string, []string) []string // // History ----------------------------------------------------------------------------------- @@ -229,6 +231,25 @@ func NewInstance() *Instance { rl.HintFormatting = "\x1b[2m" rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn) rl.TempDirectory = os.TempDir() + rl.Searcher = func(needle string, haystack []string) []string { + suggs := make([]string, 0) + + var err error + rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine)) + if err != nil { + 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.MatchString(hay) { + suggs = append(suggs, hay) + } + } + + return suggs + } // Registers rl.initRegisters() diff --git a/readline/tab.go b/readline/tab.go index e6522e6e..d00decc3 100644 --- a/readline/tab.go +++ b/readline/tab.go @@ -94,7 +94,7 @@ func (rl *Instance) getTabSearchCompletion() { rl.getCurrentGroup() // Set the info for this completion mode - rl.infoText = append([]rune("Completion search: "), rl.tfLine...) + rl.infoText = append([]rune("Completion search: " + UNDERLINE + BOLD), rl.tfLine...) for _, g := range rl.tcGroups { g.updateTabFind(rl) @@ -102,7 +102,7 @@ func (rl *Instance) getTabSearchCompletion() { // If total number of matches is zero, we directly change the info, and return if comps, _, _ := rl.getCompletionCount(); comps == 0 { - rl.infoText = append(rl.infoText, []rune(DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...) + rl.infoText = append(rl.infoText, []rune(RESET+DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...) } } diff --git a/readline/tabfind.go b/readline/tabfind.go index aa382596..830dad39 100644 --- a/readline/tabfind.go +++ b/readline/tabfind.go @@ -1,9 +1,5 @@ package readline -import ( - "regexp" -) - // FindMode defines how the autocomplete suggestions display type FindMode int @@ -30,12 +26,7 @@ func (rl *Instance) updateTabFind(r []rune) { rl.tfLine = append(rl.tfLine, r...) // The search regex is common to all search modes - var err error - rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine)) - if err != nil { - rl.RefreshPromptLog(err.Error()) - rl.infoText = []rune(Red("Failed to match search regexp")) - } + rl.search = string(rl.tfLine) // We update and print rl.clearHelpers() diff --git a/readline/tui-effects.go b/readline/tui-effects.go index 491ef98b..5610b103 100644 --- a/readline/tui-effects.go +++ b/readline/tui-effects.go @@ -14,6 +14,7 @@ var ( // effects BOLD = "\033[1m" DIM = "\033[2m" + UNDERLINE = "\033[4m" RESET = "\033[0m" // colors RED = "\033[31m" diff --git a/rl.go b/rl.go index 96b84514..17ea4df3 100644 --- a/rl.go +++ b/rl.go @@ -7,8 +7,9 @@ import ( "hilbish/util" - "github.com/maxlandon/readline" rt "github.com/arnodel/golua/runtime" + "github.com/maxlandon/readline" + "github.com/sahilm/fuzzy" ) type lineReader struct { @@ -24,6 +25,24 @@ func newLineReader(prompt string, noHist bool) *lineReader { rl: rl, } + regexSearcher := rl.Searcher + rl.Searcher = func(needle string, haystack []string) []string { + fz, _ := util.DoString(l, "return hilbish.opts.fuzzy") + fuzz, ok := fz.TryBool() + if !fuzz || !ok { + return regexSearcher(needle, haystack) + } + + matches := fuzzy.Find(needle, haystack) + suggs := make([]string, 0) + + for _, match := range matches { + suggs = append(suggs, match.Str) + } + + return suggs + } + // we don't mind hilbish.read rl instances having completion, // but it cant have shared history if !noHist { diff --git a/sink.go b/sink.go index 9a98856a..3aa5507f 100644 --- a/sink.go +++ b/sink.go @@ -1,8 +1,11 @@ package main import ( + "bufio" "fmt" "io" + "os" + "strings" "hilbish/util" @@ -15,9 +18,11 @@ var sinkMetaKey = rt.StringValue("hshsink") // A sink is a structure that has input and/or output to/from // a desination. type sink struct{ - writer io.Writer - reader io.Reader + writer *bufio.Writer + reader *bufio.Reader + file *os.File ud *rt.UserData + autoFlush bool } func setupSinkType(rtm *rt.Runtime) { @@ -25,15 +30,36 @@ func setupSinkType(rtm *rt.Runtime) { sinkMethods := rt.NewTable() sinkFuncs := map[string]util.LuaExport{ + "flush": {luaSinkFlush, 1, false}, + "read": {luaSinkRead, 1, false}, + "readAll": {luaSinkReadAll, 1, false}, + "autoFlush": {luaSinkAutoFlush, 2, false}, "write": {luaSinkWrite, 2, false}, "writeln": {luaSinkWriteln, 2, false}, } util.SetExports(l, sinkMethods, sinkFuncs) sinkIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + s, _ := sinkArg(c, 0) + arg := c.Arg(1) val := sinkMethods.Get(arg) + if val != rt.NilValue { + return c.PushingNext1(t.Runtime, val), nil + } + + keyStr, _ := arg.TryString() + + switch keyStr { + case "pipe": + val = rt.BoolValue(false) + if s.file != nil { + fileInfo, _ := s.file.Stat(); + val = rt.BoolValue(fileInfo.Mode() & os.ModeCharDevice == 0) + } + } + return c.PushingNext1(t.Runtime, val), nil } @@ -41,6 +67,57 @@ func setupSinkType(rtm *rt.Runtime) { l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta)) } + +// #member +// readAll() -> string +// --- @returns string +// Reads all input from the sink. +func luaSinkReadAll(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + + s, err := sinkArg(c, 0) + if err != nil { + return nil, err + } + + lines := []string{} + for { + line, err := s.reader.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } + + return nil, err + } + + lines = append(lines, line) + } + + return c.PushingNext1(t.Runtime, rt.StringValue(strings.Join(lines, ""))), nil +} + +// #member +// read() -> string +// --- @returns string +// Reads a liine of input from the sink. +func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + + s, err := sinkArg(c, 0) + if err != nil { + return nil, err + } + + str, _ := s.reader.ReadString('\n') + + return c.PushingNext1(t.Runtime, rt.StringValue(str)), nil +} + // #member // write(str) // Writes data to a sink. @@ -59,6 +136,9 @@ func luaSinkWrite(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { } s.writer.Write([]byte(data)) + if s.autoFlush { + s.writer.Flush() + } return c.Next(), nil } @@ -81,22 +161,74 @@ func luaSinkWriteln(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { } s.writer.Write([]byte(data + "\n")) + if s.autoFlush { + s.writer.Flush() + } + + return c.Next(), nil +} + +// #member +// flush() +// Flush writes all buffered input to the sink. +func luaSinkFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + + s, err := sinkArg(c, 0) + if err != nil { + return nil, err + } + + s.writer.Flush() + + return c.Next(), nil +} + +// #member +// autoFlush(auto) +// Sets/toggles the option of automatically flushing output. +// A call with no argument will toggle the value. +// --- @param auto boolean|nil +func luaSinkAutoFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + s, err := sinkArg(c, 0) + if err != nil { + return nil, err + } + + v := c.Arg(1) + if v.Type() != rt.BoolType && v.Type() != rt.NilType { + return nil, fmt.Errorf("#1 must be a boolean") + } + + value := !s.autoFlush + if v.Type() == rt.BoolType { + value = v.AsBool() + } + + s.autoFlush = value return c.Next(), nil } func newSinkInput(r io.Reader) *sink { s := &sink{ - reader: r, + reader: bufio.NewReader(r), } s.ud = sinkUserData(s) + if f, ok := r.(*os.File); ok { + s.file = f + } + return s } func newSinkOutput(w io.Writer) *sink { s := &sink{ - writer: w, + writer: bufio.NewWriter(w), + autoFlush: true, } s.ud = sinkUserData(s) diff --git a/testplugin/testplugin.go b/testplugin/testplugin.go new file mode 100644 index 00000000..2d8a41be --- /dev/null +++ b/testplugin/testplugin.go @@ -0,0 +1,9 @@ +package main + +import ( + rt "github.com/arnodel/golua/runtime" +) + +func Loader(rtm *rt.Runtime) rt.Value { + return rt.StringValue("hello world!") +} diff --git a/testplugin/testplugin.so b/testplugin/testplugin.so new file mode 100644 index 00000000..3c83992c Binary files /dev/null and b/testplugin/testplugin.so differ diff --git a/util/util.go b/util/util.go index 45e33dce..0fcd4b05 100644 --- a/util/util.go +++ b/util/util.go @@ -26,13 +26,14 @@ func SetFieldProtected(module, realModule *rt.Table, field string, value rt.Valu } // DoString runs the code string in the Lua runtime. -func DoString(rtm *rt.Runtime, code string) error { +func DoString(rtm *rt.Runtime, code string) (rt.Value, error) { chunk, err := rtm.CompileAndLoadLuaChunk("", []byte(code), rt.TableValue(rtm.GlobalEnv())) + var ret rt.Value if chunk != nil { - _, err = rt.Call1(rtm.MainThread(), rt.FunctionValue(chunk)) + ret, err = rt.Call1(rtm.MainThread(), rt.FunctionValue(chunk)) } - return err + return ret, err } // DoFile runs the contents of the file in the Lua runtime. diff --git a/vars.go b/vars.go index 7cbc7683..583db716 100644 --- a/vars.go +++ b/vars.go @@ -13,6 +13,7 @@ var ( var ( ver = "v2.2.0" releaseName = "Poppy" + gitCommit string gitBranch string ) diff --git a/website/content/_index.md b/website/content/_index.md index 2b1087b5..44217986 100644 --- a/website/content/_index.md +++ b/website/content/_index.md @@ -8,11 +8,11 @@ description: 'Something Unique. Hilbish is the new interactive shell for Lua fan

Something Unique.

- Hilbish is the new interactive shell for Lua fans.
- Extensible, scriptable, configurable: All in Lua. + 🌺 Hilbish is the new Moon-powered interactive shell for Lua fans!
+ Extensible, scriptable, configurable: All in Lua. ✨

Install - Github + GitHub

@@ -108,14 +108,28 @@ description: 'Something Unique. Hilbish is the new interactive shell for Lua fan
+

Screenshots

+
+
+ +
+
+ +
+
+ +
+
+
+

Why not just Lua?

Hilbish is your interactive shell as well as a just a Lua interpreter and enhanced REPL.

-
    +
    • Batteries included Lua runtime that's also your user shell!
    • -
    • Hilbish is easily cross platform. It has OS agnostic interfaces for easy cross platform Lua code.
    • +
    • Provides cross-platform and OS agnostic APIs to ensure your Lua code works everywhere Hilbish does, as expected.

    @@ -123,11 +137,10 @@ description: 'Something Unique. Hilbish is the new interactive shell for Lua fan

    Try It Today!

    Hilbish is known to run on the 3 major platforms (Windows, MacOS, Linux) - but likely builds on other Unixes! -
    - Windows doesn't work as well as it should, so if you're a Windows user, + but likely builds on other Unixes! Windows doesn't work as well as it should, + so if you're a Windows user, say something! -

      + diff --git a/website/content/blog/improving-this-website.md b/website/content/blog/improving-this-website.md new file mode 100644 index 00000000..0cff4647 --- /dev/null +++ b/website/content/blog/improving-this-website.md @@ -0,0 +1,66 @@ +--- +title: "Improving Hilbish's Branding" +date: 2023-04-13T22:15:31-04:00 +draft: false +--- + +Happy birthday Hilbish! As of last month, Hilbish is now 2 years old. +Unfortunately I missed the official date, but I will still make a more +focused post on the date (19st). + +I decided to fix up this website and Hilbish's logo, so that can +be thought of as something for the 2 years milestone? + +# Logo +Hilbish's old logo was.. not that good. It definitely functioned +as a logo, but the yellow part of it looked ugly (sorry old logo). + + +
      + +You would have definitely seen the new logo, since it is currently +in use on the navigation bar and footer. Here it is in a bigger view: + + +
      + +# Website +Ever since this website was first made, from the release of v2.0, it has +been doing it's job of being a website good enough, but there were a few issues. + +# Padding +Padding is very important! The edges of your screen need space to do nothing, +after all. On mobile or screens small enough, there would not be enough space +for the auto margin to fill, and since there was no padding besides that, +it means things would look a bit cramped. This was simple to fix. + +Here it is before: +![Before](https://safe.kashima.moe/nupzzalt2oa4.png) + +and after: +![After](https://safe.kashima.moe/r0ox4nazfi0q.png) + +# Docs Navigation +On the docs page, the pages are on the left on desktop. Since +phones are too small to have this content on the side, it stays at the top. +This is a bit counter intuitive since it brings in extra scrolling +when navigating to every page for docs and just doesn't look that good. + +A few months ago I made it collapse with the site wide navigation, but it +was not hidden by default. So a few improvements were made: +- Make the doc navigation hidden by default on mobile, just like site wide navigation +- Make doc navigation have the same look as site wide navigation + +Here's a before: +![](https://safe.kashima.moe/krn0a6qwegdj.png) + +and after: +![](https://safe.kashima.moe/sk11ighz47yb.png) + +Looks a lot better now. + +# Other Changes +If you haven't noticed, I have made other changes to the website. +This includes: +- Borders! Something this simple makes the website look a lot better, especially on mobile. +- More padding and margin everywhere. Home, doc pages, blog post listing. diff --git a/website/content/blog/v2.1.1-release.md b/website/content/blog/v2.1.1-release.md new file mode 100644 index 00000000..cea287a9 --- /dev/null +++ b/website/content/blog/v2.1.1-release.md @@ -0,0 +1,38 @@ +--- +title: "v2.1.1 Release" +date: 2023-04-01T18:15:42-04:00 +draft: false +--- + +> The release with full changelogs and prebuilt binaries can be +seen at the [v2.1.1](https://github.com/Rosettea/Hilbish/releases/tag/v2.1.1) +tag. + +Welcome to a fresh new release of Hilbish! Some people (or none) may be awaiting +the long coming v2.2 release with lots of features, but I *needed* to push +out this little bug fix (wink) release. + +# Bug Fixes +## Validation checks for command input +When running this version, you may have noticed an odd message that sometimes +comes up when running commands. This is from the new TMOLI42SH +(The Meaning of Life is 42 String Hash) input validation scheme. + +## Improved runtime code +Commands now have a chance of taking exactly 2-3s ~~more~~ less time of running due to +improvements in the code for shell runners!!!!! + +## Validate lua code +Hilbish already threw an error when Lua code was not valid in syntax, but there was the +need for an extra validation scheme (called OpTTCLC - Opinion based Turing Test to Check Lua Code) +which results in less time wasted running invalid and TERRIBLE Lua code. + +# Features +There is only 1 new feature in this glorious release. + +## Fix your mistakes for the future +If you run a command that does not exist, Hilbish will say goodbye. + +# Closing +Hope you enjoy this new release! It took a lot of effort to create this new version +while I was busy doing completely nothing. :))) diff --git a/website/content/blog/v2.1.2-release.md b/website/content/blog/v2.1.2-release.md new file mode 100644 index 00000000..48523762 --- /dev/null +++ b/website/content/blog/v2.1.2-release.md @@ -0,0 +1,15 @@ +--- +title: "v2.1.2 Release" +date: 2023-04-10T12:27:41-04:00 +draft: false +--- + +> The release with full changelogs and prebuilt binaries can be +seen at the [v2.1.2](https://github.com/Rosettea/Hilbish/releases/tag/v2.1.2) +tag. + +This release reverts the April Fool's code additions in v2.1.1. It is +functionally equal to v2.1.0. Nice! + +A real release will come possibly in a few days or next week, so stay tuned for +the good and feature-filled release of v2.2! diff --git a/website/content/docs/features/_index.md b/website/content/docs/features/_index.md index 0e14346d..21e2fd54 100644 --- a/website/content/docs/features/_index.md +++ b/website/content/docs/features/_index.md @@ -5,7 +5,7 @@ weight: -40 menu: docs --- -Hilbish has a wide range of features to enhance the user's experience and -is always adding new ones. If there is something missing here or something -you would like to see, please [start a discussion](https://github.com/Rosettea/Hilbish/discussions) +Hilbish has a wide range of features to enhance the user's experience +new ones are always being added. If there is something missing here or +something you would like to see, please [start a discussion](https://github.com/Rosettea/Hilbish/discussions) or comment on any existing ones which match your request. diff --git a/website/content/docs/features/notifications.md b/website/content/docs/features/notifications.md new file mode 100644 index 00000000..c3a9b53f --- /dev/null +++ b/website/content/docs/features/notifications.md @@ -0,0 +1,39 @@ +--- +title: Notification +description: Get notified of shell actions. +layout: doc +menu: + docs: + parent: "Features" +--- + +Hilbish features a simple notification system which can be +used by other plugins and parts of the shell to notify the user +of various actions. This is used via the `hilbish.message` interface. + +A `message` is defined as a table with the following properties: +- `icon`: A unicode/emoji icon for the notification. +- `title`: The title of the message +- `text`: Message text/body +- `channel`: The source of the message. This should be a +unique and easily readable text identifier. +- `summary`: A short summary of the notification and message. +If this is not present and you are using this to display messages, +you should take part of the `text` instead. + +The `hilbish.message` interface provides the following functions: +- `send(message)`: Sends a message and emits the `hilbish.notification` +signal. DO NOT emit the `hilbish.notification` signal directly, or +the message will not be stored by the message handler. +- `read(idx)`: Marks message at `idx` as read. +- `delete(idx)`: Removes message at `idx`. +- `readAll()`: Marks all messages as read. +- `clear()`: Deletes all messages. + +There are a few simple use cases of this notification/messaging system. +It could also be used as some "inter-shell" messaging system (???) but +is intended to display to users. + +An example is notifying users of completed jobs/commands ran in the background. +Any Hilbish-native command (think the upcoming Greenhouse pager) can display +it. diff --git a/website/content/docs/features/runner-mode.md b/website/content/docs/features/runner-mode.md index 87ecc8bf..58b55dd2 100644 --- a/website/content/docs/features/runner-mode.md +++ b/website/content/docs/features/runner-mode.md @@ -13,5 +13,9 @@ is that it runs Lua first and then falls back to shell script. In some cases, someone might want to switch to just shell script to avoid it while interactive but still have a Lua config, or go full Lua to use -Hilbish as a REPL. This also allows users to add alternative languages, -instead of either like Fennel. +Hilbish as a REPL. This also allows users to add alternative languages like +Fennel as the interactive script runner. + +Runner mode can also be used to handle specific kinds of input before +evaluating like normal, which is how [Link.hsh](https://github.com/TorchedSammy/Link.hsh) +handles links. diff --git a/website/content/docs/getting-started.md b/website/content/docs/getting-started.md index f0fe56de..de7607ee 100644 --- a/website/content/docs/getting-started.md +++ b/website/content/docs/getting-started.md @@ -15,9 +15,10 @@ To exit, you can either run the `exit` command or hit Ctrl+D. There are a few ways to make Hilbish your default shell. A simple way is to make it your user/login shell. -{{< warning `It is not recommended to set Hilbish as your login shell. That is expected to be a -POSIX compliant shell, which Hilbish is not. At most, there will just be a -few variables missing in your environment` >}} +{{< warning `It is not recommended to set Hilbish as your login shell. That +is expected to be a POSIX compliant shell, which Hilbish is not. Though if +you still decide to do it, there will just be a few variables missing in +your environment` >}} To do that, simply run `chsh -s /usr/bin/hilbish`. Some distros (namely Fedora) might have `lchsh` instead, which is used like `lchsh `. diff --git a/website/content/install.md b/website/content/install.md index 1ae103f0..392ded04 100644 --- a/website/content/install.md +++ b/website/content/install.md @@ -15,7 +15,12 @@ have breaking changes. For the latest **stable release**, check here: https://github.com/Rosettea/Hilbish/releases/latest For a **development build**: https://nightly.link/Rosettea/Hilbish/workflows/build/master +## Compiling +To read the steps for compiling Hilbish, head over to the [GitHub repository.](https://github.com/Rosettea/Hilbish#build) + ## Package Repositories +Methods of installing Hilbish for your Linux distro. + ### Fedora (COPR) An official COPR is offered to install Hilbish easily on Fedora. Enable the repo: diff --git a/website/static/default.png b/website/static/default.png new file mode 100644 index 00000000..d2cb2c63 Binary files /dev/null and b/website/static/default.png differ diff --git a/website/static/hilbish-flower.png b/website/static/hilbish-flower.png index b4fb0f72..866e57ee 100644 Binary files a/website/static/hilbish-flower.png and b/website/static/hilbish-flower.png differ diff --git a/website/static/hilbish-logo-and-text.png b/website/static/hilbish-logo-and-text.png new file mode 100644 index 00000000..325034c6 Binary files /dev/null and b/website/static/hilbish-logo-and-text.png differ diff --git a/website/static/pillprompt.png b/website/static/pillprompt.png new file mode 100644 index 00000000..c50d6756 Binary files /dev/null and b/website/static/pillprompt.png differ diff --git a/website/static/tab.png b/website/static/tab.png new file mode 100644 index 00000000..409d796d Binary files /dev/null and b/website/static/tab.png differ diff --git a/website/static/terminal.png b/website/static/terminal.png new file mode 100644 index 00000000..b2ff38cf Binary files /dev/null and b/website/static/terminal.png differ diff --git a/website/themes/hsh/layouts/_default/doc.html b/website/themes/hsh/layouts/_default/doc.html index 2748a23c..fa7c6b9d 100644 --- a/website/themes/hsh/layouts/_default/doc.html +++ b/website/themes/hsh/layouts/_default/doc.html @@ -1,31 +1,43 @@ {{ define "main" }} -
      -
      -
      - + +
      -
      -
      + + +

      {{ .Title }}

      @@ -47,7 +59,7 @@

      {{ .Title }}

      Want to help improve this page? Create an issue.

      -
      + {{ end }} diff --git a/website/themes/hsh/layouts/_default/list.html b/website/themes/hsh/layouts/_default/list.html index bbb9e8af..ac6ea4ba 100644 --- a/website/themes/hsh/layouts/_default/list.html +++ b/website/themes/hsh/layouts/_default/list.html @@ -1,5 +1,5 @@ {{ define "main" }} -
      +
      {{ range where .Site.RegularPages "Section" "in" "blog" }}
      diff --git a/website/themes/hsh/layouts/index.html b/website/themes/hsh/layouts/index.html index a9f64d2e..ef646ab5 100644 --- a/website/themes/hsh/layouts/index.html +++ b/website/themes/hsh/layouts/index.html @@ -1,6 +1,8 @@ {{ define "main" }} -
      - {{.Content}} -
      +
      +
      + {{.Content}} +
      +
      {{ end }} diff --git a/website/themes/hsh/layouts/partials/footer.html b/website/themes/hsh/layouts/partials/footer.html index 15aa1936..210538af 100644 --- a/website/themes/hsh/layouts/partials/footer.html +++ b/website/themes/hsh/layouts/partials/footer.html @@ -1,24 +1,11 @@ -