local cmp = require 'cmp' local NAME_REGEX = '\\%([^/\\\\:\\*?<>\'"`\\|]\\)' local PATH_REGEX = vim.regex(([[\%(\%(/PAT*[^/\\\\:\\*?<>\'"`\\| .~]\)\|\%(/\.\.\)\)*/\zePAT*$]]):gsub('PAT', NAME_REGEX)) local source = {} local constants = { max_lines = 20, } ---@class cmp_path.Option ---@field public trailing_slash boolean ---@field public label_trailing_slash boolean ---@field public get_cwd fun(): string ---@type cmp_path.Option local defaults = { trailing_slash = false, label_trailing_slash = true, get_cwd = function(params) return vim.fn.expand(('#%d:p:h'):format(params.context.bufnr)) end, } source.new = function() return setmetatable({}, { __index = source }) end source.get_trigger_characters = function() return { '/', '.' } end source.get_keyword_pattern = function(self, params) return NAME_REGEX .. '*' end source.complete = function(self, params, callback) local option = self:_validate_option(params) local dirname = self:_dirname(params, option) if not dirname then return callback() end local include_hidden = string.sub(params.context.cursor_before_line, params.offset, params.offset) == '.' self:_candidates(dirname, include_hidden, option, function(err, candidates) if err then return callback() end callback(candidates) end) end source.resolve = function(self, completion_item, callback) local data = completion_item.data if data.stat and data.stat.type == 'file' then local ok, documentation = pcall(function() return self:_get_documentation(data.path, constants.max_lines) end) if ok then completion_item.documentation = documentation end end callback(completion_item) end source._dirname = function(self, params, option) local s = PATH_REGEX:match_str(params.context.cursor_before_line) if not s then return nil end local dirname = string.gsub(string.sub(params.context.cursor_before_line, s + 2), '%a*$', '') -- exclude '/' local prefix = string.sub(params.context.cursor_before_line, 1, s + 1) -- include '/' local buf_dirname = option.get_cwd(params) 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('= count then break end end local filetype = vim.filetype.match({ filename = filename }) if not filetype then return { kind = cmp.lsp.MarkupKind.PlainText, value = table.concat(contents, '\n') } end table.insert(contents, 1, '```' .. filetype) table.insert(contents, '```') return { kind = cmp.lsp.MarkupKind.Markdown, value = table.concat(contents, '\n') } end return source