From b77413ff8f59f380612074f0c9bd49093d8db695 Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Sun, 19 Jan 2025 13:52:52 +0100 Subject: 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 --- lua/blink/cmp/sources/path/fs.lua | 70 ++++++++++++++++++++ lua/blink/cmp/sources/path/init.lua | 89 +++++++++++++++++++++++++ lua/blink/cmp/sources/path/lib.lua | 125 +++++++++++++++++++++++++++++++++++ lua/blink/cmp/sources/path/regex.lua | 10 +++ 4 files changed, 294 insertions(+) create mode 100644 lua/blink/cmp/sources/path/fs.lua create mode 100644 lua/blink/cmp/sources/path/init.lua create mode 100644 lua/blink/cmp/sources/path/lib.lua create mode 100644 lua/blink/cmp/sources/path/regex.lua (limited to 'lua/blink/cmp/sources/path') diff --git a/lua/blink/cmp/sources/path/fs.lua b/lua/blink/cmp/sources/path/fs.lua new file mode 100644 index 0000000..4ac79f0 --- /dev/null +++ b/lua/blink/cmp/sources/path/fs.lua @@ -0,0 +1,70 @@ +local async = require('blink.cmp.lib.async') +local uv = vim.uv +local fs = {} + +--- Scans a directory asynchronously in a loop until +--- it finds all entries +--- @param path string +--- @return blink.cmp.Task +function fs.scan_dir_async(path) + local max_entries = 200 + return async.task.new(function(resolve, reject) + uv.fs_opendir(path, function(err, handle) + if err ~= nil or handle == nil then return reject(err) end + + local all_entries = {} + + local function read_dir() + uv.fs_readdir(handle, function(err, entries) + if err ~= nil or entries == nil then return reject(err) end + + vim.list_extend(all_entries, entries) + if #entries == max_entries then + read_dir() + else + resolve(all_entries) + end + end) + end + read_dir() + end, max_entries) + end) +end + +--- @param entries { name: string, type: string }[] +--- @return blink.cmp.Task +function fs.fs_stat_all(cwd, entries) + local tasks = {} + for _, entry in ipairs(entries) do + table.insert( + tasks, + async.task.new(function(resolve) + uv.fs_stat(cwd .. '/' .. entry.name, function(err, stat) + if err then return resolve(nil) end + resolve({ name = entry.name, type = entry.type, stat = stat }) + end) + end) + ) + end + return async.task.await_all(tasks):map(function(entries) + return vim.tbl_filter(function(entry) return entry ~= nil end, entries) + end) +end + +--- @param path string +--- @param byte_limit number +--- @return blink.cmp.Task +function fs.read_file(path, byte_limit) + return async.task.new(function(resolve, reject) + uv.fs_open(path, 'r', 438, function(open_err, fd) + if open_err or fd == nil then return reject(open_err) end + uv.fs_read(fd, byte_limit, 0, function(read_err, data) + uv.fs_close(fd, function() end) + if read_err or data == nil then return reject(read_err) end + resolve(data) + end) + end) + end) +end + +return fs diff --git a/lua/blink/cmp/sources/path/init.lua b/lua/blink/cmp/sources/path/init.lua new file mode 100644 index 0000000..bb5f508 --- /dev/null +++ b/lua/blink/cmp/sources/path/init.lua @@ -0,0 +1,89 @@ +-- credit to https://github.com/hrsh7th/cmp-path for the original implementation +-- and https://codeberg.org/FelipeLema/cmp-async-path for the async implementation + +--- @class blink.cmp.PathOpts +--- @field trailing_slash boolean +--- @field label_trailing_slash boolean +--- @field get_cwd fun(context: blink.cmp.Context): string +--- @field show_hidden_files_by_default boolean + +--- @class blink.cmp.Source +--- @field opts blink.cmp.PathOpts +local path = {} + +function path.new(opts) + local self = setmetatable({}, { __index = path }) + + --- @type blink.cmp.PathOpts + opts = vim.tbl_deep_extend('keep', opts, { + trailing_slash = true, + label_trailing_slash = true, + get_cwd = function(context) return vim.fn.expand(('#%d:p:h'):format(context.bufnr)) end, + show_hidden_files_by_default = false, + }) + require('blink.cmp.config.utils').validate('sources.providers.path', { + trailing_slash = { opts.trailing_slash, 'boolean' }, + label_trailing_slash = { opts.label_trailing_slash, 'boolean' }, + get_cwd = { opts.get_cwd, 'function' }, + show_hidden_files_by_default = { opts.show_hidden_files_by_default, 'boolean' }, + }, opts) + + self.opts = opts + return self +end + +function path:get_trigger_characters() return { '/', '.' } end + +function path:get_completions(context, callback) + -- we use libuv, but the rest of the library expects to be synchronous + callback = vim.schedule_wrap(callback) + + local lib = require('blink.cmp.sources.path.lib') + + local dirname = lib.dirname(self.opts.get_cwd, context) + if not dirname then return callback({ is_incomplete_forward = false, is_incomplete_backward = false, items = {} }) end + + local include_hidden = self.opts.show_hidden_files_by_default + or (string.sub(context.line, context.bounds.start_col, context.bounds.start_col) == '.' and context.bounds.length == 0) + or ( + string.sub(context.line, context.bounds.start_col - 1, context.bounds.start_col - 1) == '.' + and context.bounds.length > 0 + ) + lib + .candidates(context, dirname, include_hidden, self.opts) + :map( + function(candidates) + callback({ is_incomplete_forward = false, is_incomplete_backward = false, items = candidates }) + end + ) + :catch(function() callback() end) +end + +function path:resolve(item, callback) + require('blink.cmp.sources.path.fs') + .read_file(item.data.full_path, 1024) + :map(function(content) + local is_binary = content:find('\0') + + -- binary file + if is_binary then + item.documentation = { + kind = 'plaintext', + value = 'Binary file', + } + -- highlight with markdown + else + local ext = vim.fn.fnamemodify(item.data.path, ':e') + item.documentation = { + kind = 'markdown', + value = '```' .. ext .. '\n' .. content .. '```', + } + end + + return item + end) + :map(function(resolved_item) callback(resolved_item) end) + :catch(function() callback(item) end) +end + +return path diff --git a/lua/blink/cmp/sources/path/lib.lua b/lua/blink/cmp/sources/path/lib.lua new file mode 100644 index 0000000..53fd970 --- /dev/null +++ b/lua/blink/cmp/sources/path/lib.lua @@ -0,0 +1,125 @@ +local regex = require('blink.cmp.sources.path.regex') +local lib = {} + +--- @param get_cwd fun(context: blink.cmp.Context): string +--- @param context blink.cmp.Context +function lib.dirname(get_cwd, context) + -- HACK: move this :sub logic into the context? + -- it's not obvious that you need to avoid going back a char if the start_col == end_col + local line_before_cursor = context.line:sub(1, context.bounds.start_col - (context.bounds.length == 0 and 1 or 0)) + local s = regex.PATH:match_str(line_before_cursor) + if not s then return nil end + + local dirname = string.gsub(string.sub(line_before_cursor, s + 2), regex.NAME .. '*$', '') -- exclude '/' + local prefix = string.sub(line_before_cursor, 1, s + 1) -- include '/' + + local buf_dirname = get_cwd(context) + if vim.api.nvim_get_mode().mode == 'c' then buf_dirname = vim.fn.getcwd() end + if prefix:match('%.%./$') then return vim.fn.resolve(buf_dirname .. '/../' .. dirname) end + if prefix:match('%./$') or prefix:match('"$') or prefix:match("'$") then + return vim.fn.resolve(buf_dirname .. '/' .. dirname) + end + if prefix:match('~/$') then return vim.fn.resolve(vim.fn.expand('~') .. '/' .. dirname) end + local env_var_name = prefix:match('%$([%a_]+)/$') + if env_var_name then + local env_var_value = vim.fn.getenv(env_var_name) + if env_var_value ~= vim.NIL then return vim.fn.resolve(env_var_value .. '/' .. dirname) end + end + if prefix:match('/$') then + local accept = true + -- Ignore URL components + accept = accept and not prefix:match('%a/$') + -- Ignore URL scheme + accept = accept and not prefix:match('%a+:/$') and not prefix:match('%a+://$') + -- Ignore HTML closing tags + accept = accept and not prefix:match('\'"`\\|]\\)' +local PATH_REGEX = + assert(vim.regex(([[\%(\%(/PAT*[^/\\\\:\\*?<>\'"`\\| .~]\)\|\%(/\.\.\)\)*/\zePAT*$]]):gsub('PAT', NAME_REGEX))) + +return { + --- Lua pattern for matching file names + NAME = '[^/\\:*?<>\'"`|]', + --- Vim regex for matching file paths + PATH = PATH_REGEX, +} -- cgit v1.2.3