diff --git a/lua/blink/cmp/lib/async.lua b/lua/blink/cmp/lib/async.lua index d4155e2e..9bc0a88e 100644 --- a/lua/blink/cmp/lib/async.lua +++ b/lua/blink/cmp/lib/async.lua @@ -151,7 +151,9 @@ end --- utils function task.await_all(tasks) - if #tasks == 0 then return task.empty() end + if #tasks == 0 then + return task.new(function(resolve) resolve({}) end) + end local all_task all_task = task.new(function(resolve, reject) diff --git a/lua/blink/cmp/sources/cmdline/help.lua b/lua/blink/cmp/sources/cmdline/help.lua new file mode 100644 index 00000000..2388475e --- /dev/null +++ b/lua/blink/cmp/sources/cmdline/help.lua @@ -0,0 +1,47 @@ +local async = require('blink.cmp.lib.async') + +local help = {} + +--- Processes a help file and returns a list of tags asynchronously +--- @param file string +--- @return blink.cmp.Task +--- TODO: rewrite using async lib, shared as a library in lib/fs.lua +local function read_tags_from_file(file) + return async.task.new(function(resolve) + vim.uv.fs_open(file, 'r', 438, function(err, fd) + if err or fd == nil then return resolve({}) end + + -- Read file content + vim.uv.fs_fstat(fd, function(stat_err, stat) + if stat_err or stat == nil then + vim.uv.fs_close(fd) + return resolve({}) + end + + vim.uv.fs_read(fd, stat.size, 0, function(read_err, data) + vim.uv.fs_close(fd) + + if read_err or data == nil then return resolve({}) end + + -- Process the file content + local tags = {} + for line in data:gmatch('[^\r\n]+') do + local tag = line:match('^([^\t]+)') + if tag then table.insert(tags, tag) end + end + + resolve(tags) + end) + end) + end) + end) +end + +function help.get_completions() + local help_files = vim.api.nvim_get_runtime_file('doc/tags', true) + return async.task + .await_all(vim.tbl_map(read_tags_from_file, help_files)) + :map(function(results) return require('blink.cmp.lib.utils').flatten(results) end) +end + +return help diff --git a/lua/blink/cmp/sources/cmdline/init.lua b/lua/blink/cmp/sources/cmdline/init.lua index 35aa4efc..1d4c5f0e 100644 --- a/lua/blink/cmp/sources/cmdline/init.lua +++ b/lua/blink/cmp/sources/cmdline/init.lua @@ -2,6 +2,8 @@ -- https://github.com/hrsh7th/cmp-cmdline -- License: MIT +local async = require('blink.cmp.lib.async') + --- @class blink.cmp.Source local cmdline = {} @@ -31,39 +33,56 @@ function cmdline:get_completions(context, callback) ) local current_arg_prefix = current_arg:sub(1, keyword.start_col - #text_before_cursor - 1) - local query = (text_before_cursor .. current_arg_prefix):gsub([[\\]], [[\\\\]]) - local completions = vim.fn.getcompletion(query, 'cmdline') + local task = async.task + .empty() + :map(function() + -- Special case for help where we read all the tags ourselves + if vim.tbl_contains({ 'h', 'he', 'hel', 'help' }, arguments[1] or '') then + return require('blink.cmp.sources.cmdline.help').get_completions() + end + + local query = (text_before_cursor .. current_arg_prefix):gsub([[\\]], [[\\\\]]) + return vim.fn.getcompletion(query, 'cmdline') + end) + :map(function(completions) + local items = {} + for _, completion in ipairs(completions) do + -- remove prefix from the label + if string.find(completion, current_arg_prefix, 1, true) == 1 then + completion = completion:sub(#current_arg_prefix + 1) + end - local items = {} - for _, completion in ipairs(completions) do - -- remove prefix from the label - if string.find(completion, current_arg_prefix, 1, true) == 1 then - completion = completion:sub(#current_arg_prefix + 1) - end + -- add prefix to the newText + local new_text = completion + if string.find(new_text, current_arg_prefix, 1, true) ~= 1 then new_text = current_arg_prefix .. completion end - -- add prefix to the newText - local new_text = completion - if string.find(new_text, current_arg_prefix, 1, true) ~= 1 then new_text = current_arg_prefix .. completion end + table.insert(items, { + label = completion, + insertText = completion, + sortText = completion, + textEdit = { + newText = new_text, + range = { + start = { line = 0, character = #text_before_cursor }, + ['end'] = { line = 0, character = #text_before_cursor + #current_arg }, + }, + }, + kind = require('blink.cmp.types').CompletionItemKind.Property, + }) + end - table.insert(items, { - label = completion, - insertText = completion, - textEdit = { - newText = new_text, - range = { - start = { line = 0, character = #text_before_cursor }, - ['end'] = { line = 0, character = #text_before_cursor + #current_arg }, - }, - }, - kind = require('blink.cmp.types').CompletionItemKind.Property, - }) - end + callback({ + is_incomplete_backward = true, + is_incomplete_forward = false, + items = items, + }) + end) + :catch(function(err) + vim.notify('Error while fetching completions: ' .. err, vim.log.levels.ERROR) + callback({ is_incomplete_backward = false, is_incomplete_forward = false, items = {} }) + end) - callback({ - is_incomplete_backward = false, - is_incomplete_forward = false, - items = items, - }) + return function() task:cancel() end end return cmdline diff --git a/lua/blink/cmp/sources/cmdline/regex.lua b/lua/blink/cmp/sources/cmdline/regex.lua deleted file mode 100644 index 003b66ba..00000000 --- a/lua/blink/cmp/sources/cmdline/regex.lua +++ /dev/null @@ -1,60 +0,0 @@ ----@param patterns string[] ----@param head boolean ----@return table #regex object -local function create_regex(patterns, head) - local pattern = [[\%(]] .. table.concat(patterns, [[\|]]) .. [[\)]] - if head then pattern = '^' .. pattern end - return vim.regex(pattern) -end - ----@class cmp-cmdline.Option ----@field treat_trailing_slash boolean ----@field ignore_cmds string[] -local DEFAULT_OPTION = { - treat_trailing_slash = true, - ignore_cmds = { 'Man', '!' }, -} - -local MODIFIER_REGEX = create_regex({ - [=[\s*abo\%[veleft]\s*]=], - [=[\s*bel\%[owright]\s*]=], - [=[\s*bo\%[tright]\s*]=], - [=[\s*bro\%[wse]\s*]=], - [=[\s*conf\%[irm]\s*]=], - [=[\s*hid\%[e]\s*]=], - [=[\s*keepal\s*t]=], - [=[\s*keeppa\%[tterns]\s*]=], - [=[\s*lefta\%[bove]\s*]=], - [=[\s*loc\%[kmarks]\s*]=], - [=[\s*nos\%[wapfile]\s*]=], - [=[\s*rightb\%[elow]\s*]=], - [=[\s*sil\%[ent]\s*]=], - [=[\s*tab\s*]=], - [=[\s*to\%[pleft]\s*]=], - [=[\s*verb\%[ose]\s*]=], - [=[\s*vert\%[ical]\s*]=], -}, true) - -local COUNT_RANGE_REGEX = create_regex({ - [=[\s*\%(\d\+\|\$\)\%[,\%(\d\+\|\$\)]\s*]=], - [=[\s*'\%[<,'>]\s*]=], - [=[\s*\%(\d\+\|\$\)\s*]=], -}, true) - -local ONLY_RANGE_REGEX = create_regex({ - [=[^\s*\%(\d\+\|\$\)\%[,\%(\d\+\|\$\)]\s*$]=], - [=[^\s*'\%[<,'>]\s*$]=], - [=[^\s*\%(\d\+\|\$\)\s*$]=], -}, true) - -local OPTION_NAME_COMPLETION_REGEX = create_regex({ - [=[se\%[tlocal][^=]*$]=], -}, true) - -return { - DEFAULT_OPTION = DEFAULT_OPTION, - MODIFIER_REGEX = MODIFIER_REGEX, - COUNT_RANGE_REGEX = COUNT_RANGE_REGEX, - ONLY_RANGE_REGEX = ONLY_RANGE_REGEX, - OPTION_NAME_COMPLETION_REGEX = OPTION_NAME_COMPLETION_REGEX, -}