From c0c59d1e5771a8de25b547dc18eb274cbce2438c Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:39:32 -0500 Subject: [PATCH] perf: async parallel cache initialization and remove deepcopy (#6) 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. --- lua/blink-cmp-tmux.lua | 77 ++++++++++++++++++++++++++++++++---------- spec/tmux_spec.lua | 39 ++++++++++----------- 2 files changed, 79 insertions(+), 37 deletions(-) diff --git a/lua/blink-cmp-tmux.lua b/lua/blink-cmp-tmux.lua index dc8dce7..1653c06 100644 --- a/lua/blink-cmp-tmux.lua +++ b/lua/blink-cmp-tmux.lua @@ -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 -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 @@ -126,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 diff --git a/spec/tmux_spec.lua b/spec/tmux_spec.lua index 3f47a97..da0c32e 100644 --- a/spec/tmux_spec.lua +++ b/spec/tmux_spec.lua @@ -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