diff --git a/CHANGELOG.md b/CHANGELOG.md index 36b82da..d8f5cf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,22 @@ - `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. -[#219]: https://github.com/Rosettea/Hilbish/issues/219 ### 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 diff --git a/Taskfile.yaml b/Taskfile.yaml index 5c5caae..264e7d5 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 bfacc43..8b815b3 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 2f0a548..b7afb3d 100644 --- a/api.go +++ b/api.go @@ -166,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 } diff --git a/cmd/docgen/docgen.go b/cmd/docgen/docgen.go index 487af9f..5b452d7 100644 --- a/cmd/docgen/docgen.go +++ b/cmd/docgen/docgen.go @@ -439,7 +439,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(strings.Replace(modu.Description, "<", `\<`, -1), "{{\\<", "{{<", -1), func(typ string) string { typName := typ[1:] typLookup := typeTable[strings.ToLower(typName)] diff --git a/complete.go b/complete.go index 51b426f..0c70e07 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 36e6413..0cb17d5 100644 --- a/docs/api/hilbish/_index.md +++ b/docs/api/hilbish/_index.md @@ -44,7 +44,7 @@ interfaces and functions which directly relate to shell functionality. |interactive|Is Hilbish in an interactive shell?| |login|Is Hilbish the login shell?| |vimMode|Current Vim input mode of Hilbish (will be nil if not in Vim input mode)| -|exitCode|xit code of the last executed command| +|exitCode|Exit code of the last executed command|

@@ -331,7 +331,10 @@ A call with no argument will toggle the value. Flush writes all buffered input to the sink. #### read() -> string -Reads input from the sink. +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 282737d..3ded67a 100644 --- a/docs/api/hilbish/hilbish.editor.md +++ b/docs/api/hilbish/hilbish.editor.md @@ -17,6 +17,7 @@ directly interact with the line editor in use. |getLine() -> string|Returns the current input line.| |getVimRegister(register) -> string|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| |setVimRegister(register, text)|Sets the vim register at `register` to hold the passed text.|
@@ -58,6 +59,20 @@ Inserts text into the line. This function has no parameters.
+
+

+hilbish.editor.getChar() -> string + + + +

+ +Reads a keystroke from the user. This is in a format +of something like Ctrl-L.. +#### Parameters +This function has no parameters. +
+

hilbish.editor.setVimRegister(register, text) diff --git a/docs/api/hilbish/hilbish.module.md b/docs/api/hilbish/hilbish.module.md new file mode 100644 index 0000000..9a0af12 --- /dev/null +++ b/docs/api/hilbish/hilbish.module.md @@ -0,0 +1,69 @@ +--- +title: Module 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!" + +## Functions +||| +|----|----| +|load(path)|Loads a module at the designated `path`.| + +## Static module 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`| + +
+

+hilbish.module.load(path) + + + +

+ +Loads a module at the designated `path`. +It will throw if any error occurs. +#### Parameters +This function has no parameters. +
+ diff --git a/editor.go b/editor.go index 3038f07..d720a41 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 c26c7ec..2ee93ed 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 @@ -196,10 +200,14 @@ function hilbish:autoFlush(auto) end --- Flush writes all buffered input to the sink. function hilbish:flush() end ---- Reads input from the sink. +--- 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 @@ -212,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 792d576..7bf5efd 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/arnodel/golua v0.0.0-20220221163911-dfcf252b6f86 github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504 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 @@ -19,6 +18,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 d889ed3..f82ef01 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= @@ -20,8 +12,6 @@ github.com/atsushinee/go-markdown-generator v0.0.0-20191121114853-83f9e1f68504 h 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/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= @@ -38,6 +28,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= @@ -53,7 +45,6 @@ github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4Rx 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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 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= @@ -62,18 +53,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/main.go b/main.go index 300f333..90caa47 100644 --- a/main.go +++ b/main.go @@ -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 0000000..2ab55e8 --- /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/doc.lua b/nature/commands/doc.lua index d37e677..ee1e37c 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,47 +23,15 @@ to Hilbish. Usage: doc
[subdoc] Available sections: ]] .. table.concat(modules, ', ') - if #args > 0 then - local mod = args[1] - - local 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') - if not f then - f = io.open(moddocPath .. subdocName:match '%w+' .. '/' .. subdocName .. '.md', 'rb') - end - if not f then - moddocPath = moddocPath .. subdocName .. '/' - subdocName = args[3] or '_index' - f = io.open(moddocPath .. subdocName .. '.md', 'rb') - end - if not f then - sinks.out:writeln('No documentation found for ' .. mod .. '.') - 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 f + local function handleYamlInfo(d) local vals = {} + local docs = d + + local valsStr = docs:match '%-%-%-\n([^%-%-%-]+)\n' + print(valsStr) if valsStr then - local _, endpos = funcdocs:find('---\n' .. valsStr .. '\n---\n\n', 1, true) - funcdocs = funcdocs:sub(endpos + 1, #funcdocs) + docs = docs:sub(valsStr:len() + 10, #docs) -- parse vals local lines = string.split(valsStr, '\n') @@ -78,23 +44,113 @@ Available sections: ]] .. table.concat(modules, ', ') end end end - if mod == 'api' then - funcdocs = string.format(apidocHeader, vals.title, vals.description or 'no description.') .. funcdocs - end - doc = funcdocs:sub(1, #funcdocs - 1) - f:close() + + --docs = docs:sub(1, #docs - 1) + return docs, vals 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}' + if #args > 0 then + local mod = args[1] + + f = io.open(moddocPath .. mod .. '.md', 'rb') + local funcdocs = nil + local subdocName = args[2] + if not f then + moddocPath = moddocPath .. mod .. '/' + if not subdocName then + subdocName = '_index' + end + f = io.open(moddocPath .. subdocName .. '.md', 'rb') + local oldmoddocPath = moddocPath + if not f then + moddocPath = moddocPath .. subdocName:match '%w+' .. '/' + f = io.open(moddocPath .. subdocName .. '.md', 'rb') + end + if not f then + moddocPath = oldmoddocPath .. subdocName .. '/' + subdocName = args[3] or '_index' + f = io.open(moddocPath .. subdocName .. '.md', 'rb') + end + if not f then + sinks.out:writeln('No documentation found for ' .. mod .. '.') + return 1 + end end - end):gsub('\n#+.-\n', function(t) - local signature = t:gsub('<.->(.-)', '{underline}%1'):gsub('\\', '<') - return '{bold}{yellow}' .. signature .. '{reset}' - end))) + + end + + 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) + + 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 + + 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 + end + local backtickOccurence = 0 + 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 + + 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 0000000..9c155b0 --- /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 0000000..d5877e8 --- /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 0000000..51d1440 --- /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/init.lua b/nature/init.lua index 9e78135..a0579d7 100644 --- a/nature/init.lua +++ b/nature/init.lua @@ -6,6 +6,18 @@ 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' diff --git a/nature/object.lua b/nature/object.lua new file mode 100644 index 0000000..053be4a --- /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/readline/codes.go b/readline/codes.go index 492bc72..dd8495d 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/history.go b/readline/history.go index f772813..e226b4d 100644 --- a/readline/history.go +++ b/readline/history.go @@ -156,8 +156,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/sink.go b/sink.go index 2ecc19d..3aa5507 100644 --- a/sink.go +++ b/sink.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "strings" "hilbish/util" @@ -31,6 +32,7 @@ func setupSinkType(rtm *rt.Runtime) { 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}, @@ -65,10 +67,42 @@ 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 input from the sink. +// 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 diff --git a/testplugin/testplugin.go b/testplugin/testplugin.go new file mode 100644 index 0000000..2d8a41b --- /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 0000000..3c83992 Binary files /dev/null and b/testplugin/testplugin.so differ diff --git a/website/content/docs/features/runner-mode.md b/website/content/docs/features/runner-mode.md index 8774de9..58b55dd 100644 --- a/website/content/docs/features/runner-mode.md +++ b/website/content/docs/features/runner-mode.md @@ -13,8 +13,8 @@ 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)