From b4070042bfed5f873e4aa870df67e703e5443b7d Mon Sep 17 00:00:00 2001 From: Mika Vilpas Date: Mon, 4 Nov 2024 20:37:43 +0200 Subject: [PATCH] feat: display context for matches as documentation --- .busted | 13 +++++ .gitignore | 3 ++ .luarc.json | 9 ++++ blink-cmp-rg.nvim-scm-1.rockspec | 24 +++++++++ justfile | 48 +++++++++++++++++ lua/blink-cmp-rg/init.lua | 46 ++++------------- lua/blink-cmp-rg/ripgrep_parser.lua | 63 +++++++++++++++++++++++ selene.toml | 1 + spec/blink-cmp-rg/rg-output.jsonl | 22 ++++++++ spec/blink-cmp-rg/ripgrep_parser_spec.lua | 25 +++++++++ vim.toml | 55 ++++++++++++++++++++ 11 files changed, 274 insertions(+), 35 deletions(-) create mode 100644 .busted create mode 100644 .luarc.json create mode 100644 blink-cmp-rg.nvim-scm-1.rockspec create mode 100644 justfile create mode 100644 lua/blink-cmp-rg/ripgrep_parser.lua create mode 100644 selene.toml create mode 100644 spec/blink-cmp-rg/rg-output.jsonl create mode 100644 spec/blink-cmp-rg/ripgrep_parser_spec.lua create mode 100644 vim.toml diff --git a/.busted b/.busted new file mode 100644 index 0000000..834fa17 --- /dev/null +++ b/.busted @@ -0,0 +1,13 @@ +return { + _all = { + coverage = false, + lpath = 'lua/?.lua;lua/?/init.lua', + lua = '~/.luarocks/bin/nlua', + }, + default = { + verbose = true, + }, + tests = { + verbose = true, + }, +} diff --git a/.gitignore b/.gitignore index a699df0..3973366 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ integration-tests/test-environment/.repro integration-tests/test-environment/testdirs/ integration-tests/cypress/screenshots/ integration-tests/dist +.luarocks +lua_modules +luarocks diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..68da2f2 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,9 @@ +{ + "runtime": { + "version": "LuaJIT", + "pathStrict": true + }, + "type": { + "checkTableShape": true + } +} diff --git a/blink-cmp-rg.nvim-scm-1.rockspec b/blink-cmp-rg.nvim-scm-1.rockspec new file mode 100644 index 0000000..7d64143 --- /dev/null +++ b/blink-cmp-rg.nvim-scm-1.rockspec @@ -0,0 +1,24 @@ +---@diagnostic disable: lowercase-global +rockspec_format = "3.0" +package = "blink-cmp-rg.nvim" +version = "scm-1" +source = { + url = "git+https://github.com/mikavilpas/blink-cmp-rg.nvim", +} +dependencies = { + -- Add runtime dependencies here + -- e.g. "plenary.nvim", + -- blink is not available on luarocks (yet) + -- "blink.nvim", +} +test_dependencies = { + "nlua", +} +build = { + type = "builtin", + copy_directories = { + -- Add runtimepath directories, like + -- 'plugin', 'ftplugin', 'doc' + -- here. DO NOT add 'lua' or 'lib'. + }, +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..6c185aa --- /dev/null +++ b/justfile @@ -0,0 +1,48 @@ +set unstable := true + +# allow `just --fmt` + +COLOR_RESET := '\033[0m' +COLOR_GREEN := '\033[1;32m' +COLOR_BLUE_ := '\033[1;34m' +COLOR_YELLO := '\033[1;33m' +COLOR_WHITE := '\033[1;37m' + +default: help + +@help: + just --list + +# Build the project +@build: + echo "Building project..." + luarocks init --no-gitignore + luarocks install busted 2.2.0-1 + + just help + +# Check the code for lint errors +lint: + selene ./lua/ ./spec/ + + @if grep -r -e "#focus" --include \*.lua ./spec/; then \ + echo "\n"; \ + echo "Error: {{ COLOR_GREEN }}#focus{{ COLOR_RESET }} tags found in the codebase.\n"; \ + echo "Please remove them to prevent issues with not accidentally running all tests."; \ + exit 1; \ + fi + +# Run all tests +test: + luarocks test --local + +# Run only the tests marked with #focus somewhere in the test name +test-focus: + luarocks test --local -- --filter=focus + +# Reformat all code +format: + stylua lua/ spec/ integration-tests/ + +# Check the code for errors (lint + test + format) +check: lint test format diff --git a/lua/blink-cmp-rg/init.lua b/lua/blink-cmp-rg/init.lua index c4e2510..b7e8cea 100644 --- a/lua/blink-cmp-rg/init.lua +++ b/lua/blink-cmp-rg/init.lua @@ -54,45 +54,21 @@ function RgSource:get_completions(context, resolve) end local lines = vim.split(result.stdout, "\n") - local cwd = vim.uv.cwd() + local cwd = vim.uv.cwd() or "" + + local parsed = require("blink-cmp-rg.ripgrep_parser").parse(lines, cwd) ---@type table local items = {} - for _, line in ipairs(lines) do - local ok, item = pcall(vim.json.decode, line) - item = ok and item or {} - - if item.type == "match" then - assert( - item.data.lines.text, - "ripgrep output missing item.data.lines.text" - ) - assert( - item.data.path.text, - "ripgrep output missing item.data.path.text" - ) - ---@type string - local path = item.data.path.text - if path:sub(1, #cwd) == cwd then - path = path:sub(#cwd + 2) - end - - ---@type string[] - local documentation = { - item.data.lines.text, - " ", -- empty lines seem to do nothing, so just have something - path, + for _, file in pairs(parsed.files) do + for _, match in ipairs(file.submatches) do + ---@diagnostic disable-next-line: missing-fields + items[match.match.text] = { + documentation = table.concat(file.lines, "\n"), + source_id = "blink-cmp-rg", + label = match.match.text .. " (rg)", + insertText = match.match.text, } - - for _, submatch in ipairs(item.data.submatches) do - ---@diagnostic disable-next-line: missing-fields - items[submatch.match.text] = { - documentation = table.concat(documentation, "\n"), - source_id = "blink-cmp-rg", - label = submatch.match.text .. " (rg)", - insertText = submatch.match.text, - } - end end end diff --git a/lua/blink-cmp-rg/ripgrep_parser.lua b/lua/blink-cmp-rg/ripgrep_parser.lua new file mode 100644 index 0000000..a1b72f2 --- /dev/null +++ b/lua/blink-cmp-rg/ripgrep_parser.lua @@ -0,0 +1,63 @@ +local M = {} + +---@class(exact) RipgrepOutput +---@field files table + +---@class RipgrepFile +---@field lines string[] the context preview for all the matches +---@field submatches RipgrepSubmatch[] the matches + +---@class RipgrepSubmatch +---@field start number the start column of the match +---@field end_ number the end column of the match +---@field match {text: string} the matched text + +---@param ripgrep_output string[] ripgrep output in jsonl format +---@param cwd string the current working directory +function M.parse(ripgrep_output, cwd) + ---@type RipgrepOutput + local output = { files = {} } + + for _, line in ipairs(ripgrep_output) do + local ok, json = pcall(vim.json.decode, line) + if ok then + if json.type == "begin" then + ---@type string + local filename = json.data.path.text + + output.files[filename] = { lines = {}, submatches = {} } + elseif json.type == "context" then + ---@type string + local filename = json.data.path.text + local data = output.files[filename] + + data.lines[#data.lines + 1] = json.data.lines.text + elseif json.type == "match" then + ---@type string + local filename = json.data.path.text + local data = output.files[filename] + + data.lines[#data.lines + 1] = json.data.lines.text + + data.submatches[#data.submatches + 1] = { + start = json.data.submatches[1].start, + end_ = json.data.submatches[1]["end"], + match = { text = json.data.submatches[1].match.text }, + } + elseif json.type == "end" then + ---@type string + local filename = json.data.path.text + local data = output.files[filename] + + if filename:sub(1, #cwd) == cwd then + filename = filename:sub(#cwd + 2) + end + data.lines[#data.lines + 1] = " " + data.lines[#data.lines + 1] = "> " .. filename + end + end + end + return output +end + +return M diff --git a/selene.toml b/selene.toml new file mode 100644 index 0000000..7312a91 --- /dev/null +++ b/selene.toml @@ -0,0 +1 @@ +std="vim" diff --git a/spec/blink-cmp-rg/rg-output.jsonl b/spec/blink-cmp-rg/rg-output.jsonl new file mode 100644 index 0000000..af3548f --- /dev/null +++ b/spec/blink-cmp-rg/rg-output.jsonl @@ -0,0 +1,22 @@ +{"type":"begin","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"}}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" //\n"},"line_number":13,"absolute_offset":462,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" // If the plugin works, this text should show up as a suggestion.\n"},"line_number":14,"absolute_offset":471,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" cy.typeIntoTerminal(\n"},"line_number":15,"absolute_offset":543,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" // NOTE: need to break it into parts so that this test file itself does\n"},"line_number":16,"absolute_offset":570,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" // not match the search :)\n"},"line_number":17,"absolute_offset":650,"submatches":[]}} +{"type":"match","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" \"hip\" + \"234\",\n"},"line_number":18,"absolute_offset":685,"submatches":[{"match":{"text":"234"},"start":17,"end":20}]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" )\n"},"line_number":19,"absolute_offset":708,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":"\n"},"line_number":20,"absolute_offset":716,"submatches":[]}} +{"type":"match","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" cy.contains(\"Hippopotamus\" + \"234 (rg)\")\n"},"line_number":21,"absolute_offset":717,"submatches":[{"match":{"text":"234"},"start":36,"end":39}]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":"\n"},"line_number":22,"absolute_offset":764,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" // should show documentation with more details about the match\n"},"line_number":23,"absolute_offset":765,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" //\n"},"line_number":24,"absolute_offset":834,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" // should show the text for the matched line\n"},"line_number":25,"absolute_offset":843,"submatches":[]}} +{"type":"match","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" cy.contains(\"Hippopotamus\" + \"234 was my previous password\")\n"},"line_number":26,"absolute_offset":894,"submatches":[{"match":{"text":"234"},"start":36,"end":39}]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" // should show the file name\n"},"line_number":27,"absolute_offset":961,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" cy.contains(dir.contents[\"other-file.txt\"].name)\n"},"line_number":28,"absolute_offset":996,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" })\n"},"line_number":29,"absolute_offset":1051,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":" })\n"},"line_number":30,"absolute_offset":1058,"submatches":[]}} +{"type":"context","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"lines":{"text":"})\n"},"line_number":31,"absolute_offset":1063,"submatches":[]}} +{"type":"end","data":{"path":{"text":"integration-tests/cypress/e2e/cmp-rg/basic_spec.cy.ts"},"binary_offset":null,"stats":{"elapsed":{"secs":0,"nanos":72959,"human":"0.000073s"},"searches":1,"searches_with_match":1,"bytes_searched":1066,"bytes_printed":4189,"matched_lines":3,"matches":3}}} +{"data":{"elapsed_total":{"human":"0.007127s","nanos":7126833,"secs":0},"stats":{"bytes_printed":4189,"bytes_searched":1066,"elapsed":{"human":"0.000073s","nanos":72959,"secs":0},"matched_lines":3,"matches":3,"searches":1,"searches_with_match":1}},"type":"summary"} diff --git a/spec/blink-cmp-rg/ripgrep_parser_spec.lua b/spec/blink-cmp-rg/ripgrep_parser_spec.lua new file mode 100644 index 0000000..ab6ecde --- /dev/null +++ b/spec/blink-cmp-rg/ripgrep_parser_spec.lua @@ -0,0 +1,25 @@ +local ripgrep_parser = require("blink-cmp-rg.ripgrep_parser") +local assert = require("luassert") + +describe("ripgrep_parser", function() + local ripgrep_output_lines = + vim.fn.readfile("spec/blink-cmp-rg/rg-output.jsonl") + + it("can parse according to the expected schema", function() + 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 > 19) + assert.same(#result.files[filename].submatches, 3) + + for _, submatch in ipairs(result.files[filename].submatches) do + assert.is_not_nil(submatch.start) + assert.is_not_nil(submatch.end_) + assert.is_not_nil(submatch.match.text) + end + end) + + -- TODO test that the cwd is stripped from the filename +end) diff --git a/vim.toml b/vim.toml new file mode 100644 index 0000000..77b3b7f --- /dev/null +++ b/vim.toml @@ -0,0 +1,55 @@ +[selene] +base = "lua51" +name = "vim" + +[vim] +any = true + +[[describe.args]] +type = "string" +[[describe.args]] +type = "function" + +[[it.args]] +type = "string" +[[it.args]] +type = "function" + +[[before_each.args]] +type = "function" +[[after_each.args]] +type = "function" + +[assert.is_not] +any = true + +[assert.matches] +any = true + +[assert.has_error] +any = true + +[[assert.equals.args]] +type = "any" +[[assert.equals.args]] +type = "any" +[[assert.equals.args]] +type = "any" +required = false + +[[assert.same.args]] +type = "any" +[[assert.same.args]] +type = "any" + +[[assert.truthy.args]] +type = "any" + +[[assert.falsy.args]] +type = "any" + +[[assert.spy.args]] +type = "any" + +[[assert.stub.args]] +type = "any"