diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 547673b..6ee4349 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 "::set-output name=BRANCH_NAME::${GITHUB_REF##*/}" + + - name: Fix base URL + if: steps.branch.outputs.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea' + run: sed -i "s%baseURL = 'https://rosettea.github.io/Hilbish/'%baseURL = 'https://rosettea.github.io/Hilbish/versions/${{ steps.branch.outputs.BRANCH_NAME }}'%" website/config.toml + - name: Build run: 'cd website && hugo --minify' - name: Deploy + if: steps.branch.outputs.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: steps.branch.outputs.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/${{ steps.branch.outputs.BRANCH_NAME }} + keep_files: true diff --git a/.hilbishrc.lua b/.hilbishrc.lua index 5d6382b..249f97e 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 ec8db20..36b82da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,32 @@ # 🎀 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 +- `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. + +[#219]: https://github.com/Rosettea/Hilbish/issues/219 ### Fixed - 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 3a3dbbb..469630b 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/api.go b/api.go index 0d6c208..142e410 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)) @@ -642,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 b4fb0f7..866e57e 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 0000000..325034c 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 16412c4..0000000 Binary files a/assets/hilbish-text.png and /dev/null differ diff --git a/docs/api/hilbish/_index.md b/docs/api/hilbish/_index.md index 81ca993..4cf0180 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,16 @@ 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 input from the sink. + #### write(str) Writes data to a sink. diff --git a/docs/hooks/command.md b/docs/hooks/command.md index cd1ae3c..2d29f4b 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 3d6d2ea..7118901 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/emmyLuaDocs/hilbish.lua b/emmyLuaDocs/hilbish.lua index 8b20583..c26c7ec 100644 --- a/emmyLuaDocs/hilbish.lua +++ b/emmyLuaDocs/hilbish.lua @@ -79,6 +79,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 +188,18 @@ 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 input from the sink. +--- @returns string +function hilbish:read() end + --- Writes data to a sink. function hilbish:write(str) end diff --git a/go.mod b/go.mod index 1573917..1549f76 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/blackfireio/osinfo v1.0.3 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 @@ -16,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 53e83d6..7c36fa0 100644 --- a/go.sum +++ b/go.sum @@ -26,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= @@ -39,6 +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= +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= diff --git a/lua.go b/lua.go index 0a7c115..e46d27b 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 4fa321c..300f333 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) } diff --git a/nature/commands/cdr.lua b/nature/commands/cdr.lua index 288ae22..e6aba36 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/hummingbird.lua b/nature/hummingbird.lua new file mode 100644 index 0000000..581e92c --- /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 d1f919c..9e78135 100644 --- a/nature/init.lua +++ b/nature/init.lua @@ -11,6 +11,7 @@ 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/opts/init.lua b/nature/opts/init.lua index ae95ee1..56c34ba 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 0000000..a8841a1 --- /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/comp-group.go b/readline/comp-group.go index 0c53ed1..b2ee4b8 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/instance.go b/readline/instance.go index 039f040..a477246 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 e6522e6..d00decc 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 aa38259..830dad3 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 491ef98..5610b10 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 96b8451..17ea4df 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 9a98856..2ecc19d 100644 --- a/sink.go +++ b/sink.go @@ -1,8 +1,10 @@ package main import ( + "bufio" "fmt" "io" + "os" "hilbish/util" @@ -15,9 +17,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 +29,35 @@ func setupSinkType(rtm *rt.Runtime) { sinkMethods := rt.NewTable() sinkFuncs := map[string]util.LuaExport{ + "flush": {luaSinkFlush, 1, false}, + "read": {luaSinkRead, 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 +65,25 @@ func setupSinkType(rtm *rt.Runtime) { l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta)) } +// #member +// read() -> string +// --- @returns string +// Reads 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 +102,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 +127,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/util/util.go b/util/util.go index 45e33dc..0fcd4b0 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 7cbc768..583db71 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 2b1087b..4421798 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.

-