Compare commits

..

1 commit

Author SHA1 Message Date
695ff4aa46
feat: add healthcheck
Problem: users had no way to diagnose why completions were missing or
incomplete.

Solution: add a :checkhealth module that verifies blink.cmp is
installed, tmux is on PATH and responds to list-commands, and man is
available for command descriptions.
2026-02-20 20:18:49 -05:00
3 changed files with 41 additions and 87 deletions

View file

@ -2,12 +2,6 @@
"runtime.version": "Lua 5.1",
"runtime.path": ["lua/?.lua", "lua/?/init.lua"],
"diagnostics.globals": ["vim", "jit"],
"diagnostics.disable": [
"undefined-doc-name",
"undefined-doc-class",
"undefined-field",
"need-check-nil"
],
"workspace.library": ["$VIMRUNTIME/lua", "${3rd}/luv/library"],
"workspace.checkThirdParty": false,
"completion.callSnippet": "Replace"

View file

@ -3,9 +3,6 @@ local M = {}
---@type blink.cmp.CompletionItem[]?
local cache = nil
local loading = false
---@type {ctx: blink.cmp.Context, callback: fun(response: blink.cmp.CompletionResponse)}[]
local pending = {}
function M.new()
return setmetatable({}, { __index = M })
@ -16,17 +13,18 @@ function M.enabled()
return vim.bo.filetype == 'tmux'
end
---@param man_stdout string
---@param names_stdout string
---@return table<string, string>
local function parse_descriptions(man_stdout, names_stdout)
local function parse_descriptions()
local result = vim.system({ 'bash', '-c', 'MANWIDTH=80 man -P cat tmux 2>/dev/null' }):wait()
local stdout = result.stdout or ''
local lines = {}
for line in (man_stdout .. '\n'):gmatch('(.-)\n') do
for line in (stdout .. '\n'):gmatch('(.-)\n') do
lines[#lines + 1] = line
end
local cmd_result = vim.system({ 'tmux', 'list-commands', '-F', '#{command_list_name}' }):wait()
local cmds = {}
for name in names_stdout:gmatch('[^\n]+') do
for name in (cmd_result.stdout or ''):gmatch('[^\n]+') do
cmds[name] = true
end
@ -47,7 +45,7 @@ local function parse_descriptions(man_stdout, names_stdout)
local j = def.line + 1
while j <= block_end do
local l = lines[j]
if l:match('^%s+%(alias:') or vim.trim(l) == '' then
if l:match('^%s+%(alias:') then
j = j + 1
elseif l:match('^ ') then
local stripped = vim.trim(l)
@ -56,6 +54,8 @@ local function parse_descriptions(man_stdout, names_stdout)
else
break
end
elseif vim.trim(l) == '' then
j = j + 1
else
break
end
@ -84,7 +84,7 @@ local function parse_descriptions(man_stdout, names_stdout)
end
end
local desc = table.concat(parts, '\n\n')
desc = desc:gsub(string.char(0xe2, 0x80, 0x90) .. ' ', '')
desc = desc:gsub('\xe2\x80\x90 ', '')
desc = desc:gsub(' +', ' ')
if desc ~= '' then
descs[def.cmd] = desc
@ -128,66 +128,27 @@ end
---@param ctx blink.cmp.Context
---@param callback fun(response: blink.cmp.CompletionResponse)
local function respond(ctx, callback)
---@return fun()
function M:get_completions(ctx, callback)
if not cache then
local ok, descs = pcall(parse_descriptions)
if not ok then
descs = {}
end
local result = vim.system({ 'tmux', 'list-commands' }):wait()
cache = parse(result.stdout or '', descs)
end
local before = ctx.line:sub(1, ctx.cursor[2])
if before:match('^%s*[a-z-]*$') then
callback({
is_incomplete_forward = false,
is_incomplete_backward = false,
items = cache,
items = vim.deepcopy(cache),
})
else
callback({ items = {} })
end
end
---@param ctx blink.cmp.Context
---@param callback fun(response: blink.cmp.CompletionResponse)
---@return fun()
function M:get_completions(ctx, callback)
if cache then
respond(ctx, callback)
return function() end
end
pending[#pending + 1] = { ctx = ctx, callback = callback }
if not loading then
loading = true
local man_out, names_out, cmds_out
local remaining = 3
local function on_all_done()
remaining = remaining - 1
if remaining > 0 then
return
end
vim.schedule(function()
local ok, descs = pcall(parse_descriptions, man_out, names_out)
if not ok then
descs = {}
end
cache = parse(cmds_out, descs)
loading = false
for _, p in ipairs(pending) do
respond(p.ctx, p.callback)
end
pending = {}
end)
end
vim.system({ 'bash', '-c', 'MANWIDTH=80 man -P cat tmux 2>/dev/null' }, {}, function(result)
man_out = result.stdout or ''
on_all_done()
end)
vim.system({ 'tmux', 'list-commands', '-F', '#{command_list_name}' }, {}, function(result)
names_out = result.stdout or ''
on_all_done()
end)
vim.system({ 'tmux', 'list-commands' }, {}, function(result)
cmds_out = result.stdout or ''
on_all_done()
end)
end
return function() end
end

View file

@ -26,38 +26,37 @@ local MAN_PAGE = table.concat({
}, '\n')
local function mock_system()
local original_system = vim.system
local original_schedule = vim.schedule
local original = vim.system
---@diagnostic disable-next-line: duplicate-set-field
vim.system = function(cmd, _, on_exit)
local result
vim.system = function(cmd)
if cmd[1] == 'bash' then
result = { stdout = MAN_PAGE, code = 0 }
return {
wait = function()
return { stdout = MAN_PAGE, code = 0 }
end,
}
elseif cmd[1] == 'tmux' and cmd[2] == 'list-commands' then
if cmd[3] == '-F' then
result = { stdout = TMUX_NAMES, code = 0 }
else
result = { stdout = TMUX_COMMANDS, code = 0 }
return {
wait = function()
return { stdout = TMUX_NAMES, code = 0 }
end,
}
end
else
result = { stdout = '', code = 1 }
end
if on_exit then
on_exit(result)
return {}
return {
wait = function()
return { stdout = TMUX_COMMANDS, code = 0 }
end,
}
end
return {
wait = function()
return result
return { stdout = '', code = 1 }
end,
}
end
vim.schedule = function(fn)
fn()
end
return function()
vim.system = original_system
vim.schedule = original_schedule
vim.system = original
end
end