summaryrefslogtreecommitdiff
path: root/lua/blink/cmp/sources/cmdline
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-01-19 13:52:52 +0100
committerMike Vink <mike@pionative.com>2025-01-19 13:52:52 +0100
commitb77413ff8f59f380612074f0c9bd49093d8db695 (patch)
tree32c39a811ba96ed4ab0a1c81cce9f8d518ed7e31 /lua/blink/cmp/sources/cmdline
Squashed 'mut/neovim/pack/plugins/start/blink.cmp/' content from commit 1cc3b1a
git-subtree-dir: mut/neovim/pack/plugins/start/blink.cmp git-subtree-split: 1cc3b1a908fbcfd15451c4772759549724f38524
Diffstat (limited to 'lua/blink/cmp/sources/cmdline')
-rw-r--r--lua/blink/cmp/sources/cmdline/constants.lua40
-rw-r--r--lua/blink/cmp/sources/cmdline/help.lua53
-rw-r--r--lua/blink/cmp/sources/cmdline/init.lua107
3 files changed, 200 insertions, 0 deletions
diff --git a/lua/blink/cmp/sources/cmdline/constants.lua b/lua/blink/cmp/sources/cmdline/constants.lua
new file mode 100644
index 0000000..fd2ee85
--- /dev/null
+++ b/lua/blink/cmp/sources/cmdline/constants.lua
@@ -0,0 +1,40 @@
+return {
+ help_commands = {
+ 'help',
+ 'hel',
+ 'he',
+ 'h',
+ },
+ file_commands = {
+ 'edit',
+ 'e',
+ 'read',
+ 'r',
+ 'write',
+ 'w',
+ 'saveas',
+ 'sav',
+ 'split',
+ 'sp',
+ 'vsplit',
+ 'vs',
+ 'tabedit',
+ 'tabe',
+ 'badd',
+ 'bad',
+ 'next',
+ 'n',
+ 'previous',
+ 'prev',
+ 'args',
+ 'source',
+ 'so',
+ 'find',
+ 'fin',
+ 'diffsplit',
+ 'diffs',
+ 'diffpatch',
+ 'diffp',
+ 'make',
+ },
+}
diff --git a/lua/blink/cmp/sources/cmdline/help.lua b/lua/blink/cmp/sources/cmdline/help.lua
new file mode 100644
index 0000000..bae4988
--- /dev/null
+++ b/lua/blink/cmp/sources/cmdline/help.lua
@@ -0,0 +1,53 @@
+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
+
+--- @param arg_prefix string
+function help.get_completions(arg_prefix)
+ 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(tags_arrs) return require('blink.cmp.lib.utils').flatten(tags_arrs) end)
+ :map(function(tags)
+ -- TODO: remove after adding support for fuzzy matching on custom range
+ return vim.tbl_filter(function(tag) return vim.startswith(tag, arg_prefix) end, tags)
+ end)
+end
+
+return help
diff --git a/lua/blink/cmp/sources/cmdline/init.lua b/lua/blink/cmp/sources/cmdline/init.lua
new file mode 100644
index 0000000..5ff5e11
--- /dev/null
+++ b/lua/blink/cmp/sources/cmdline/init.lua
@@ -0,0 +1,107 @@
+-- Credit goes to @hrsh7th for the code that this was based on
+-- https://github.com/hrsh7th/cmp-cmdline
+-- License: MIT
+
+local async = require('blink.cmp.lib.async')
+local constants = require('blink.cmp.sources.cmdline.constants')
+
+--- @class blink.cmp.Source
+local cmdline = {}
+
+function cmdline.new()
+ local self = setmetatable({}, { __index = cmdline })
+ self.before_line = ''
+ self.offset = -1
+ self.ctype = ''
+ self.items = {}
+ return self
+end
+
+function cmdline:get_trigger_characters() return { ' ', '.', '#', '-', '=', '/', ':' } end
+
+function cmdline:get_completions(context, callback)
+ local arguments = vim.split(context.line, ' ', { plain = true })
+ local arg_number = #vim.split(context.line:sub(1, context.cursor[2] + 1), ' ', { plain = true })
+ local text_before_argument = table.concat(require('blink.cmp.lib.utils').slice(arguments, 1, arg_number - 1), ' ')
+ .. (arg_number > 1 and ' ' or '')
+
+ local current_arg = arguments[arg_number]
+ local keyword_config = require('blink.cmp.config').completion.keyword
+ local keyword = context.get_bounds(keyword_config.range)
+ local current_arg_prefix = current_arg:sub(1, keyword.start_col - #text_before_argument - 1)
+
+ local task = async.task
+ .empty()
+ :map(function()
+ -- Special case for help where we read all the tags ourselves
+ if vim.tbl_contains(constants.help_commands, arguments[1] or '') then
+ return require('blink.cmp.sources.cmdline.help').get_completions(current_arg_prefix)
+ end
+
+ local completions = {}
+ local completion_type = vim.fn.getcmdcompltype()
+ -- Handle custom completions explicitly, since otherwise they won't work in input() mode (getcmdtype() == '@')
+ if vim.startswith(completion_type, 'custom,') or vim.startswith(completion_type, 'customlist,') then
+ local fun = completion_type:gsub('custom,', ''):gsub('customlist,', '')
+ completions = vim.fn.call(fun, { current_arg_prefix, vim.fn.getcmdline(), vim.fn.getcmdpos() })
+ -- `custom,` type returns a string, delimited by newlines
+ if type(completions) == 'string' then completions = vim.split(completions, '\n') end
+ else
+ local query = (text_before_argument .. current_arg_prefix):gsub([[\\]], [[\\\\]])
+ completions = vim.fn.getcompletion(query, 'cmdline')
+ end
+
+ -- Special case for files, escape special characters
+ if vim.tbl_contains(constants.file_commands, arguments[1] or '') then
+ completions = vim.tbl_map(function(completion) return vim.fn.fnameescape(completion) end, completions)
+ end
+
+ return completions
+ end)
+ :map(function(completions)
+ local items = {}
+ for _, completion in ipairs(completions) do
+ local has_prefix = string.find(completion, current_arg_prefix, 1, true) == 1
+
+ -- remove prefix from the filter text
+ local filter_text = completion
+ if has_prefix then filter_text = completion:sub(#current_arg_prefix + 1) end
+
+ -- for lua, use the filter text as the label since it doesn't include the prefix
+ local label = arguments[1] == 'lua' and filter_text or completion
+
+ -- add prefix to the newText
+ local new_text = completion
+ if not has_prefix then new_text = current_arg_prefix .. completion end
+
+ table.insert(items, {
+ label = label,
+ filterText = filter_text,
+ -- move items starting with special characters to the end of the list
+ sortText = label:lower():gsub('^([!-@\\[-`])', '~%1'),
+ textEdit = {
+ newText = new_text,
+ range = {
+ start = { line = 0, character = #text_before_argument },
+ ['end'] = { line = 0, character = #text_before_argument + #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, { title = 'blink.cmp' })
+ callback({ is_incomplete_backward = false, is_incomplete_forward = false, items = {} })
+ end)
+
+ return function() task:cancel() end
+end
+
+return cmdline