Compare commits

...

21 Commits

Author SHA1 Message Date
sammyette e288826be4
chore: merge from master 2023-10-25 00:25:33 -04:00
sammyette ae356f6ffa
docs: add details to changelog 2023-10-25 00:24:17 -04:00
sammyette d8a7a4332d
fix(commands/greenhouse): remove extra space in title, set pipe page title to stdin 2023-10-25 00:00:59 -04:00
sammyette 8fabdf4502
fix: correct no files check to compensate for pipes 2023-10-24 23:59:53 -04:00
sammyette 003100d3c4
fix: dont concat subdocs list if on main page 2023-10-24 23:52:53 -04:00
sammyette 7288f85e9a
feat(commands/doc): add subdocs as additional pages 2023-10-24 23:51:42 -04:00
sammyette a5b6fc8eda
fix: return cmdstr in alias resolve instead of nothing 2023-09-30 23:25:15 -04:00
sammyette 4421869b85
fix: check length of args in aliases resolve
this shouldnt be a problem.. but ... what
2023-09-30 23:21:00 -04:00
sammyette 6d07d8db53
chore: merge from upstream 2023-09-30 19:53:38 -04:00
sammyette 483e5f6dbe
fix(hilbish.completions): return prefix on the `call` function (from #263) 2023-09-30 19:52:33 -04:00
sammyette 83492e4e69
docs: fix odd sentence in runner-mode docs 2023-09-27 20:31:27 -04:00
sammyette 4fcd310048
chore: merge from upstream 2023-09-09 13:53:22 -04:00
sammyette db8e87e5dd
fix: compare lower case strings in contains function
this fixes file completion on windows.
2023-09-09 13:53:09 -04:00
sammyette ba04fb2af7
feat: allow numbered substitutions in aliases (#258) 2023-09-09 13:51:58 -04:00
sammyette caff604d95
ci: stop using deprecated method of setting branch name 2023-07-10 22:42:04 -04:00
sammyette 2b9059f726
ci: add missing quote 2023-07-10 22:38:22 -04:00
sammyette 00c967d7c1
ci: fix branch name 2023-07-10 22:38:13 -04:00
TorchedSammy ab26b82a9f docs: [ci] generate new docs 2023-07-11 01:57:00 +00:00
sammyette f540fc2c04
feat: show go version hilbish was compiled with 2023-07-10 21:56:35 -04:00
sammyette c6c81ddece
feat: add notification/message interface (#239)
details in #219
2023-07-10 19:03:30 -04:00
sammyette 3eae0f07be
feat: add fuzzy searching for completion and history search (#247)
* feat: add fuzzy searching for completion and history search

* feat: add fuzzy opt for fuzzy history searching

* chore: add fuzzy opt to changelog
2023-07-10 00:06:29 -04:00
29 changed files with 400 additions and 87 deletions

View File

@ -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)

View File

@ -6,9 +6,25 @@
- `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 <file>`
- Using this also brings enhancements to the `doc` command like easy
navigation of neighboring doc files.
### Fixed
- Return the prefix when calling `hilbish.completions.call`
[#219]: https://github.com/Rosettea/Hilbish/issues/219
### Fixed
- Replaced `sed` in-place editing with `grep` and `mv` for compatibility with BSD utils

View File

@ -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

2
api.go
View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

1
go.mod
View File

@ -8,6 +8,7 @@ require (
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

2
go.sum
View File

@ -49,6 +49,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 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=

2
lua.go
View File

@ -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.")
}

View File

@ -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:]...)
}

View File

@ -23,12 +23,36 @@ to Hilbish.
Usage: doc <section> [subdoc]
Available sections: ]] .. table.concat(modules, ', ')
local vals = {}
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
@ -52,35 +76,14 @@ 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, ', ') .. '\n\n'
end
local valsStr = funcdocs:match '%-%-%-\n([^%-%-%-]+)\n'
if valsStr then
local _, endpos = funcdocs:find('---\n' .. valsStr .. '\n---\n\n', 1, true)
funcdocs = funcdocs:sub(endpos + 1, #funcdocs)
-- 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
doc = funcdocs:sub(1, #funcdocs - 1)
f:close()
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()
@ -102,26 +105,52 @@ Available sections: ]] .. table.concat(modules, ', ')
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', vals.title)))
self.sink:write(lunacolors.format(string.format('{grayBg} ↳ {white}{italic}%s {reset}', vals.description or 'No description.')))
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 page = Page(nil, lunacolors.format(doc: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)))
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)

View File

@ -43,7 +43,7 @@ commander.register('greenhouse', function(args, sinks)
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 '')))
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
@ -98,27 +98,27 @@ commander.register('greenhouse', function(args, sinks)
end)
if sinks['in'].pipe then
local page = Page('', sinks['in']:readAll())
local page = Page('stdin', sinks['in']:readAll())
gh:addPage(page)
end
if #args ~= 0 then
for _, name in ipairs(args) do
local f <close> = 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)
for _, name in ipairs(args) do
local f <close> = io.open(name, 'r')
if not f then
sinks.err:writeln(string.format('could not open file %s', name))
end
ansikit.hideCursor()
gh:initUi()
else
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 <file>...
example: greenhouse hello.md]]
return 1
end
ansikit.hideCursor()
gh:initUi()
end)

View File

@ -74,6 +74,10 @@ function Greenhouse:draw()
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'))

View File

@ -5,6 +5,9 @@ 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)
@ -15,4 +18,15 @@ 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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)...)
}
}

View File

@ -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()

View File

@ -14,6 +14,7 @@ var (
// effects
BOLD = "\033[1m"
DIM = "\033[2m"
UNDERLINE = "\033[4m"
RESET = "\033[0m"
// colors
RED = "\033[31m"

21
rl.go
View File

@ -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 {

View File

@ -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("<string>", []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.

View File

@ -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.

View File

@ -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)