From ecaaa79f45a2cd75dd8faf29a504e05113abcfc7 Mon Sep 17 00:00:00 2001 From: tris203 Date: Thu, 19 Dec 2024 22:09:21 +0000 Subject: [PATCH] feat!(simulation): v2 rewrite --- lua/precognition/horizontal_motions.lua | 303 ---------------- lua/precognition/init.lua | 30 +- lua/precognition/sim.lua | 37 ++ tests/minimal.lua | 13 + tests/precognition/e2e_spec.lua | 10 +- .../precognition/horizontal_motions_spec.lua | 327 ------------------ tests/precognition/inlay_hints_spec.lua | 4 +- tests/precognition/virtline_spec.lua | 61 ++-- 8 files changed, 103 insertions(+), 682 deletions(-) delete mode 100644 lua/precognition/horizontal_motions.lua create mode 100644 lua/precognition/sim.lua delete mode 100644 tests/precognition/horizontal_motions_spec.lua diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua deleted file mode 100644 index e4e858d..0000000 --- a/lua/precognition/horizontal_motions.lua +++ /dev/null @@ -1,303 +0,0 @@ -local M = {} - -local supportedBrackets = { - open = { "(", "[", "{" }, - middle = { nil, nil, nil }, - close = { ")", "]", "}" }, -} - ----@param str string ----@param _cursorcol integer ----@param _linelen integer ----@return Precognition.PlaceLoc -function M.line_start_non_whitespace(str, _cursorcol, _linelen) - return str:find("%S") or 0 -end - ----@param _str string ----@param _cursorcol integer ----@param linelen integer ----@return Precognition.PlaceLoc -function M.line_end(_str, _cursorcol, linelen) - return linelen or nil -end - ----@param str string ----@param cursorcol integer ----@param linelen integer ----@param big_word boolean ----@return Precognition.PlaceLoc -function M.next_word_boundary(str, cursorcol, linelen, big_word) - local utils = require("precognition.utils") - local cc = utils.char_classes - - local offset = cursorcol - local char = vim.fn.strcharpart(str, offset - 1, 1) - local c_class = utils.char_class(char, big_word) - - if c_class ~= cc.whitespace then - while utils.char_class(char, big_word) == c_class and offset <= linelen do - offset = offset + 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - end - end - - while utils.char_class(char, big_word) == cc.whitespace and offset <= linelen do - offset = offset + 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - end - if offset > linelen then - return 0 - end - - return offset -end - ----@param str string ----@param cursorcol integer ----@param linelen integer ----@param big_word boolean ----@return Precognition.PlaceLoc -function M.end_of_word(str, cursorcol, linelen, big_word) - if cursorcol >= linelen then - return 0 - end - local utils = require("precognition.utils") - local cc = utils.char_classes - - local offset = cursorcol - local char = vim.fn.strcharpart(str, offset - 1, 1) - local c_class = utils.char_class(char, big_word) - local next_char_class = utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1), big_word) - local rev_offset - - if - (c_class == cc.punctuation and next_char_class ~= cc.punctuation) - or (next_char_class == cc.punctuation and c_class ~= cc.punctuation) - then - offset = offset + 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - c_class = utils.char_class(char, big_word) - next_char_class = utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1), big_word) - end - - if c_class ~= cc.whitespace and next_char_class ~= cc.whitespace then - while utils.char_class(char, big_word) == c_class and offset <= linelen do - offset = offset + 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - end - end - - if c_class == cc.whitespace or next_char_class == cc.whitespace then - local next_word_start = M.next_word_boundary(str, cursorcol, linelen, big_word) - if next_word_start then - c_class = utils.char_class(vim.fn.strcharpart(str, next_word_start - 1, 1), big_word) - next_char_class = utils.char_class(vim.fn.strcharpart(str, (next_word_start - 1) + 1, 1), big_word) - if next_char_class == cc.whitespace then - --next word is single char - rev_offset = next_word_start - elseif c_class == cc.punctuation and next_char_class ~= cc.punctuation then - --next word starts with punctuation - rev_offset = next_word_start - else - rev_offset = M.end_of_word(str, next_word_start, linelen, big_word) - end - end - end - - if rev_offset and rev_offset <= 0 then - return 0 - end - - if rev_offset ~= nil then - --e should never be behind the cursor - if rev_offset < cursorcol then - return 0 - end - return rev_offset - end - return offset - 1 -end - ----@param str string ----@param cursorcol integer ----@param linelen integer ----@param big_word boolean ----@return Precognition.PlaceLoc -function M.prev_word_boundary(str, cursorcol, linelen, big_word) - local utils = require("precognition.utils") - local cc = utils.char_classes - - local offset = cursorcol - 1 - local char = vim.fn.strcharpart(str, offset - 1, 1) - local c_class = utils.char_class(char, big_word) - - if c_class == cc.whitespace then - while utils.char_class(char, big_word) == cc.whitespace and offset >= 0 do - offset = offset - 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - end - c_class = utils.char_class(char, big_word) - end - - while utils.char_class(char, big_word) == c_class and offset >= 0 do - offset = offset - 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - --if remaining string is whitespace, return 0 - local remaining = string.sub(str, offset) - if remaining:match("^%s*$") and #remaining > 0 then - return 0 - end - end - - if offset == nil or offset > linelen or offset < 0 then - return 0 - end - return offset + 1 -end - ----@param str string ----@param cursorcol integer ----@param linelen integer ----@return Precognition.PlaceLoc -function M.matching_bracket(str, cursorcol, linelen) - local under_cursor = vim.fn.strcharpart(str, cursorcol - 1, 1) - local offset = cursorcol - - if - not vim.tbl_contains(supportedBrackets.open, under_cursor) - and not vim.tbl_contains(supportedBrackets.close, under_cursor) - then - -- walk until we find a bracket - return 0 - end - local idxFound = false - local bracketIdx - if not idxFound then - for i, bracket in ipairs(supportedBrackets.open) do - if bracket == under_cursor then - bracketIdx = i - idxFound = true - break - end - end - end - - if not idxFound then - for i, bracket in ipairs(supportedBrackets.close) do - if bracket == under_cursor then - bracketIdx = i - idxFound = true - break - end - end - end - - if not idxFound then - return 0 - end - - local openBracket = supportedBrackets.open[bracketIdx] or "" - local closeBracket = supportedBrackets.close[bracketIdx] or "" - local middleBracket = supportedBrackets.middle[bracketIdx] or "" - - if under_cursor == openBracket then - local depth = 1 - offset = offset + 1 - while offset <= linelen do - local char = vim.fn.strcharpart(str, offset - 1, 1) - if char == openBracket then - depth = depth + 1 - end - if char == closeBracket or char == middleBracket then - depth = depth - 1 - if depth == 0 then - break - end - end - offset = offset + 1 - end - end - - if under_cursor == closeBracket then - local depth = 1 - offset = offset - 1 - while offset >= 0 do - local char = vim.fn.strcharpart(str, offset - 1, 1) - if char == closeBracket then - depth = depth + 1 - end - if char == openBracket or char == middleBracket then - depth = depth - 1 - if depth == 0 then - break - end - end - offset = offset - 1 - end - end - - if offset < 0 or offset > linelen then - return 0 - end - return offset -end - ----@param str string ----@param cursorcol integer ----@param linelen integer ----@return Precognition.PlaceLoc -function M.matching_comment(str, cursorcol, linelen) - local offset = cursorcol - local char = vim.fn.strcharpart(str, offset - 1, 1) - local next_char = vim.fn.strcharpart(str, (offset - 1) + 1, 1) - local prev_char = vim.fn.strcharpart(str, (offset - 1) - 1, 1) - - if (char == "/" and next_char == "*") or (prev_char == "/" and char == "*") then - offset = offset + 1 - while offset <= linelen do - char = vim.fn.strcharpart(str, offset - 1, 1) - next_char = vim.fn.strcharpart(str, offset, 1) - if char == "*" and next_char == "/" then - -- return the slash of the closing comment - return offset + 1 - end - offset = offset + 1 - end - end - - if (char == "*" and next_char == "/") or (prev_char == "*" and char == "/") then - offset = offset - 1 - while offset >= 0 do - char = vim.fn.strcharpart(str, offset - 1, 1) - next_char = vim.fn.strcharpart(str, offset, 1) - if char == "/" and next_char == "*" then - return offset - end - offset = offset - 1 - end - end - - return 0 -end - ----@param str string ----@param cursorcol integer ----@param _linelen integer ----@return function -function M.matching_pair(str, cursorcol, _linelen) - local char = vim.fn.strcharpart(str, cursorcol - 1, 1) - if char == "/" or char == "*" then - return M.matching_comment - end - - if vim.tbl_contains(supportedBrackets.open, char) or vim.tbl_contains(supportedBrackets.close, char) then - return M.matching_bracket - end - - return function() - return 0 - end -end - -return M diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 7b74bf7..560da09 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -243,29 +243,18 @@ local function display_marks() local line_len = vim.fn.strcharlen(cur_line) ---@type Precognition.ExtraPadding[] local extra_padding = {} - -- local after_cursor = vim.fn.strcharpart(cur_line, cursorcol + 1) - -- local before_cursor = vim.fn.strcharpart(cur_line, 0, cursorcol - 1) - -- local before_cursor_rev = string.reverse(before_cursor) - -- local under_cursor = vim.fn.strcharpart(cur_line, cursorcol - 1, 1) - - local hm = require("precognition.horizontal_motions") -- FIXME: Lua patterns don't play nice with utf-8, we need a better way to -- get char offsets for more complex motions. - -- + ---@type Precognition.VirtLine - local virtual_line_marks = { - Caret = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), - w = hm.next_word_boundary(cur_line, cursorcol, line_len, false), - e = hm.end_of_word(cur_line, cursorcol, line_len, false), - b = hm.prev_word_boundary(cur_line, cursorcol, line_len, false), - W = hm.next_word_boundary(cur_line, cursorcol, line_len, true), - E = hm.end_of_word(cur_line, cursorcol, line_len, true), - B = hm.prev_word_boundary(cur_line, cursorcol, line_len, true), - MatchingPair = hm.matching_pair(cur_line, cursorcol, line_len)(cur_line, cursorcol, line_len), - Dollar = hm.line_end(cur_line, cursorcol, line_len), - Zero = 1, - } + local virtual_line_marks = require("precognition.sim").check(cur_line, cursorcol) + ---return 0 for any hint that is not found in the simmed table + setmetatable(virtual_line_marks, { + __index = function() + return 0 + end, + }) if compat.inlay_hints_enabled({ bufnr = 0 }) then local inlays_hints = vim.lsp.inlay_hint.get({ @@ -490,6 +479,9 @@ local state = { ns = function() return ns end, + default_hint_config = function() + return defaultHintConfig + end, is_visible = function() return visible end, diff --git a/lua/precognition/sim.lua b/lua/precognition/sim.lua new file mode 100644 index 0000000..cfb34e7 --- /dev/null +++ b/lua/precognition/sim.lua @@ -0,0 +1,37 @@ +local mt = require("mini.test") + +local M = {} + +local function check_pos(string, col, default_config) + local result = {} + local locations = vim.tbl_keys(default_config) + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { " ", string, " " }) + vim.api.nvim_set_current_buf(buf) + for _, motion_name in ipairs(locations) do + local motion_key = default_config[motion_name].text + vim.fn.setcursorcharpos(2, col) + vim.api.nvim_feedkeys(motion_key, "x", true) + local cur_pos = vim.fn.getcursorcharpos(0) + if cur_pos[2] == 2 then + if motion_name == "MatchingPair" and cur_pos[3] ~= col then + result[motion_name] = cur_pos[3] + else + result[motion_name] = cur_pos[3] + end + end + end + + return result +end + +M.check = function(line, col) + local remote = mt.new_child_neovim() + remote.start() + + local remote_data = remote.lua_func(check_pos, line, col, require("precognition").default_hint_config) + remote.stop() + return remote_data +end + +return M diff --git a/tests/minimal.lua b/tests/minimal.lua index 2023951..89c1194 100644 --- a/tests/minimal.lua +++ b/tests/minimal.lua @@ -7,6 +7,19 @@ local function tempdir(plugin) return vim.loop.os_tmpdir() .. "/" .. plugin end +local minitest_dir = os.getenv("MINI_TEST_DIR") or tempdir("mini.test") +if vim.fn.isdirectory(minitest_dir) == 0 then + vim.fn.system({ + "git", + "clone", + "https://github.com/echasnovski/mini.test", + minitest_dir, + }) +end +vim.opt.rtp:append(".") +vim.opt.rtp:append(minitest_dir) +require("mini.test").setup() + local plenary_dir = os.getenv("PLENARY_DIR") or tempdir("plenary.nvim") if vim.fn.isdirectory(plenary_dir) == 0 then vim.fn.system({ diff --git a/tests/precognition/e2e_spec.lua b/tests/precognition/e2e_spec.lua index 6917d7d..9ffb35b 100644 --- a/tests/precognition/e2e_spec.lua +++ b/tests/precognition/e2e_spec.lua @@ -63,7 +63,7 @@ describe("e2e tests", function() end eq(vim.api.nvim_win_get_cursor(0)[1] - 1, extmarks[1]) - eq("b e w $", extmarks[3].virt_lines[1][1][1]) + eq("b% e w $", extmarks[3].virt_lines[1][1][1]) eq("PrecognitionHighlight", extmarks[3].virt_lines[1][1][2]) eq({ link = "Comment" }, vim.api.nvim_get_hl(0, { name = extmarks[3].virt_lines[1][1][2] })) @@ -75,7 +75,7 @@ describe("e2e tests", function() }) eq(vim.api.nvim_win_get_cursor(0)[1] - 1, extmarks[1]) - eq("b e w $", extmarks[3].virt_lines[1][1][1]) + eq("b % e w $", extmarks[3].virt_lines[1][1][1]) vim.api.nvim_win_set_cursor(0, { 2, 1 }) precognition.on_cursor_moved() @@ -101,7 +101,7 @@ describe("e2e tests", function() end eq(vim.api.nvim_win_get_cursor(0)[1] - 1, extmarks[1]) - eq("b e w", extmarks[3].virt_lines[1][1][1]) + eq("b% e w", extmarks[3].virt_lines[1][1][1]) vim.api.nvim_win_set_cursor(0, { 4, 1 }) precognition.on_cursor_moved() @@ -166,7 +166,7 @@ describe("e2e tests", function() end eq(vim.api.nvim_win_get_cursor(0)[1] - 1, extmarks[1]) - eq("b e w $", extmarks[3].virt_lines[1][1][1]) + eq("b% e w $", extmarks[3].virt_lines[1][1][1]) eq("PrecognitionHighlight", extmarks[3].virt_lines[1][1][2]) eq({ link = "Function" }, vim.api.nvim_get_hl(0, { name = extmarks[3].virt_lines[1][1][2] })) end) @@ -219,7 +219,7 @@ describe("e2e tests", function() end eq(vim.api.nvim_win_get_cursor(0)[1] - 1, extmarks[1]) - eq("b e w $", extmarks[3].virt_lines[1][1][1]) + eq("b% e w $", extmarks[3].virt_lines[1][1][1]) eq("PrecognitionHighlight", extmarks[3].virt_lines[1][1][2]) eq(customMark, vim.api.nvim_get_hl(0, { name = extmarks[3].virt_lines[1][1][2] })) end) diff --git a/tests/precognition/horizontal_motions_spec.lua b/tests/precognition/horizontal_motions_spec.lua deleted file mode 100644 index 7d0e0c9..0000000 --- a/tests/precognition/horizontal_motions_spec.lua +++ /dev/null @@ -1,327 +0,0 @@ -local hm = require("precognition.horizontal_motions") ----@diagnostic disable-next-line: undefined-field -local eq = assert.are.same - -describe("boundaries", function() - it("finds the next word boundary", function() - local str = "abc efg" - eq(5, hm.next_word_boundary(str, 1, #str, false)) - eq(5, hm.next_word_boundary(str, 2, #str, false)) - eq(5, hm.next_word_boundary(str, 3, #str, false)) - eq(5, hm.next_word_boundary(str, 4, #str, false)) - eq(0, hm.next_word_boundary(str, 5, #str, false)) - eq(0, hm.next_word_boundary(str, 6, #str, false)) - eq(0, hm.next_word_boundary(str, 7, #str, false)) - - str = "slighly more complex test" - eq(9, hm.next_word_boundary(str, 1, #str, false)) - eq(9, hm.next_word_boundary(str, 2, #str, false)) - eq(14, hm.next_word_boundary(str, 10, #str, false)) - eq(14, hm.next_word_boundary(str, 13, #str, false)) - eq(22, hm.next_word_boundary(str, 15, #str, false)) - eq(22, hm.next_word_boundary(str, 21, #str, false)) - - str = " myFunction(example, stuff)" - eq(5, hm.next_word_boundary(str, 1, #str, false)) - eq(5, hm.next_word_boundary(str, 2, #str, false)) - eq(5, hm.next_word_boundary(str, 3, #str, false)) - eq(15, hm.next_word_boundary(str, 5, #str, false)) - eq(16, hm.next_word_boundary(str, 15, #str, false)) - eq(23, hm.next_word_boundary(str, 16, #str, false)) - eq(25, hm.next_word_boundary(str, 23, #str, false)) - eq(25, hm.next_word_boundary(str, 24, #str, false)) - eq(30, hm.next_word_boundary(str, 25, #str, false)) - eq(0, hm.next_word_boundary(str, 30, #str, false)) - end) - - it("finds next big word boundary", function() - local str = "a big.word string" - eq(12, hm.next_word_boundary(str, 3, #str, true)) - eq(12, hm.next_word_boundary(str, 4, #str, true)) - end) - - it("can walk string with w", function() - local test_string = "abcdefg hijklmn opqrstu vwxyz" - local pos = hm.next_word_boundary(test_string, 1, #test_string, false) - if pos == 0 then - error("pos is 0") - end - eq("h", test_string:sub(pos, pos)) - if pos == 0 then - error("pos is 0") - end - pos = hm.next_word_boundary(test_string, pos, #test_string, false) - if pos == 0 then - error("pos is 0") - end - eq("o", test_string:sub(pos, pos)) - pos = hm.next_word_boundary(test_string, pos, #test_string, false) - if pos == 0 then - error("pos is 0") - end - eq("v", test_string:sub(pos, pos)) - pos = hm.next_word_boundary(test_string, pos, #test_string, false) - eq(0, pos) - end) - - describe("previous word boundary", function() - it("finds the previous word boundary", function() - local str = "abc efg" - eq(0, hm.prev_word_boundary(str, 1, #str, false)) - eq(1, hm.prev_word_boundary(str, 2, #str, false)) - eq(1, hm.prev_word_boundary(str, 3, #str, false)) - eq(1, hm.prev_word_boundary(str, 4, #str, false)) - eq(1, hm.prev_word_boundary(str, 5, #str, false)) - eq(5, hm.prev_word_boundary(str, 6, #str, false)) - eq(5, hm.prev_word_boundary(str, 7, #str, false)) - - str = "slighly more complex test" - eq(9, hm.prev_word_boundary(str, 10, #str, false)) - eq(9, hm.prev_word_boundary(str, 11, #str, false)) - eq(14, hm.prev_word_boundary(str, 15, #str, false)) - eq(14, hm.prev_word_boundary(str, 16, #str, false)) - eq(22, hm.prev_word_boundary(str, 23, #str, false)) - eq(22, hm.prev_word_boundary(str, 24, #str, false)) - eq(22, hm.prev_word_boundary(str, 25, #str, false)) - eq(0, hm.prev_word_boundary(str, 1, #str, false)) - - str = " myFunction(example, stuff)" - eq(0, hm.prev_word_boundary(str, 1, #str, false)) - eq(0, hm.prev_word_boundary(str, 2, #str, false)) - eq(0, hm.prev_word_boundary(str, 3, #str, false)) - eq(0, hm.prev_word_boundary(str, 4, #str, false)) - eq(0, hm.prev_word_boundary(str, 5, #str, false)) - eq(5, hm.prev_word_boundary(str, 6, #str, false)) - eq(5, hm.prev_word_boundary(str, 15, #str, false)) - eq(15, hm.prev_word_boundary(str, 16, #str, false)) - eq(16, hm.prev_word_boundary(str, 17, #str, false)) - eq(16, hm.prev_word_boundary(str, 18, #str, false)) - eq(16, hm.prev_word_boundary(str, 19, #str, false)) - eq(23, hm.prev_word_boundary(str, 25, #str, false)) - eq(25, hm.prev_word_boundary(str, 26, #str, false)) - eq(25, hm.prev_word_boundary(str, 27, #str, false)) - eq(25, hm.prev_word_boundary(str, 28, #str, false)) - eq(25, hm.prev_word_boundary(str, 29, #str, false)) - eq(25, hm.prev_word_boundary(str, 30, #str, false)) - end) - - it("finds previous big word boundary", function() - local str = "a big.word string" - eq(3, hm.prev_word_boundary(str, 10, #str, true)) - eq(3, hm.prev_word_boundary(str, 10, #str, true)) - - str = "big.word" - eq(1, hm.prev_word_boundary(str, 5, #str, true)) - end) - - it("can walk string with b", function() - local test_string = "abcdefg hijklmn opqrstu vwxyz" - local pos = hm.prev_word_boundary(test_string, 29, #test_string, false) - if pos == 0 then - error("pos is 0") - end - eq("v", test_string:sub(pos, pos)) - pos = hm.prev_word_boundary(test_string, pos, #test_string, false) - if pos == 0 then - error("pos is 0") - end - eq("o", test_string:sub(pos, pos)) - pos = hm.prev_word_boundary(test_string, pos, #test_string, false) - if pos == 0 then - error("pos is 0") - end - eq("h", test_string:sub(pos, pos)) - pos = hm.prev_word_boundary(test_string, pos, #test_string, false) - eq(1, pos) - end) - end) - - describe("end of current word", function() - it("finds the end of words", function() - local str = "abc efg" - eq(3, hm.end_of_word(str, 1, #str, false)) - eq(3, hm.end_of_word(str, 2, #str, false)) - eq(7, hm.end_of_word(str, 3, #str, false)) - - str = "slighly more complex test" - eq(7, hm.end_of_word(str, 1, #str, false)) - eq(7, hm.end_of_word(str, 2, #str, false)) - eq(12, hm.end_of_word(str, 10, #str, false)) - eq(20, hm.end_of_word(str, 13, #str, false)) - eq(20, hm.end_of_word(str, 15, #str, false)) - eq(25, hm.end_of_word(str, 21, #str, false)) - - str = " myFunction(example, stuff)" - eq(14, hm.end_of_word(str, 1, #str, false)) - eq(14, hm.end_of_word(str, 2, #str, false)) - eq(14, hm.end_of_word(str, 3, #str, false)) - eq(14, hm.end_of_word(str, 5, #str, false)) - eq(15, hm.end_of_word(str, 14, #str, false)) - eq(22, hm.end_of_word(str, 15, #str, false)) - eq(22, hm.end_of_word(str, 16, #str, false)) - eq(29, hm.end_of_word(str, 23, #str, false)) - eq(29, hm.end_of_word(str, 24, #str, false)) - eq(29, hm.end_of_word(str, 25, #str, false)) - eq(30, hm.end_of_word(str, 29, #str, false)) - eq(0, hm.end_of_word(str, 30, #str, false)) - end) - - it("finds the end of the current big word", function() - local str = "a big.word string" - eq(10, hm.end_of_word(str, 3, #str, true)) - end) - end) -end) - -describe("matching_pair returns the correction function", function() - it("returns the correct function for the given character", function() - local test_string = "()[]{}/*" - eq(hm.matching_pair(test_string, 1, #test_string), hm.matching_bracket) - eq(hm.matching_pair(test_string, 2, #test_string), hm.matching_bracket) - eq(hm.matching_pair(test_string, 3, #test_string), hm.matching_bracket) - eq(hm.matching_pair(test_string, 4, #test_string), hm.matching_bracket) - eq(hm.matching_pair(test_string, 5, #test_string), hm.matching_bracket) - eq(hm.matching_pair(test_string, 6, #test_string), hm.matching_bracket) - eq(hm.matching_pair(test_string, 7, #test_string), hm.matching_comment) - eq(hm.matching_pair(test_string, 8, #test_string), hm.matching_comment) - end) - - it("returns a function that returns 0 for other characters", function() - local test_string = "abcdefghijklmnopqrstuvwxyz!@#$%^&*_+-=,.<>?|\\~`" - for i = 1, #test_string do - local func = hm.matching_pair(test_string, i, #test_string) - eq(0, func(test_string, i, #test_string)) - end - end) -end) - -describe("matching brackets", function() - it("if cursor is over a bracket it can find the pair", function() - local str = "abc (efg)" - eq(9, hm.matching_bracket(str, 5, #str)) - eq(0, hm.matching_bracket(str, 6, #str)) - eq(0, hm.matching_bracket(str, 7, #str)) - eq(0, hm.matching_bracket(str, 8, #str)) - eq(5, hm.matching_bracket(str, 9, #str)) - end) - - it("if cursor is over a square bracket it can find the pair", function() - local str = "abc [efg]" - eq(9, hm.matching_bracket(str, 5, #str)) - eq(0, hm.matching_bracket(str, 6, #str)) - eq(0, hm.matching_bracket(str, 7, #str)) - eq(0, hm.matching_bracket(str, 8, #str)) - eq(5, hm.matching_bracket(str, 9, #str)) - end) - - it("if cursor is over a curly bracket it can find the pair", function() - local str = "abc {efg}" - eq(9, hm.matching_bracket(str, 5, #str)) - eq(0, hm.matching_bracket(str, 6, #str)) - eq(0, hm.matching_bracket(str, 7, #str)) - eq(0, hm.matching_bracket(str, 8, #str)) - eq(5, hm.matching_bracket(str, 9, #str)) - end) - - it("nested brackets find the correct pair", function() - local str = "abc (efg [hij] klm)" - eq(19, hm.matching_bracket(str, 5, #str)) - eq(0, hm.matching_bracket(str, 6, #str)) - eq(14, hm.matching_bracket(str, 10, #str)) - eq(10, hm.matching_bracket(str, 14, #str)) - eq(0, hm.matching_bracket(str, 15, #str)) - eq(5, hm.matching_bracket(str, 19, #str)) - end) - - it("nested brackets of the same type find the correct pair", function() - local str = "abc (efg (hij) klm)" - eq(19, hm.matching_bracket(str, 5, #str)) - eq(0, hm.matching_bracket(str, 6, #str)) - eq(14, hm.matching_bracket(str, 10, #str)) - eq(10, hm.matching_bracket(str, 14, #str)) - eq(0, hm.matching_bracket(str, 15, #str)) - eq(5, hm.matching_bracket(str, 19, #str)) - end) - - it("if cursor is over an unclosed bracket it returns 0", function() - local str = "abc (efg" - eq(0, hm.matching_bracket(str, 5, #str)) - eq(0, hm.matching_bracket(str, 5, #str)) - eq(0, hm.matching_bracket(str, 5, #str)) - end) -end) - -describe("matching comments", function() - it("if cursor is over a comment it can find the pair", function() - local str = "abc /*efg*/" - eq(11, hm.matching_comment(str, 5, #str)) - eq(11, hm.matching_comment(str, 6, #str)) - eq(0, hm.matching_comment(str, 7, #str)) - eq(5, hm.matching_comment(str, 10, #str)) - eq(5, hm.matching_comment(str, 11, #str)) - end) - - it("if cursor is over an unclosed comment it returns 0", function() - local str = "abc /*efg" - eq(0, hm.matching_comment(str, 5, #str)) - eq(0, hm.matching_comment(str, 6, #str)) - end) -end) - -describe("edge case", function() - it("can handle empty strings", function() - eq(0, hm.next_word_boundary("", 1, 0, false)) - eq(0, hm.prev_word_boundary("", 1, 0, false)) - eq(0, hm.end_of_word("", 1, 0, false)) - end) - - it("can handle strings with only whitespace", function() - eq(0, hm.next_word_boundary(" ", 1, 1, false)) - eq(0, hm.prev_word_boundary(" ", 1, 1, false)) - eq(0, hm.end_of_word(" ", 1, 1, false)) - end) - - it("can handle strings with special characters in the middle", function() - local str = "vim.keymap.set('n', 't;', ':Test')" - eq(5, hm.next_word_boundary(str, 4, #str, false)) - eq(1, hm.prev_word_boundary(str, 4, #str, false)) - eq(10, hm.end_of_word(str, 4, #str, false)) - end) - - it("can handle strings with multiple consecutive special characters", function() - local str = "this || that" - eq(9, hm.next_word_boundary(str, 6, #str, false)) - eq(1, hm.prev_word_boundary(str, 6, #str, false)) - eq(7, hm.end_of_word(str, 6, #str, false)) - end) - - it("strings with spaces at the end", function() - local str = "there is a space " - eq(0, hm.end_of_word(str, 16, #str, false)) - end) - - it("single character next word ends", function() - local str = "show_something = true," - eq(14, hm.end_of_word(str, 1, #str, false)) - eq(16, hm.end_of_word(str, 14, #str, false)) - eq(16, hm.end_of_word(str, 15, #str, false)) - eq(22, hm.end_of_word(str, 21, #str, false)) - end) - - it("multibyte characters", function() - local str = "# 💭👀precognition.nvim" - local len = vim.fn.strcharlen(str) - eq(3, hm.next_word_boundary(str, 1, len, false)) - eq(17, hm.prev_word_boundary(str, 18, len, false)) - eq(3, hm.prev_word_boundary(str, 18, len, true)) - eq(3, hm.prev_word_boundary(str, 5, len, false)) - end) - - it("quoted strings", function() - local str = 'this = "that"' - eq(8, hm.end_of_word(str, 6, #str, false)) - - str = 'b = "^", c = 2 },' - eq(8, hm.end_of_word(str, 3, #str, false)) - end) -end) diff --git a/tests/precognition/inlay_hints_spec.lua b/tests/precognition/inlay_hints_spec.lua index cecf716..3af28be 100644 --- a/tests/precognition/inlay_hints_spec.lua +++ b/tests/precognition/inlay_hints_spec.lua @@ -134,7 +134,7 @@ describe("lsp based tests", function() details = true, }) - eq("b e w $", extmarks[3].virt_lines[1][1][1]) + eq("b% e w $", extmarks[3].virt_lines[1][1][1]) vim.lsp.inlay_hint.enable(true, { bufnr = buf }) -- NOTE:The test LSP replies with an inlay hint, that suggest "foo" as line 1, position 4 @@ -146,7 +146,7 @@ describe("lsp based tests", function() details = true, }) - eq("b e w $", extmarks[3].virt_lines[1][1][1]) + eq("b% e w $", extmarks[3].virt_lines[1][1][1]) end) after_each(function() diff --git a/tests/precognition/virtline_spec.lua b/tests/precognition/virtline_spec.lua index 26efc7d..75e5253 100644 --- a/tests/precognition/virtline_spec.lua +++ b/tests/precognition/virtline_spec.lua @@ -1,5 +1,4 @@ local precognition = require("precognition") -local hm = require("precognition.horizontal_motions") local utils = require("precognition.utils") ---@diagnostic disable-next-line: undefined-field local eq = assert.are.same @@ -79,12 +78,14 @@ describe("Build Virtual Line", function() local cur_line = line:gsub("\t", string.rep(" ", tab_width)) local line_len = vim.fn.strcharlen(cur_line) + local sim = require("precognition.sim").check(cur_line, cursorcol) + local virt_line = precognition.build_virt_line({ - w = hm.next_word_boundary(cur_line, cursorcol, line_len, false), - e = hm.end_of_word(cur_line, cursorcol, line_len, false), - b = hm.prev_word_boundary(cur_line, cursorcol, line_len, false), - Caret = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), - Dollar = hm.line_end(cur_line, cursorcol, line_len), + w = sim["w"], + e = sim["e"], + b = sim["b"], + Caret = sim["Caret"], + Dollar = sim["Dollar"], }, line_len, {}) eq("b e w $", virt_line[1][1]) @@ -98,12 +99,14 @@ describe("Build Virtual Line", function() local cur_line = line:gsub("\t", string.rep(" ", tab_width)) local line_len = vim.fn.strcharlen(cur_line) + local sim = require("precognition.sim").check(cur_line, cursorcol) + local virt_line = precognition.build_virt_line({ - w = hm.next_word_boundary(cur_line, cursorcol, line_len, false), - e = hm.end_of_word(cur_line, cursorcol, line_len, false), - b = hm.prev_word_boundary(cur_line, cursorcol, line_len, false), - Caret = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), - Dollar = hm.line_end(cur_line, cursorcol, line_len), + w = sim["w"], + e = sim["e"], + b = sim["b"], + Caret = sim["Caret"], + Dollar = sim["Dollar"], }, line_len, {}) eq(" ^ e w $", virt_line[1][1]) @@ -118,12 +121,14 @@ describe("Build Virtual Line", function() local line_len = vim.fn.strcharlen(cur_line) local extra_padding = { { start = 4, length = 4 } } + local sim = require("precognition.sim").check(cur_line, cursorcol) + local virt_line = precognition.build_virt_line({ - w = hm.next_word_boundary(cur_line, cursorcol, line_len, false), - e = hm.end_of_word(cur_line, cursorcol, line_len, false), - b = hm.prev_word_boundary(cur_line, cursorcol, line_len, false), - Caret = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), - Dollar = hm.line_end(cur_line, cursorcol, line_len), + w = sim["w"], + e = sim["e"], + b = sim["b"], + Caret = sim["Caret"], + Dollar = sim["Dollar"], }, line_len, extra_padding) local total_added = 0 @@ -143,12 +148,14 @@ describe("Build Virtual Line", function() local line_len = vim.fn.strcharlen(cur_line) local extra_padding = { { start = 4, length = 4 }, { start = 10, length = 5 } } + local sim = require("precognition.sim").check(cur_line, cursorcol) + local virt_line = precognition.build_virt_line({ - w = hm.next_word_boundary(cur_line, cursorcol, line_len, false), - e = hm.end_of_word(cur_line, cursorcol, line_len, false), - b = hm.prev_word_boundary(cur_line, cursorcol, line_len, false), - Caret = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), - Dollar = hm.line_end(cur_line, cursorcol, line_len), + w = sim["w"], + e = sim["e"], + b = sim["b"], + Caret = sim["Caret"], + Dollar = sim["Dollar"], }, line_len, extra_padding) local total_added = 0 @@ -170,12 +177,14 @@ describe("Build Virtual Line", function() utils.add_multibyte_padding(cur_line, extra_padding, line_len) + local sim = require("precognition.sim").check(cur_line, cursorcol) + local virt_line = precognition.build_virt_line({ - w = hm.next_word_boundary(cur_line, cursorcol, line_len, false), - e = hm.end_of_word(cur_line, cursorcol, line_len, false), - b = hm.prev_word_boundary(cur_line, cursorcol, line_len, false), - Caret = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), - Dollar = hm.line_end(cur_line, cursorcol, line_len), + w = sim["w"], + e = sim["e"], + b = sim["b"], + Caret = sim["Caret"], + Dollar = sim["Dollar"], }, line_len, extra_padding) eq("^ b e", virt_line[1][1])