diff --git a/lua/cmp_path/init.lua b/lua/cmp_path/init.lua index 7bd094a..8886914 100644 --- a/lua/cmp_path/init.lua +++ b/lua/cmp_path/init.lua @@ -1,4 +1,4 @@ -local cmp = require'cmp' +local cmp = require 'cmp' local NAME_REGEX = '\\%([^/\\\\:\\*?<>\'"`\\|]\\)' local PATH_REGEX = vim.regex(([[\%(/PAT*[^/\\\\:\\*?<>\'"`\\| ]\)*/\zePAT*$]]):gsub('PAT', NAME_REGEX)) @@ -9,10 +9,11 @@ local constants = { max_lines = 20, } ----@class cmp_path.Options +---@class cmp_path.Option ---@field public trailing_slash boolean +---@field public get_cwd fun(): string ----@type cmp_buffer.Options +---@type cmp_path.Option local defaults = { trailing_slash = false, get_cwd = function(params) @@ -24,34 +25,24 @@ source.new = function() return setmetatable({}, { __index = source }) end ----@return cmp_buffer.Options -source._validate_options = function(_, params) - local opts = vim.tbl_deep_extend('keep', params.option, defaults) - vim.validate({ - trailing_slash = { opts.trailing_slash, 'boolean' }, - get_cwd = { opts.get_cwd, 'function' }, - }) - return opts -end - source.get_trigger_characters = function() return { '/', '.' } end -source.get_keyword_pattern = function() +source.get_keyword_pattern = function(self, params) return NAME_REGEX .. '*' end source.complete = function(self, params, callback) - local opts = self:_validate_options(params) + local option = self:_validate_option(params) - local dirname = self:_dirname(params, opts) + 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, opts, function(err, candidates) + self:_candidates(dirname, include_hidden, option, function(err, candidates) if err then return callback() end @@ -59,7 +50,20 @@ source.complete = function(self, params, callback) end) end -source._dirname = function(self, params, opts) +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 @@ -68,7 +72,7 @@ source._dirname = function(self, params, opts) 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 = opts.get_cwd(params) + local buf_dirname = option.get_cwd(params) if vim.api.nvim_get_mode().mode == 'c' then buf_dirname = vim.fn.getcwd() end @@ -107,24 +111,7 @@ source._dirname = function(self, params, opts) return nil end -local function lines_from(file, count) - local bfile = assert(io.open(file, 'rb')) - local first_k = bfile:read(1024) - if first_k:find('\0') then - return { kind = cmp.lsp.MarkupKind.PlainText, value = 'binary file' } - end - local lines = { '```' .. (vim.filetype.match { filename = file } or '') } - for line in first_k:gmatch("[^\r\n]+") do - lines[#lines + 1] = line - if count ~= nil and #lines >= count then - break - end - end - lines[#lines + 1] = '```' - return { kind = cmp.lsp.MarkupKind.Markdown, value = table.concat(lines, '\n') } -end - -source._candidates = function(_, dirname, include_hidden, opts, callback) +source._candidates = function(_, dirname, include_hidden, option, callback) local fs, err = vim.loop.fs_scandir(dirname) if err then return callback(err, nil) @@ -168,7 +155,7 @@ source._candidates = function(_, dirname, include_hidden, opts, callback) item.kind = cmp.lsp.CompletionItemKind.Folder item.label = name .. '/' item.insertText = name .. '/' - if not opts.trailing_slash then + if not option.trailing_slash then item.word = name end end @@ -198,15 +185,39 @@ source._is_slash_comment = function(_) return is_slash_comment and not no_filetype end -function source:resolve(completion_item, callback) - local data = completion_item.data - if data.stat and data.stat.type == 'file' then - local ok, preview_lines = pcall(lines_from, data.path, constants.max_lines) - if ok then - completion_item.documentation = preview_lines +---@return cmp_path.Option +source._validate_option = function(_, params) + local option = vim.tbl_deep_extend('keep', params.option, defaults) + vim.validate({ + trailing_slash = { option.trailing_slash, 'boolean' }, + get_cwd = { option.get_cwd, 'function' }, + }) + return option +end + +source._get_documentation = function(_, filename, count) + local binary = assert(io.open(filename, 'rb')) + local first_kb = binary:read(1024) + if first_kb:find('\0') then + return { kind = cmp.lsp.MarkupKind.PlainText, value = 'binary file' } + end + + local contents = {} + for content in first_kb:gmatch("[^\r\n]+") do + table.insert(contents, content) + if count ~= nil and #contents >= count then + break end end - callback(completion_item) + + 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