Compare commits

..

2 commits

Author SHA1 Message Date
Barrett Ruth
c0c59d1e57
perf: async parallel cache initialization and remove deepcopy (#6)
Some checks are pending
quality / changes (push) Waiting to run
quality / Lua Format Check (push) Blocked by required conditions
quality / Lua Lint Check (push) Blocked by required conditions
quality / Lua Type Check (push) Blocked by required conditions
quality / Markdown Format Check (push) Blocked by required conditions
test / Test (Neovim nightly) (push) Waiting to run
test / Test (Neovim stable) (push) Waiting to run
luarocks / quality (push) Waiting to run
luarocks / publish (push) Blocked by required conditions
Problem: first completion request blocked the UI with three sequential
synchronous vim.system():wait() calls (man page, command names,
command list), and every subsequent completion unnecessarily
deep-copied the entire cache.

Solution: run all three system calls concurrently via vim.system
callbacks, merging results when all complete. Queue pending completion
requests during loading. Return cached items directly instead of
deep-copying.
2026-02-20 20:39:32 -05:00
Barrett Ruth
7003996643
feat: add healthcheck (#7)
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:36:41 -05:00
3 changed files with 87 additions and 41 deletions

View file

@ -2,6 +2,12 @@
"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,6 +3,9 @@ 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 })
@ -13,18 +16,17 @@ 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()
local result = vim.system({ 'bash', '-c', 'MANWIDTH=80 man -P cat tmux 2>/dev/null' }):wait()
local stdout = result.stdout or ''
local function parse_descriptions(man_stdout, names_stdout)
local lines = {}
for line in (stdout .. '\n'):gmatch('(.-)\n') do
for line in (man_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 (cmd_result.stdout or ''):gmatch('[^\n]+') do
for name in names_stdout:gmatch('[^\n]+') do
cmds[name] = true
end
@ -45,7 +47,7 @@ local function parse_descriptions()
local j = def.line + 1
while j <= block_end do
local l = lines[j]
if l:match('^%s+%(alias:') then
if l:match('^%s+%(alias:') or vim.trim(l) == '' then
j = j + 1
elseif l:match('^ ') then
local stripped = vim.trim(l)
@ -54,8 +56,6 @@ local function parse_descriptions()
else
break
end
elseif vim.trim(l) == '' then
j = j + 1
else
break
end
@ -84,7 +84,7 @@ local function parse_descriptions()
end
end
local desc = table.concat(parts, '\n\n')
desc = desc:gsub('\xe2\x80\x90 ', '')
desc = desc:gsub(string.char(0xe2, 0x80, 0x90) .. ' ', '')
desc = desc:gsub(' +', ' ')
if desc ~= '' then
descs[def.cmd] = desc
@ -128,27 +128,66 @@ end
---@param ctx blink.cmp.Context
---@param callback fun(response: blink.cmp.CompletionResponse)
---@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 function respond(ctx, callback)
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 = vim.deepcopy(cache),
items = 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,37 +26,38 @@ local MAN_PAGE = table.concat({
}, '\n')
local function mock_system()
local original = vim.system
local original_system = vim.system
local original_schedule = vim.schedule
---@diagnostic disable-next-line: duplicate-set-field
vim.system = function(cmd)
vim.system = function(cmd, _, on_exit)
local result
if cmd[1] == 'bash' then
return {
wait = function()
return { stdout = MAN_PAGE, code = 0 }
end,
}
result = { stdout = MAN_PAGE, code = 0 }
elseif cmd[1] == 'tmux' and cmd[2] == 'list-commands' then
if cmd[3] == '-F' then
return {
wait = function()
return { stdout = TMUX_NAMES, code = 0 }
end,
}
result = { stdout = TMUX_NAMES, code = 0 }
else
result = { stdout = TMUX_COMMANDS, code = 0 }
end
return {
wait = function()
return { stdout = TMUX_COMMANDS, code = 0 }
end,
}
else
result = { stdout = '', code = 1 }
end
if on_exit then
on_exit(result)
return {}
end
return {
wait = function()
return { stdout = '', code = 1 }
return result
end,
}
end
vim.schedule = function(fn)
fn()
end
return function()
vim.system = original
vim.system = original_system
vim.schedule = original_schedule
end
end