Skip to content

Instantly share code, notes, and snippets.

@itsfrank
Last active October 30, 2024 19:01
Show Gist options
  • Save itsfrank/bad1f494798df5370e5f8236757bbf2a to your computer and use it in GitHub Desktop.
Save itsfrank/bad1f494798df5370e5f8236757bbf2a to your computer and use it in GitHub Desktop.
localhost tcp based notification system for neovim
-- some place where you define user commands
---@type uv_tcp_t?
local notify_server = nil
vim.api.nvim_create_user_command("StartNotifyServer", function()
if notify_server and not notify_server:is_closing() then
return
end
local try_notify_server, err = require("notify_server").start_server()
if err then
error(err)
end
notify_server = try_notify_server
end, {})
vim.api.nvim_create_user_command("StopNotifyServer", function()
if notify_server and not notify_server:is_closing() then
notify_server:close()
end
end, {})
#!/bin/bash
# put this on your path
# check var
port=${NOTIFY_PORT:-34567}
case $# in
0)
# do nothing
;;
1)
echo "{\"evt\": \"$1\"}" | nc localhost "$port" >/dev/null 2>&1
;;
2)
echo "{\"evt\": \"$1\", \"msg\": \"$2\"}" | nc localhost "$port" >/dev/null 2>&1
;;
esac
# the nc call might fail if ther eis no server listening, but this script doesnt consider that a failure
# I want to use this script in command chains, it should not caus echains to fail
exit 0
---@param host string
---@param port number
---@param on_data? fun(data: string, sock: uv_tcp_t)
---@param on_err? fun(data: string, sock: uv_tcp_t)
---@return uv_tcp_t?, string?
local function create_tcp_server(host, port, on_data, on_err)
local server = vim.uv.new_tcp()
local _, err = server:bind(host, port)
if err ~= nil then
return nil, "start server failed for '" .. host .. ":" .. tostring(port) .. "', err: " .. err
end
_, err = server:listen(128, function(err1)
assert(not err1, err1)
local sock = vim.uv.new_tcp()
server:accept(sock)
sock:read_start(function(err2, chunk)
if err2 then
error(err2)
end
if err2 and on_err ~= nil then
on_err(err2, sock)
elseif chunk and on_data ~= nil then
on_data(chunk, sock)
end
if not sock:is_closing() then
sock:close()
end
end)
end)
if err ~= nil then
return nil, "start server failed for '" .. host .. ":" .. tostring(port) .. "', err: " .. err
end
return server
end
--- start a tcp server that will notify based on tcp messages to localhost @ port env.NOTIFY_PORT
--- messages are json and take form {"evt": "info|warn|err", "msg": "<your message>"}
--- they are fed to vim.notify with the matching level
---@return uv_tcp_t?, string?
local function notify_start_server()
local notify_port = tonumber(vim.env.NOTIFY_PORT) or 34567
local notify_server, err = create_tcp_server("0.0.0.0", notify_port, function(data, sock)
local ok, json = pcall(function()
return vim.json.decode(data)
end)
local lvl = vim.log.levels[string.upper(json.evt or "")]
local msg = json.msg
if ok and lvl ~= nil and msg ~= nil then
vim.schedule(function()
vim.notify(msg, lvl)
end)
end
sock:write("ack")
end)
if err ~= nil then
return nil, err
end
assert(notify_server ~= nil)
vim.api.nvim_create_autocmd("VimLeavePre", {
callback = function()
if not notify_server:is_closing() then
notify_server:close()
end
end,
})
return notify_server
end
return {
start_server = notify_start_server,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment