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 4742feb..36b82da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,12 @@ - `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 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/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 10af1d6..56c34ba 100644 --- a/nature/opts/init.lua +++ b/nature/opts/init.lua @@ -26,7 +26,8 @@ local defaultOpts = { The nice lil shell for {blue}Lua{reset} fanatics! ]], hilbish.user), motd = true, - fuzzy = false + 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/website/content/docs/features/notifications.md b/website/content/docs/features/notifications.md new file mode 100644 index 0000000..c3a9b53 --- /dev/null +++ b/website/content/docs/features/notifications.md @@ -0,0 +1,39 @@ +--- +title: Notification +description: Get notified of shell actions. +layout: doc +menu: + docs: + parent: "Features" +--- + +Hilbish features a simple notification system which can be +used by other plugins and parts of the shell to notify the user +of various actions. This is used via the `hilbish.message` interface. + +A `message` is defined as a table with the following properties: +- `icon`: A unicode/emoji icon for the notification. +- `title`: The title of the message +- `text`: Message text/body +- `channel`: The source of the message. This should be a +unique and easily readable text identifier. +- `summary`: A short summary of the notification and message. +If this is not present and you are using this to display messages, +you should take part of the `text` instead. + +The `hilbish.message` interface provides the following functions: +- `send(message)`: Sends a message and emits the `hilbish.notification` +signal. DO NOT emit the `hilbish.notification` signal directly, or +the message will not be stored by the message handler. +- `read(idx)`: Marks message at `idx` as read. +- `delete(idx)`: Removes message at `idx`. +- `readAll()`: Marks all messages as read. +- `clear()`: Deletes all messages. + +There are a few simple use cases of this notification/messaging system. +It could also be used as some "inter-shell" messaging system (???) but +is intended to display to users. + +An example is notifying users of completed jobs/commands ran in the background. +Any Hilbish-native command (think the upcoming Greenhouse pager) can display +it.