diff --git a/lua/blink-ripgrep/backends/ripgrep/ripgrep.lua b/lua/blink-ripgrep/backends/ripgrep/ripgrep.lua index 72f1279..a973378 100644 --- a/lua/blink-ripgrep/backends/ripgrep/ripgrep.lua +++ b/lua/blink-ripgrep/backends/ripgrep/ripgrep.lua @@ -58,8 +58,7 @@ function RipgrepBackend:get_matches(prefix, context, resolve) local parsed = require("blink-ripgrep.backends.ripgrep.ripgrep_parser").parse( lines, - cwd, - self.config.context_size + cwd ) local kinds = require("blink.cmp.types").CompletionItemKind @@ -73,10 +72,6 @@ function RipgrepBackend:get_matches(prefix, context, resolve) -- way to display the same match multiple times if not items[matchkey] then local label = match.match.text - local docstring = "" - for _, line in ipairs(match.context_preview) do - docstring = docstring .. line.text .. "\n" - end local draw_docs = function(draw_opts) require("blink-ripgrep.documentation").render_item_documentation( @@ -91,7 +86,6 @@ function RipgrepBackend:get_matches(prefix, context, resolve) items[matchkey] = { documentation = { kind = "markdown", - value = docstring, draw = draw_docs, -- legacy, will be removed in a future release of blink -- https://github.com/Saghen/blink.cmp/issues/1113 diff --git a/lua/blink-ripgrep/backends/ripgrep/ripgrep_parser.lua b/lua/blink-ripgrep/backends/ripgrep/ripgrep_parser.lua index 1d207de..8b8d499 100644 --- a/lua/blink-ripgrep/backends/ripgrep/ripgrep_parser.lua +++ b/lua/blink-ripgrep/backends/ripgrep/ripgrep_parser.lua @@ -5,7 +5,6 @@ local M = {} ---@class blink-ripgrep.RipgrepFile ---@field language string the treesitter language of the file, used to determine what grammar to highlight the preview with ----@field lines table the context preview, shared for all the matches in this file so that they can display a subset ---@field matches table ---@field relative_to_cwd string the relative path of the file to the current working directory @@ -14,7 +13,6 @@ local M = {} ---@field start_col number ---@field end_col number ---@field match {text: string} the matched text ----@field context_preview blink-ripgrep.NumberedLine[] the preview of this match. Each key is the line number in the original file, and each value is the line of context (text) ---@param json unknown ---@param output blink-ripgrep.RipgrepOutput @@ -23,8 +21,6 @@ local function get_file_context(json, output) local filename = json.data.path.text local file = output.files[filename] local line_number = json.data.line_number - -- assert(line_number) - -- assert(not file.lines[line_number]) return file, line_number end @@ -36,8 +32,7 @@ end -- ---@param ripgrep_output string[] ripgrep output in jsonl format ---@param cwd string the current working directory ----@param context_size number the number of lines of context to include in the output -function M.parse(ripgrep_output, cwd, context_size) +function M.parse(ripgrep_output, cwd) ---@type blink-ripgrep.RipgrepOutput local output = { files = {} } @@ -63,16 +58,11 @@ function M.parse(ripgrep_output, cwd, context_size) output.files[filename] = { language = language, - lines = {}, matches = {}, relative_to_cwd = relative_filename, } - elseif json.type == "context" then - local file, line_number = get_file_context(json, output) - file.lines[line_number] = json.data.lines.text elseif json.type == "match" then local file, line_number = get_file_context(json, output) - file.lines[line_number] = json.data.lines.text local text = json.data.submatches[1].match.text @@ -82,51 +72,12 @@ function M.parse(ripgrep_output, cwd, context_size) end_col = json.data.submatches[1]["end"], match = { text = text }, line_number = line_number, - context_preview = {}, } end - elseif json.type == "end" then - -- Right now, we have collected the necessary lines for the context in - -- previous steps. Get the context preview for each match. - local filename = json.data.path.text - local file = output.files[filename] - - for _, match in pairs(file.matches) do - match.context_preview = - M.get_context_preview(file.lines, match.line_number, context_size) - end - - -- clear the lines to save memory - file.lines = {} end end end return output end ----@alias blink-ripgrep.NumberedLine {line_number: number, text: string} - ----@param lines table ----@param matched_line number the line number the match was found on ----@param context_size number how many lines of context to include before and after the match ----@return blink-ripgrep.NumberedLine[] -function M.get_context_preview(lines, matched_line, context_size) - ---@type blink-ripgrep.NumberedLine[] - local context_preview = {} - - local start_line = math.max(1, matched_line - context_size) - local end_line = matched_line + context_size - - for i = start_line, end_line do - local line = lines[i] - - if line then - local data = { line_number = i, text = line:gsub("%s*$", "") } - context_preview[#context_preview + 1] = data - end - end - - return context_preview -end - return M diff --git a/lua/blink-ripgrep/documentation.lua b/lua/blink-ripgrep/documentation.lua index 0b36bd8..2535c07 100644 --- a/lua/blink-ripgrep/documentation.lua +++ b/lua/blink-ripgrep/documentation.lua @@ -6,6 +6,8 @@ pcall(function() end) vim.api.nvim_set_hl(0, "BlinkRipgrepMatch", { link = "Search", default = true }) +---@alias blink-ripgrep.NumberedLine {line_number: number, text: string} + ---@param config blink-ripgrep.Options ---@param draw_opts blink.cmp.CompletionDocumentationDrawOpts ---@param file blink-ripgrep.RipgrepFile @@ -23,8 +25,14 @@ function documentation.render_item_documentation(config, draw_opts, file, match) - 1 ), } - for _, data in ipairs(match.context_preview) do - table.insert(text, data.text) + + local context_preview = documentation.get_match_context( + config.context_size, + match.line_number, + file.relative_to_cwd + ) + for _, line in ipairs(context_preview) do + table.insert(text, line.text) end -- TODO add extmark highlighting for the divider line like in blink @@ -63,8 +71,34 @@ function documentation.render_item_documentation(config, draw_opts, file, match) require("blink-ripgrep.highlighting").highlight_match_in_doc_window( bufnr, match, - highlight_ns_id + highlight_ns_id, + context_preview ) end +---@param context_size number +---@param match_line number # the line number the match was found on +---@param file_path string +function documentation.get_match_context(context_size, match_line, file_path) + local start_line = math.max(1, match_line - context_size) + local end_line = match_line + context_size + + local text = vim.fn.readfile(file_path, "", end_line) + assert(type(text) == "table", "expected table from readfile") + ---@cast text string[] + + ---@type blink-ripgrep.NumberedLine[] + local context = {} + for i, line in ipairs(text) do + if i >= start_line then + table.insert(context, { + line_number = i, + text = line, + } --[[@as blink-ripgrep.NumberedLine]]) + end + end + + return context +end + return documentation diff --git a/lua/blink-ripgrep/highlighting.lua b/lua/blink-ripgrep/highlighting.lua index 4cd2adc..5bc28a0 100644 --- a/lua/blink-ripgrep/highlighting.lua +++ b/lua/blink-ripgrep/highlighting.lua @@ -6,10 +6,16 @@ local M = {} ---@param bufnr number ---@param match blink-ripgrep.RipgrepMatch ---@param highlight_ns_id number -function M.highlight_match_in_doc_window(bufnr, match, highlight_ns_id) +---@param context_preview blink-ripgrep.NumberedLine[] +function M.highlight_match_in_doc_window( + bufnr, + match, + highlight_ns_id, + context_preview +) ---@type number | nil local line_in_docs = nil - for line, data in ipairs(match.context_preview) do + for line, data in ipairs(context_preview) do if data.line_number == match.line_number then line_in_docs = line break @@ -18,6 +24,7 @@ function M.highlight_match_in_doc_window(bufnr, match, highlight_ns_id) assert(line_in_docs, "missing line in docs") + -- highlight the word that matched in this context preview vim.api.nvim_buf_set_extmark( bufnr, highlight_ns_id, diff --git a/spec/blink-ripgrep/documentation_spec.lua b/spec/blink-ripgrep/documentation_spec.lua new file mode 100644 index 0000000..ed199bd --- /dev/null +++ b/spec/blink-ripgrep/documentation_spec.lua @@ -0,0 +1,109 @@ +local documentation = require("blink-ripgrep.documentation") +local assert = require("luassert") + +---@param lines string[] +local function create_test_file(lines) + local target_file_path = vim.fn.tempname() + local file = io.open(target_file_path, "w") -- Open or create the file in write mode + assert(file, "Failed to create file " .. target_file_path) + if file then + for _, line in ipairs(lines) do + file:write(line .. "\n") + end + file:close() + end + local stat = vim.uv.fs_stat(target_file_path) + assert(stat) + assert(stat.type == "file") + + return target_file_path +end + +describe("get_context_preview", function() + it("can display context around the match", function() + -- the happy path case + local lines = { + "line 1", + "line 2", + "line 3", + "line 4", + "line 5", + "line 6", + "line 7", + "line 8", + "line 9", + "line 10", + } + local file = create_test_file(lines) + + local matched_line = 4 + local context_size = 1 + local result = + documentation.get_match_context(context_size, matched_line, file) + + assert.same(result, { + { line_number = 3, text = "line 3" }, + { line_number = 4, text = "line 4" }, + { line_number = 5, text = "line 5" }, + }) + end) + + it("does not crash if context_size is too large", function() + local lines = { + "line 1", + } + local file = create_test_file(lines) + + local matched_line = 1 + local context_size = 10 + local result = + documentation.get_match_context(context_size, matched_line, file) + + assert.same(result, { + { line_number = 1, text = "line 1" }, + }) + end) + + it("does not crash if context_size is too small", function() + local lines = { + "line 1", + } + local file = create_test_file(lines) + + local matched_line = 1 + local context_size = 0 + local result = + documentation.get_match_context(context_size, matched_line, file) + + assert.same(result, { + { line_number = 1, text = "line 1" }, + }) + end) + + it("can display context around the match at the end of the file", function() + local lines = { + "line 1", + "line 2", + "line 3", + "line 4", + "line 5", + "line 6", + "line 7", + "line 8", + "line 9", + "line 10", + } + local file = create_test_file(lines) + + local matched_line = 9 + local context_size = 1 + local result = + documentation.get_match_context(context_size, matched_line, file) + + assert.same(result, { + { line_number = 8, text = "line 8" }, + { line_number = 9, text = "line 9" }, + { line_number = 10, text = "line 10" }, + }) + end) +end) diff --git a/spec/blink-ripgrep/ripgrep_parser_spec.lua b/spec/blink-ripgrep/ripgrep_parser_spec.lua index e2b4090..2cd427d 100644 --- a/spec/blink-ripgrep/ripgrep_parser_spec.lua +++ b/spec/blink-ripgrep/ripgrep_parser_spec.lua @@ -6,12 +6,11 @@ describe("ripgrep_parser", function() vim.fn.readfile("spec/blink-ripgrep/rg-output.jsonl") it("can parse according to the expected schema", function() - local result = ripgrep_parser.parse(ripgrep_output_lines, "/home/user", 1) + local result = ripgrep_parser.parse(ripgrep_output_lines, "/home/user") local filename = "integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts" assert.is_not_nil(result.files) assert.is_not_nil(result.files[filename]) - assert.is_truthy(#result.files[filename].lines == 0) assert.same(result.files[filename].relative_to_cwd, filename) for _, file in ipairs(result.files) do @@ -26,83 +25,4 @@ describe("ripgrep_parser", function() assert.is_not_nil(submatch.end_col) end end) - - describe("get_context_preview", function() - it("can display context around the match", function() - -- the happy path case - local lines = { - [1] = "line 1", - [2] = "line 2", - [3] = "line 3", - [4] = "line 4", - [5] = "line 5", - [6] = "line 6", - [7] = "line 7", - [8] = "line 8", - [9] = "line 9", - [10] = "line 10", - } - - local matched_line = 4 - local context_size = 1 - local result = - ripgrep_parser.get_context_preview(lines, matched_line, context_size) - - assert.same(result, { - { line_number = 3, text = "line 3" }, - { line_number = 4, text = "line 4" }, - { line_number = 5, text = "line 5" }, - }) - end) - - it("does not crash if context_size is too large", function() - local lines = { - [1] = "line 1", - } - - local matched_line = 1 - local context_size = 10 - local result = - ripgrep_parser.get_context_preview(lines, matched_line, context_size) - - assert.same(result, { - { line_number = 1, text = "line 1" }, - }) - end) - - it("does not crash if context_size is too small", function() - local lines = { - "line 1", - } - - local matched_line = 1 - local context_size = 0 - local result = - ripgrep_parser.get_context_preview(lines, matched_line, context_size) - - assert.same(result, { - { line_number = 1, text = "line 1" }, - }) - end) - - it("can display context around the match at the end of the file", function() - local lines = { - [7] = "line 7", - [8] = "line 8", - [9] = "line 9", - [10] = "line 10", - } - - local matched_line = 9 - local context_size = 1 - local result = - ripgrep_parser.get_context_preview(lines, matched_line, context_size) - - assert.same(result, { - { line_number = 8, text = "line 8" }, - { line_number = 9, text = "line 9" }, - { line_number = 10, text = "line 10" }, - }) - end) - end) end)