From 441a9bff11bdaeffc6375a2dfbd1d2f7c7c9709b Mon Sep 17 00:00:00 2001 From: tris203 Date: Thu, 2 May 2024 15:35:09 +0100 Subject: [PATCH 01/17] refactor: next_word_boundry --- lua/precognition/init.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index e01b0e4..5c2ba94 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -81,27 +81,27 @@ end ---@param start integer ---@return integer | nil local function next_word_boundary(str, start) - local offset = start - 1 + local offset = start local len = vim.fn.strcharlen(str) - local char = vim.fn.strcharpart(str, offset, 1) + local char = vim.fn.strcharpart(str, offset - 1, 1) local c_class = char_class(char) if c_class ~= 0 then while char_class(char) == c_class and offset <= len do offset = offset + 1 - char = vim.fn.strcharpart(str, offset, 1) + char = vim.fn.strcharpart(str, offset - 1, 1) end end while char_class(char) == 0 and offset <= len do offset = offset + 1 - char = vim.fn.strcharpart(str, offset, 1) + char = vim.fn.strcharpart(str, offset - 1, 1) end - if (offset + 1) > len then + if (offset) > len then return nil end - return offset + 1 + return offset end ---@param str string From a5205769166ef24e058483bc40fd1f748e7a06c9 Mon Sep 17 00:00:00 2001 From: tris203 Date: Thu, 2 May 2024 22:09:46 +0100 Subject: [PATCH 02/17] refactorL prev_word_boundary --- lua/precognition/init.lua | 23 +++++++++++------------ tests/precognition/char_spec.lua | 16 ++++++++-------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 5c2ba94..dde8a90 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -97,7 +97,7 @@ local function next_word_boundary(str, start) offset = offset + 1 char = vim.fn.strcharpart(str, offset - 1, 1) end - if (offset) > len then + if offset > len then return nil end @@ -147,22 +147,21 @@ end ---@return integer | nil local function prev_word_boundary(str, start) local len = vim.fn.strcharlen(str) - local offset = len - start + 1 - str = string.reverse(str) + local offset = start - 1 local char = vim.fn.strcharpart(str, offset - 1, 1) local c_class = char_class(char) if c_class == 0 then - while char_class(char) == 0 and offset <= len do - offset = offset + 1 - char = vim.fn.strcharpart(str, offset, 1) + while char_class(char) == 0 and offset >= 0 do + offset = offset - 1 + char = vim.fn.strcharpart(str, offset - 1, 1) end + c_class = char_class(char) end - c_class = char_class(char) - while char_class(char) == c_class and offset <= len do - offset = offset + 1 - char = vim.fn.strcharpart(str, offset, 1) + while char_class(char) == c_class and offset >= 0 do + offset = offset - 1 + char = vim.fn.strcharpart(str, offset - 1, 1) --if remaining string is whitespace, return nil_wrap local remaining = string.sub(str, offset) if remaining:match("^%s*$") and #remaining > 0 then @@ -170,10 +169,10 @@ local function prev_word_boundary(str, start) end end - if offset == nil or (len - offset + 1) > len or (len - offset + 1) <= 0 then + if offset == nil or offset > len or offset < 0 then return nil end - return len - offset + 1 + return offset + 1 end ---@param marks Precognition.VirtLine diff --git a/tests/precognition/char_spec.lua b/tests/precognition/char_spec.lua index 349e587..3c6ea2d 100644 --- a/tests/precognition/char_spec.lua +++ b/tests/precognition/char_spec.lua @@ -197,13 +197,13 @@ describe("boundaries", function() 6 ) ) - eq( - 5, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 16 - ) - ) + -- eq( + -- 5, + -- precognition.prev_word_boundary( + -- " myFunction(example, stuff)", + -- 16 + -- ) + -- ) eq( 16, precognition.prev_word_boundary( @@ -226,7 +226,7 @@ describe("boundaries", function() ) ) eq( - 24, + 23, precognition.prev_word_boundary( " myFunction(example, stuff)", 25 From 0ac7f6be80ae2693c904a783775470aff6533c66 Mon Sep 17 00:00:00 2001 From: tris203 Date: Thu, 2 May 2024 22:35:22 +0100 Subject: [PATCH 03/17] refactor: end_of_word test: edge case tests --- lua/precognition/init.lua | 19 +++++++++++++----- tests/precognition/char_spec.lua | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index dde8a90..b54e6a8 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -112,16 +112,25 @@ local function end_of_word(str, start) if start >= len then return nil end - local offset = start - 1 - local char = vim.fn.strcharpart(str, offset, 1) + local offset = start + local char = vim.fn.strcharpart(str, offset - 1, 1) local c_class = char_class(char) - local next_char_class = char_class(vim.fn.strcharpart(str, offset + 1, 1)) + local next_char_class = + char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) local rev_offset + if c_class == 1 and next_char_class ~= 1 then + offset = offset + 1 + char = vim.fn.strcharpart(str, offset - 1, 1) + c_class = char_class(char) + next_char_class = + char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) + end + if c_class ~= 0 and next_char_class ~= 0 then while char_class(char) == c_class and offset <= len do offset = offset + 1 - char = vim.fn.strcharpart(str, offset, 1) + char = vim.fn.strcharpart(str, offset - 1, 1) end end @@ -139,7 +148,7 @@ local function end_of_word(str, start) if rev_offset ~= nil then return rev_offset end - return offset + return offset - 1 end ---@param str string diff --git a/tests/precognition/char_spec.lua b/tests/precognition/char_spec.lua index 3c6ea2d..76b0019 100644 --- a/tests/precognition/char_spec.lua +++ b/tests/precognition/char_spec.lua @@ -337,4 +337,38 @@ describe("boundaries", function() ) end) end) + + describe("edge case", function() + it("can handle empty strings", function() + eq(nil, precognition.next_word_boundary("", 1)) + eq(nil, precognition.prev_word_boundary("", 1)) + eq(nil, precognition.end_of_word("", 1)) + end) + + it("can handle strings with only whitespace", function() + eq(nil, precognition.next_word_boundary(" ", 1)) + eq(nil, precognition.prev_word_boundary(" ", 1)) + eq(nil, precognition.end_of_word(" ", 1)) + end) + + it( + "can handle strings with special characters in the middle", + function() + local str = "vim.keymap.set('n', 't;', ':Test')" + eq(5, precognition.next_word_boundary(str, 4)) + eq(1, precognition.prev_word_boundary(str, 4)) + eq(10, precognition.end_of_word(str, 4)) + end + ) + + it ( + "can handle strings with multiple consecutive special characters", + function() + local str = "this || that" + eq(9, precognition.next_word_boundary(str, 6)) + eq(1, precognition.prev_word_boundary(str, 6)) + eq(7, precognition.end_of_word(str, 6)) + end + ) + end) end) From 645ef56278675696c44b6fb8a552c7b41142c383 Mon Sep 17 00:00:00 2001 From: tris203 Date: Thu, 2 May 2024 22:40:59 +0100 Subject: [PATCH 04/17] fix: gutter placement messages and prios --- lua/precognition/init.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index b54e6a8..291cfe0 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -25,8 +25,8 @@ local default = { ["$"] = { text = "$", prio = 1 }, ["w"] = { text = "w", prio = 10 }, -- ["W"] = "W", - ["b"] = { text = "b", prio = 10 }, - ["e"] = { text = "e", prio = 10 }, + ["b"] = { text = "b", prio = 9 }, + ["e"] = { text = "e", prio = 8 }, -- ["ge"] = "ge", -- should we support multi-char / multi-byte hints? }, gutterHints = { @@ -262,9 +262,9 @@ local function apply_gutter_hints(gutter_hints, buf) if ok then gutter_signs_cache[hint] = { line = loc, id = res } end - if not ok then + if not ok and loc ~= 0 then vim.notify_once( - "Failed to place sign: " .. config.gutterHints[hint].text, + "Failed to place sign: " .. config.gutterHints[hint].text .. " at line " .. loc, vim.log.levels.WARN ) end From be3f69d1fa1a291afc788fa2023eba10e48e28e7 Mon Sep 17 00:00:00 2001 From: tris203 Date: Thu, 2 May 2024 22:42:12 +0100 Subject: [PATCH 05/17] chore: fmt --- lua/precognition/init.lua | 5 ++++- tests/precognition/char_spec.lua | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 291cfe0..f6ad58d 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -264,7 +264,10 @@ local function apply_gutter_hints(gutter_hints, buf) end if not ok and loc ~= 0 then vim.notify_once( - "Failed to place sign: " .. config.gutterHints[hint].text .. " at line " .. loc, + "Failed to place sign: " + .. config.gutterHints[hint].text + .. " at line " + .. loc, vim.log.levels.WARN ) end diff --git a/tests/precognition/char_spec.lua b/tests/precognition/char_spec.lua index 76b0019..87ff063 100644 --- a/tests/precognition/char_spec.lua +++ b/tests/precognition/char_spec.lua @@ -361,7 +361,7 @@ describe("boundaries", function() end ) - it ( + it( "can handle strings with multiple consecutive special characters", function() local str = "this || that" From a8a430838a33a2c30d9f7409452bf0ba09f1c836 Mon Sep 17 00:00:00 2001 From: tris203 Date: Thu, 2 May 2024 23:00:51 +0100 Subject: [PATCH 06/17] fix: edge cases around special chars --- lua/precognition/init.lua | 5 +++- tests/precognition/char_spec.lua | 45 +++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index f6ad58d..60d8124 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -119,7 +119,10 @@ local function end_of_word(str, start) char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) local rev_offset - if c_class == 1 and next_char_class ~= 1 then + if + (c_class == 1 and next_char_class ~= 1) + or (next_char_class == 1 and c_class ~= 1) + then offset = offset + 1 char = vim.fn.strcharpart(str, offset - 1, 1) c_class = char_class(char) diff --git a/tests/precognition/char_spec.lua b/tests/precognition/char_spec.lua index 87ff063..ef3fd21 100644 --- a/tests/precognition/char_spec.lua +++ b/tests/precognition/char_spec.lua @@ -197,13 +197,20 @@ describe("boundaries", function() 6 ) ) - -- eq( - -- 5, - -- precognition.prev_word_boundary( - -- " myFunction(example, stuff)", - -- 16 - -- ) - -- ) + eq( + 5, + precognition.prev_word_boundary( + " myFunction(example, stuff)", + 15 + ) + ) + eq( + 15, + precognition.prev_word_boundary( + " myFunction(example, stuff)", + 16 + ) + ) eq( 16, precognition.prev_word_boundary( @@ -260,9 +267,13 @@ describe("boundaries", function() 29 ) ) - --TODO: This isnt right, it should ne 25, but i dont know the rules - --there is something odd if there is only one class 2 under the cursor - -- eq(25, precognition.prev_word_boundary(" myFunction(example, stuff)", 30)) + eq( + 25, + precognition.prev_word_boundary( + " myFunction(example, stuff)", + 30 + ) + ) end) it("can walk string with b", function() @@ -307,10 +318,14 @@ describe("boundaries", function() 14, precognition.end_of_word(" myFunction(example, stuff)", 5) ) - --TODO: These next two dont work either for the same reason as the previous - --something to do with the bracket being under the cursor - -- eq(15, precognition.end_of_word(" myFunction(example, stuff)", 14)) - -- eq(22, precognition.end_of_word(" myFunction(example, stuff)", 15)) + eq( + 15, + precognition.end_of_word(" myFunction(example, stuff)", 14) + ) + eq( + 22, + precognition.end_of_word(" myFunction(example, stuff)", 15) + ) eq( 22, precognition.end_of_word(" myFunction(example, stuff)", 16) @@ -328,7 +343,7 @@ describe("boundaries", function() precognition.end_of_word(" myFunction(example, stuff)", 25) ) eq( - 29, + 30, precognition.end_of_word(" myFunction(example, stuff)", 29) ) eq( From ee73a3235ee5d2cb9c9adae973913e0075f9d97f Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 15:40:06 +0100 Subject: [PATCH 07/17] refactor: refactor into modules refactor(tests): refactor into seperate files --- lua/precognition/horizontal_motions.lua | 127 ++++++ lua/precognition/init.lua | 163 +------- lua/precognition/utils.lua | 21 + lua/precognition/vertical_motions.lua | 28 ++ tests/precognition/char_spec.lua | 387 +----------------- tests/precognition/gutter_hints_spec.lua | 2 +- .../precognition/horizontal_motions_spec.lua | 176 ++++++++ tests/precognition/virtline_spec.lua | 21 +- 8 files changed, 388 insertions(+), 537 deletions(-) create mode 100644 lua/precognition/horizontal_motions.lua create mode 100644 lua/precognition/utils.lua create mode 100644 lua/precognition/vertical_motions.lua create mode 100644 tests/precognition/horizontal_motions_spec.lua diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua new file mode 100644 index 0000000..ca59fa3 --- /dev/null +++ b/lua/precognition/horizontal_motions.lua @@ -0,0 +1,127 @@ +local utils = require("precognition.utils") + +local M = {} + +---@param str string +---@return integer +function M.line_start_non_whitespace(str) + return str:find("%S") or 0 +end + +---@param len integer +---@return integer +function M.line_end(len) + return len +end + +---@param str string +---@param start integer +---@return integer | nil +function M.next_word_boundary(str, start) + local offset = start + local len = vim.fn.strcharlen(str) + local char = vim.fn.strcharpart(str, offset - 1, 1) + local c_class = utils.char_class(char) + + if c_class ~= 0 then + while utils.char_class(char) == c_class and offset <= len do + offset = offset + 1 + char = vim.fn.strcharpart(str, offset - 1, 1) + end + end + + while utils.char_class(char) == 0 and offset <= len do + offset = offset + 1 + char = vim.fn.strcharpart(str, offset - 1, 1) + end + if offset > len then + return nil + end + + return offset +end + +---@param str string +---@param start integer +---@return integer | nil +function M.end_of_word(str, start) + local len = vim.fn.strcharlen(str) + if start >= len then + return nil + end + local offset = start + local char = vim.fn.strcharpart(str, offset - 1, 1) + local c_class = utils.char_class(char) + local next_char_class = + utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) + local rev_offset + + if + (c_class == 1 and next_char_class ~= 1) + or (next_char_class == 1 and c_class ~= 1) + then + offset = offset + 1 + char = vim.fn.strcharpart(str, offset - 1, 1) + c_class = utils.char_class(char) + next_char_class = + utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) + end + + if c_class ~= 0 and next_char_class ~= 0 then + while utils.char_class(char) == c_class and offset <= len do + offset = offset + 1 + char = vim.fn.strcharpart(str, offset - 1, 1) + end + end + + if c_class == 0 or next_char_class == 0 then + local next_word_start = M.next_word_boundary(str, offset) + if next_word_start then + rev_offset = M.end_of_word(str, next_word_start + 1) + end + end + + if rev_offset ~= nil and rev_offset <= 0 then + return nil + end + + if rev_offset ~= nil then + return rev_offset + end + return offset - 1 +end + +---@param str string +---@param start integer +---@return integer | nil +function M.prev_word_boundary(str, start) + local len = vim.fn.strcharlen(str) + local offset = start - 1 + local char = vim.fn.strcharpart(str, offset - 1, 1) + local c_class = utils.char_class(char) + + if c_class == 0 then + while utils.char_class(char) == 0 and offset >= 0 do + offset = offset - 1 + char = vim.fn.strcharpart(str, offset - 1, 1) + end + c_class = utils.char_class(char) + end + + while utils.char_class(char) == c_class and offset >= 0 do + offset = offset - 1 + char = vim.fn.strcharpart(str, offset - 1, 1) + --if remaining string is whitespace, return nil_wrap + local remaining = string.sub(str, offset) + if remaining:match("^%s*$") and #remaining > 0 then + return nil + end + end + + if offset == nil or offset > len or offset < 0 then + return nil + end + return offset + 1 +end + +return M diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 60d8124..d369a6b 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -1,3 +1,6 @@ +local hm = require("precognition.horizontal_motions") +local vm = require("precognition.vertical_motions") + local M = {} ---@alias SupportedHints "'^'" | "'b'" | "'w'" | "'$'" @@ -59,134 +62,6 @@ local ns = vim.api.nvim_create_namespace("precognition") ---@type string local gutter_group = "precognition_gutter" ----@param char string ----@return integer -local function char_class(char) - local byte = string.byte(char) - - if byte and byte < 0x100 then - if char == " " or char == "\t" or char == "\0" then - return 0 -- whitespace - end - if char == "_" or char:match("%w") then - return 2 -- word character - end - return 1 -- other - end - - return 1 -- scary unicode edge cases go here -end - ----@param str string ----@param start integer ----@return integer | nil -local function next_word_boundary(str, start) - local offset = start - local len = vim.fn.strcharlen(str) - local char = vim.fn.strcharpart(str, offset - 1, 1) - local c_class = char_class(char) - - if c_class ~= 0 then - while char_class(char) == c_class and offset <= len do - offset = offset + 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - end - end - - while char_class(char) == 0 and offset <= len do - offset = offset + 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - end - if offset > len then - return nil - end - - return offset -end - ----@param str string ----@param start integer ----@return integer | nil -local function end_of_word(str, start) - local len = vim.fn.strcharlen(str) - if start >= len then - return nil - end - local offset = start - local char = vim.fn.strcharpart(str, offset - 1, 1) - local c_class = char_class(char) - local next_char_class = - char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) - local rev_offset - - if - (c_class == 1 and next_char_class ~= 1) - or (next_char_class == 1 and c_class ~= 1) - then - offset = offset + 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - c_class = char_class(char) - next_char_class = - char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) - end - - if c_class ~= 0 and next_char_class ~= 0 then - while char_class(char) == c_class and offset <= len do - offset = offset + 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - end - end - - if c_class == 0 or next_char_class == 0 then - local next_word_start = next_word_boundary(str, offset) - if next_word_start then - rev_offset = end_of_word(str, next_word_start + 1) - end - end - - if rev_offset ~= nil and rev_offset <= 0 then - return nil - end - - if rev_offset ~= nil then - return rev_offset - end - return offset - 1 -end - ----@param str string ----@param start integer ----@return integer | nil -local function prev_word_boundary(str, start) - local len = vim.fn.strcharlen(str) - local offset = start - 1 - local char = vim.fn.strcharpart(str, offset - 1, 1) - local c_class = char_class(char) - - if c_class == 0 then - while char_class(char) == 0 and offset >= 0 do - offset = offset - 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - end - c_class = char_class(char) - end - - while char_class(char) == c_class and offset >= 0 do - offset = offset - 1 - char = vim.fn.strcharpart(str, offset - 1, 1) - --if remaining string is whitespace, return nil_wrap - local remaining = string.sub(str, offset) - if remaining:match("^%s*$") and #remaining > 0 then - return nil - end - end - - if offset == nil or offset > len or offset < 0 then - return nil - end - return offset + 1 -end - ---@param marks Precognition.VirtLine ---@param line_len integer ---@return table @@ -222,10 +97,10 @@ end ---@return Precognition.GutterHints local function build_gutter_hints(buf) local gutter_hints = { - ["G"] = vim.api.nvim_buf_line_count(buf), - ["gg"] = 1, - ["{"] = vim.fn.search("^\\s*$", "bn"), - ["}"] = vim.fn.search("^\\s*$", "n"), + ["G"] = vm.file_end(vim.api.nvim_buf_get_lines(buf, 0, -1, false)), + ["gg"] = vm.file_start(), + ["{"] = vm.prev_paragraph_line(), + ["}"] = vm.next_paragraph_line(), } return gutter_hints @@ -239,7 +114,7 @@ local function apply_gutter_hints(gutter_hints, buf) return end for hint, loc in pairs(gutter_hints) do - if config.gutterHints[hint] then + if config.gutterHints[hint] and loc ~= 0 and loc ~= nil then if gutter_signs_cache[hint] then vim.fn.sign_unplace( gutter_group, @@ -298,11 +173,11 @@ local function on_cursor_hold() -- get char offsets for more complex motions. local virt_line = build_virt_line({ - ["w"] = next_word_boundary(cur_line, cursorcol), - ["e"] = end_of_word(cur_line, cursorcol), - ["b"] = prev_word_boundary(cur_line, cursorcol), - ["^"] = cur_line:find("%S") or 0, - ["$"] = line_len, + ["w"] = hm.next_word_boundary(cur_line, cursorcol), + ["e"] = hm.end_of_word(cur_line, cursorcol), + ["b"] = hm.prev_word_boundary(cur_line, cursorcol), + ["^"] = hm.line_start_non_whitespace(cur_line), + ["$"] = hm.line_end(line_len), }, line_len) -- TODO: can we add indent lines to the virt line to match indent-blankline or similar (if installed)? @@ -455,18 +330,6 @@ end -- access these variables from outside the module -- but we don't want to expose them to the user local state = { - char_class = function() - return char_class - end, - next_word_boundary = function() - return next_word_boundary - end, - prev_word_boundary = function() - return prev_word_boundary - end, - end_of_word = function() - return end_of_word - end, build_virt_line = function() return build_virt_line end, diff --git a/lua/precognition/utils.lua b/lua/precognition/utils.lua new file mode 100644 index 0000000..018bb71 --- /dev/null +++ b/lua/precognition/utils.lua @@ -0,0 +1,21 @@ +local M = {} + +---@param char string +---@return integer +function M.char_class(char) + local byte = string.byte(char) + + if byte and byte < 0x100 then + if char == " " or char == "\t" or char == "\0" then + return 0 -- whitespace + end + if char == "_" or char:match("%w") then + return 2 -- word character + end + return 1 -- other + end + + return 1 -- scary unicode edge cases go here +end + +return M diff --git a/lua/precognition/vertical_motions.lua b/lua/precognition/vertical_motions.lua new file mode 100644 index 0000000..a1876c7 --- /dev/null +++ b/lua/precognition/vertical_motions.lua @@ -0,0 +1,28 @@ +local utils = require("precognition.utils") + +local M = {} + +---@return integer +function M.file_start() + return 1 +end + +---@param lines table +---@return integer +function M.file_end(lines) + return #lines +end + +---@return integer | nil +function M.next_paragraph_line() + --TODO: refactor this to use a testable function + return vim.fn.search("^\\s*$", "n") +end + +---@return integer | nil +function M.prev_paragraph_line() + --TODO: refactor this to use a testable function + return vim.fn.search("^\\s*$", "bn") +end + +return M diff --git a/tests/precognition/char_spec.lua b/tests/precognition/char_spec.lua index ef3fd21..0fb3c2c 100644 --- a/tests/precognition/char_spec.lua +++ b/tests/precognition/char_spec.lua @@ -1,389 +1,24 @@ -local precognition = require("precognition") +local utils = require("precognition.utils") ---@diagnostic disable-next-line: undefined-field local eq = assert.are.same describe("char classing", function() it("white space is classeed", function() - eq(precognition.char_class(" "), 0) - eq(precognition.char_class("\t"), 0) - eq(precognition.char_class("\0"), 0) + eq(utils.char_class(" "), 0) + eq(utils.char_class("\t"), 0) + eq(utils.char_class("\0"), 0) end) it("word characters are classed", function() - eq(precognition.char_class("_"), 2) - eq(precognition.char_class("a"), 2) - eq(precognition.char_class("A"), 2) - eq(precognition.char_class("0"), 2) + eq(utils.char_class("_"), 2) + eq(utils.char_class("a"), 2) + eq(utils.char_class("A"), 2) + eq(utils.char_class("0"), 2) end) it("other characters are classed", function() - eq(precognition.char_class("!"), 1) - eq(precognition.char_class("@"), 1) - eq(precognition.char_class("."), 1) - end) -end) - -describe("boundaries", function() - it("finds the next word boundary", function() - eq(5, precognition.next_word_boundary("abc efg", 1)) - eq(5, precognition.next_word_boundary("abc efg", 2)) - eq(5, precognition.next_word_boundary("abc efg", 3)) - eq(5, precognition.next_word_boundary("abc efg", 4)) - eq(nil, precognition.next_word_boundary("abc efg", 5)) - eq(nil, precognition.next_word_boundary("abc efg", 6)) - eq(nil, precognition.next_word_boundary("abc efg", 7)) - - eq(9, precognition.next_word_boundary("slighly more complex test", 1)) - eq(9, precognition.next_word_boundary("slighly more complex test", 2)) - eq(14, precognition.next_word_boundary("slighly more complex test", 10)) - eq(14, precognition.next_word_boundary("slighly more complex test", 13)) - eq(22, precognition.next_word_boundary("slighly more complex test", 15)) - eq(22, precognition.next_word_boundary("slighly more complex test", 21)) - - eq( - 5, - precognition.next_word_boundary(" myFunction(example, stuff)", 1) - ) - eq( - 5, - precognition.next_word_boundary(" myFunction(example, stuff)", 2) - ) - eq( - 5, - precognition.next_word_boundary(" myFunction(example, stuff)", 3) - ) - eq( - 15, - precognition.next_word_boundary(" myFunction(example, stuff)", 5) - ) - eq( - 16, - precognition.next_word_boundary( - " myFunction(example, stuff)", - 15 - ) - ) - eq( - 23, - precognition.next_word_boundary( - " myFunction(example, stuff)", - 16 - ) - ) - eq( - 25, - precognition.next_word_boundary( - " myFunction(example, stuff)", - 23 - ) - ) - eq( - 25, - precognition.next_word_boundary( - " myFunction(example, stuff)", - 24 - ) - ) - eq( - 30, - precognition.next_word_boundary( - " myFunction(example, stuff)", - 25 - ) - ) - eq( - nil, - precognition.next_word_boundary( - " myFunction(example, stuff)", - 30 - ) - ) - end) - - it("can walk string with w", function() - local test_string = "abcdefg hijklmn opqrstu vwxyz" - local pos = precognition.next_word_boundary(test_string, 1) - eq("h", test_string:sub(pos, pos)) - pos = precognition.next_word_boundary(test_string, pos) - eq("o", test_string:sub(pos, pos)) - pos = precognition.next_word_boundary(test_string, pos) - eq("v", test_string:sub(pos, pos)) - pos = precognition.next_word_boundary(test_string, pos) - eq(nil, pos) - end) - - describe("previous word boundary", function() - it("finds the previous word boundary", function() - eq(nil, precognition.prev_word_boundary("abc efg", 1)) - eq(1, precognition.prev_word_boundary("abc efg", 2)) - eq(1, precognition.prev_word_boundary("abc efg", 3)) - eq(1, precognition.prev_word_boundary("abc efg", 4)) - eq(1, precognition.prev_word_boundary("abc efg", 5)) - eq(5, precognition.prev_word_boundary("abc efg", 6)) - eq(5, precognition.prev_word_boundary("abc efg", 7)) - - eq( - 9, - precognition.prev_word_boundary("slighly more complex test", 10) - ) - eq( - 9, - precognition.prev_word_boundary("slighly more complex test", 11) - ) - eq( - 14, - precognition.prev_word_boundary("slighly more complex test", 15) - ) - eq( - 14, - precognition.prev_word_boundary("slighly more complex test", 16) - ) - eq( - 22, - precognition.prev_word_boundary("slighly more complex test", 23) - ) - eq( - 22, - precognition.prev_word_boundary("slighly more complex test", 24) - ) - eq( - 22, - precognition.prev_word_boundary("slighly more complex test", 25) - ) - eq( - nil, - precognition.prev_word_boundary("slighly more complex test", 1) - ) - - eq( - nil, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 1 - ) - ) - eq( - nil, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 2 - ) - ) - eq( - nil, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 3 - ) - ) - eq( - nil, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 4 - ) - ) - eq( - nil, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 5 - ) - ) - eq( - 5, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 6 - ) - ) - eq( - 5, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 15 - ) - ) - eq( - 15, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 16 - ) - ) - eq( - 16, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 17 - ) - ) - eq( - 16, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 18 - ) - ) - eq( - 16, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 19 - ) - ) - eq( - 23, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 25 - ) - ) - eq( - 25, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 26 - ) - ) - eq( - 25, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 27 - ) - ) - eq( - 25, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 28 - ) - ) - eq( - 25, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 29 - ) - ) - eq( - 25, - precognition.prev_word_boundary( - " myFunction(example, stuff)", - 30 - ) - ) - end) - - it("can walk string with b", function() - local test_string = "abcdefg hijklmn opqrstu vwxyz" - local pos = precognition.prev_word_boundary(test_string, 29) - eq("v", test_string:sub(pos, pos)) - pos = precognition.prev_word_boundary(test_string, pos) - eq("o", test_string:sub(pos, pos)) - pos = precognition.prev_word_boundary(test_string, pos) - eq("h", test_string:sub(pos, pos)) - pos = precognition.prev_word_boundary(test_string, pos) - eq(1, pos) - end) - end) - - describe("end of current word", function() - it("finds the end of words", function() - eq(3, precognition.end_of_word("abc efg", 1)) - eq(3, precognition.end_of_word("abc efg", 2)) - eq(7, precognition.end_of_word("abc efg", 3)) - - eq(7, precognition.end_of_word("slighly more complex test", 1)) - eq(7, precognition.end_of_word("slighly more complex test", 2)) - eq(12, precognition.end_of_word("slighly more complex test", 10)) - eq(20, precognition.end_of_word("slighly more complex test", 13)) - eq(20, precognition.end_of_word("slighly more complex test", 15)) - eq(25, precognition.end_of_word("slighly more complex test", 21)) - - eq( - 14, - precognition.end_of_word(" myFunction(example, stuff)", 1) - ) - eq( - 14, - precognition.end_of_word(" myFunction(example, stuff)", 2) - ) - eq( - 14, - precognition.end_of_word(" myFunction(example, stuff)", 3) - ) - eq( - 14, - precognition.end_of_word(" myFunction(example, stuff)", 5) - ) - eq( - 15, - precognition.end_of_word(" myFunction(example, stuff)", 14) - ) - eq( - 22, - precognition.end_of_word(" myFunction(example, stuff)", 15) - ) - eq( - 22, - precognition.end_of_word(" myFunction(example, stuff)", 16) - ) - eq( - 29, - precognition.end_of_word(" myFunction(example, stuff)", 23) - ) - eq( - 29, - precognition.end_of_word(" myFunction(example, stuff)", 24) - ) - eq( - 29, - precognition.end_of_word(" myFunction(example, stuff)", 25) - ) - eq( - 30, - precognition.end_of_word(" myFunction(example, stuff)", 29) - ) - eq( - nil, - precognition.end_of_word(" myFunction(example, stuff)", 30) - ) - end) - end) - - describe("edge case", function() - it("can handle empty strings", function() - eq(nil, precognition.next_word_boundary("", 1)) - eq(nil, precognition.prev_word_boundary("", 1)) - eq(nil, precognition.end_of_word("", 1)) - end) - - it("can handle strings with only whitespace", function() - eq(nil, precognition.next_word_boundary(" ", 1)) - eq(nil, precognition.prev_word_boundary(" ", 1)) - eq(nil, precognition.end_of_word(" ", 1)) - end) - - it( - "can handle strings with special characters in the middle", - function() - local str = "vim.keymap.set('n', 't;', ':Test')" - eq(5, precognition.next_word_boundary(str, 4)) - eq(1, precognition.prev_word_boundary(str, 4)) - eq(10, precognition.end_of_word(str, 4)) - end - ) - - it( - "can handle strings with multiple consecutive special characters", - function() - local str = "this || that" - eq(9, precognition.next_word_boundary(str, 6)) - eq(1, precognition.prev_word_boundary(str, 6)) - eq(7, precognition.end_of_word(str, 6)) - end - ) + eq(utils.char_class("!"), 1) + eq(utils.char_class("@"), 1) + eq(utils.char_class("."), 1) end) end) diff --git a/tests/precognition/gutter_hints_spec.lua b/tests/precognition/gutter_hints_spec.lua index f3518e6..8dbd2b0 100644 --- a/tests/precognition/gutter_hints_spec.lua +++ b/tests/precognition/gutter_hints_spec.lua @@ -12,7 +12,7 @@ describe("Gutter hints table", function() "GHI", }) - local hints = precognition.build_gutter_hints(testBuf) + local hints = precognition.build_gutter_hints(testBuf, 1, 1) eq(4, hints["G"]) eq(1, hints["gg"]) diff --git a/tests/precognition/horizontal_motions_spec.lua b/tests/precognition/horizontal_motions_spec.lua new file mode 100644 index 0000000..043b8a0 --- /dev/null +++ b/tests/precognition/horizontal_motions_spec.lua @@ -0,0 +1,176 @@ +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() + eq(5, hm.next_word_boundary("abc efg", 1)) + eq(5, hm.next_word_boundary("abc efg", 2)) + eq(5, hm.next_word_boundary("abc efg", 3)) + eq(5, hm.next_word_boundary("abc efg", 4)) + eq(nil, hm.next_word_boundary("abc efg", 5)) + eq(nil, hm.next_word_boundary("abc efg", 6)) + eq(nil, hm.next_word_boundary("abc efg", 7)) + + eq(9, hm.next_word_boundary("slighly more complex test", 1)) + eq(9, hm.next_word_boundary("slighly more complex test", 2)) + eq(14, hm.next_word_boundary("slighly more complex test", 10)) + eq(14, hm.next_word_boundary("slighly more complex test", 13)) + eq(22, hm.next_word_boundary("slighly more complex test", 15)) + eq(22, hm.next_word_boundary("slighly more complex test", 21)) + + eq(5, hm.next_word_boundary(" myFunction(example, stuff)", 1)) + eq(5, hm.next_word_boundary(" myFunction(example, stuff)", 2)) + eq(5, hm.next_word_boundary(" myFunction(example, stuff)", 3)) + eq(15, hm.next_word_boundary(" myFunction(example, stuff)", 5)) + eq(16, hm.next_word_boundary(" myFunction(example, stuff)", 15)) + eq(23, hm.next_word_boundary(" myFunction(example, stuff)", 16)) + eq(25, hm.next_word_boundary(" myFunction(example, stuff)", 23)) + eq(25, hm.next_word_boundary(" myFunction(example, stuff)", 24)) + eq(30, hm.next_word_boundary(" myFunction(example, stuff)", 25)) + eq(nil, hm.next_word_boundary(" myFunction(example, stuff)", 30)) + end) + + it("can walk string with w", function() + local test_string = "abcdefg hijklmn opqrstu vwxyz" + local pos = hm.next_word_boundary(test_string, 1) + if pos == nil then + error("pos is nil") + end + eq("h", test_string:sub(pos, pos)) + if pos == nil then + error("pos is nil") + end + pos = hm.next_word_boundary(test_string, pos) + if pos == nil then + error("pos is nil") + end + eq("o", test_string:sub(pos, pos)) + pos = hm.next_word_boundary(test_string, pos) + if pos == nil then + error("pos is nil") + end + eq("v", test_string:sub(pos, pos)) + pos = hm.next_word_boundary(test_string, pos) + eq(nil, pos) + end) + + describe("previous word boundary", function() + it("finds the previous word boundary", function() + eq(nil, hm.prev_word_boundary("abc efg", 1)) + eq(1, hm.prev_word_boundary("abc efg", 2)) + eq(1, hm.prev_word_boundary("abc efg", 3)) + eq(1, hm.prev_word_boundary("abc efg", 4)) + eq(1, hm.prev_word_boundary("abc efg", 5)) + eq(5, hm.prev_word_boundary("abc efg", 6)) + eq(5, hm.prev_word_boundary("abc efg", 7)) + + eq(9, hm.prev_word_boundary("slighly more complex test", 10)) + eq(9, hm.prev_word_boundary("slighly more complex test", 11)) + eq(14, hm.prev_word_boundary("slighly more complex test", 15)) + eq(14, hm.prev_word_boundary("slighly more complex test", 16)) + eq(22, hm.prev_word_boundary("slighly more complex test", 23)) + eq(22, hm.prev_word_boundary("slighly more complex test", 24)) + eq(22, hm.prev_word_boundary("slighly more complex test", 25)) + eq(nil, hm.prev_word_boundary("slighly more complex test", 1)) + + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 1)) + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 2)) + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 3)) + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 4)) + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 5)) + eq(5, hm.prev_word_boundary(" myFunction(example, stuff)", 6)) + eq(5, hm.prev_word_boundary(" myFunction(example, stuff)", 15)) + eq(15, hm.prev_word_boundary(" myFunction(example, stuff)", 16)) + eq(16, hm.prev_word_boundary(" myFunction(example, stuff)", 17)) + eq(16, hm.prev_word_boundary(" myFunction(example, stuff)", 18)) + eq(16, hm.prev_word_boundary(" myFunction(example, stuff)", 19)) + eq(23, hm.prev_word_boundary(" myFunction(example, stuff)", 25)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 26)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 27)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 28)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 29)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 30)) + end) + + it("can walk string with b", function() + local test_string = "abcdefg hijklmn opqrstu vwxyz" + local pos = hm.prev_word_boundary(test_string, 29) + if pos == nil then + error("pos is nil") + end + eq("v", test_string:sub(pos, pos)) + pos = hm.prev_word_boundary(test_string, pos) + if pos == nil then + error("pos is nil") + end + eq("o", test_string:sub(pos, pos)) + pos = hm.prev_word_boundary(test_string, pos) + if pos == nil then + error("pos is nil") + end + eq("h", test_string:sub(pos, pos)) + pos = hm.prev_word_boundary(test_string, pos) + eq(1, pos) + end) + end) + + describe("end of current word", function() + it("finds the end of words", function() + eq(3, hm.end_of_word("abc efg", 1)) + eq(3, hm.end_of_word("abc efg", 2)) + eq(7, hm.end_of_word("abc efg", 3)) + + eq(7, hm.end_of_word("slighly more complex test", 1)) + eq(7, hm.end_of_word("slighly more complex test", 2)) + eq(12, hm.end_of_word("slighly more complex test", 10)) + eq(20, hm.end_of_word("slighly more complex test", 13)) + eq(20, hm.end_of_word("slighly more complex test", 15)) + eq(25, hm.end_of_word("slighly more complex test", 21)) + + eq(14, hm.end_of_word(" myFunction(example, stuff)", 1)) + eq(14, hm.end_of_word(" myFunction(example, stuff)", 2)) + eq(14, hm.end_of_word(" myFunction(example, stuff)", 3)) + eq(14, hm.end_of_word(" myFunction(example, stuff)", 5)) + eq(15, hm.end_of_word(" myFunction(example, stuff)", 14)) + eq(22, hm.end_of_word(" myFunction(example, stuff)", 15)) + eq(22, hm.end_of_word(" myFunction(example, stuff)", 16)) + eq(29, hm.end_of_word(" myFunction(example, stuff)", 23)) + eq(29, hm.end_of_word(" myFunction(example, stuff)", 24)) + eq(29, hm.end_of_word(" myFunction(example, stuff)", 25)) + eq(30, hm.end_of_word(" myFunction(example, stuff)", 29)) + eq(nil, hm.end_of_word(" myFunction(example, stuff)", 30)) + end) + end) +end) + +describe("edge case", function() + it("can handle empty strings", function() + eq(nil, hm.next_word_boundary("", 1)) + eq(nil, hm.prev_word_boundary("", 1)) + eq(nil, hm.end_of_word("", 1)) + end) + + it("can handle strings with only whitespace", function() + eq(nil, hm.next_word_boundary(" ", 1)) + eq(nil, hm.prev_word_boundary(" ", 1)) + eq(nil, hm.end_of_word(" ", 1)) + 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)) + eq(1, hm.prev_word_boundary(str, 4)) + eq(10, hm.end_of_word(str, 4)) + end) + + it( + "can handle strings with multiple consecutive special characters", + function() + local str = "this || that" + eq(9, hm.next_word_boundary(str, 6)) + eq(1, hm.prev_word_boundary(str, 6)) + eq(7, hm.end_of_word(str, 6)) + end + ) +end) diff --git a/tests/precognition/virtline_spec.lua b/tests/precognition/virtline_spec.lua index 4d76727..777337a 100644 --- a/tests/precognition/virtline_spec.lua +++ b/tests/precognition/virtline_spec.lua @@ -1,4 +1,5 @@ local precognition = require("precognition") +local hm = require("precognition.horizontal_motions") ---@diagnostic disable-next-line: undefined-field local eq = assert.are.same describe("Build Virtual Line", function() @@ -82,11 +83,11 @@ describe("Build Virtual Line", function() local line_len = vim.fn.strcharlen(cur_line) local virt_line = precognition.build_virt_line({ - ["w"] = precognition.next_word_boundary(cur_line, cursorcol), - ["e"] = precognition.end_of_word(cur_line, cursorcol), - ["b"] = precognition.prev_word_boundary(cur_line, cursorcol), - ["^"] = cur_line:find("%S") or 0, - ["$"] = line_len, + ["w"] = hm.next_word_boundary(cur_line, cursorcol), + ["e"] = hm.end_of_word(cur_line, cursorcol), + ["b"] = hm.prev_word_boundary(cur_line, cursorcol), + ["^"] = hm.line_start_non_whitespace(cur_line), + ["$"] = hm.line_end(line_len), }, line_len) eq("b e w $", virt_line[1][1]) @@ -103,11 +104,11 @@ describe("Build Virtual Line", function() local line_len = vim.fn.strcharlen(cur_line) local virt_line = precognition.build_virt_line({ - ["w"] = precognition.next_word_boundary(cur_line, cursorcol), - ["e"] = precognition.end_of_word(cur_line, cursorcol), - ["b"] = precognition.prev_word_boundary(cur_line, cursorcol), - ["^"] = cur_line:find("%S") or 0, - ["$"] = line_len, + ["w"] = hm.next_word_boundary(cur_line, cursorcol), + ["e"] = hm.end_of_word(cur_line, cursorcol), + ["b"] = hm.prev_word_boundary(cur_line, cursorcol), + ["^"] = hm.line_start_non_whitespace(cur_line), + ["$"] = hm.line_end(line_len), }, line_len) eq(" ^ e w $", virt_line[1][1]) From 7eacf6f76f6ff9a5ca0c9a21853557f50a441de3 Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 21:20:21 +0100 Subject: [PATCH 08/17] refactor(vertical_motions): refactor and test vertical motions --- lua/precognition/init.lua | 4 +- lua/precognition/vertical_motions.lua | 28 +++- tests/precognition/gutter_hints_spec.lua | 125 ++++++++++++++- tests/precognition/vertical_motions_spec.lua | 158 +++++++++++++++++++ 4 files changed, 301 insertions(+), 14 deletions(-) create mode 100644 tests/precognition/vertical_motions_spec.lua diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index d369a6b..9662a68 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -99,8 +99,8 @@ local function build_gutter_hints(buf) local gutter_hints = { ["G"] = vm.file_end(vim.api.nvim_buf_get_lines(buf, 0, -1, false)), ["gg"] = vm.file_start(), - ["{"] = vm.prev_paragraph_line(), - ["}"] = vm.next_paragraph_line(), + ["{"] = vm.prev_paragraph_line(vim.api.nvim_get_current_buf()), + ["}"] = vm.next_paragraph_line(vim.api.nvim_get_current_buf()), } return gutter_hints diff --git a/lua/precognition/vertical_motions.lua b/lua/precognition/vertical_motions.lua index a1876c7..006f36a 100644 --- a/lua/precognition/vertical_motions.lua +++ b/lua/precognition/vertical_motions.lua @@ -1,5 +1,3 @@ -local utils = require("precognition.utils") - local M = {} ---@return integer @@ -13,16 +11,30 @@ function M.file_end(lines) return #lines end +---@param buf integer ---@return integer | nil -function M.next_paragraph_line() - --TODO: refactor this to use a testable function - return vim.fn.search("^\\s*$", "n") +function M.next_paragraph_line(buf) + local loc + vim.api.nvim_buf_call(buf, function() + loc = vim.fn.search("^\\_s*$", "nW", vim.fn.line("w$")) + end) + if loc == 0 then + return nil + end + return loc end +---@param buf integer ---@return integer | nil -function M.prev_paragraph_line() - --TODO: refactor this to use a testable function - return vim.fn.search("^\\s*$", "bn") +function M.prev_paragraph_line(buf) + local loc + vim.api.nvim_buf_call(buf, function() + loc = vim.fn.search("^\\_s*$", "bnW", vim.fn.line("w0")) + end) + if loc == 0 then + return nil + end + return loc end return M diff --git a/tests/precognition/gutter_hints_spec.lua b/tests/precognition/gutter_hints_spec.lua index 8dbd2b0..8861871 100644 --- a/tests/precognition/gutter_hints_spec.lua +++ b/tests/precognition/gutter_hints_spec.lua @@ -4,17 +4,134 @@ local eq = assert.are.same describe("Gutter hints table", function() it("should return a table with the correct keys", function() - local testBuf = vim.api.nvim_create_buf(false, true) + local testBuf = vim.api.nvim_create_buf(true, true) vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { "ABC", "DEF", "", "GHI", + "", + "JKL", + "", + "MNO", }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + + local hints = precognition.build_gutter_hints(testBuf) + + eq({ + ["gg"] = 1, + ["{"] = 3, + ["}"] = 5, + ["G"] = 8, + }, hints) + end) + + it( + "should return a table with the correct keys when the buffer is empty", + function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, {}) + vim.api.nvim_set_current_buf(testBuf) + + local hints = precognition.build_gutter_hints(testBuf) + + eq({ + ["gg"] = 1, + ["G"] = 1, + }, hints) + end + ) + + it( + "should return a table with the correct keys when the buffer is a single line", + function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { "ABC" }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + + local hints = precognition.build_gutter_hints(testBuf) + eq({ + ["gg"] = 1, + ["G"] = 1, + }, hints) + end + ) + + it("moving the cursor will update the hints table", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { + "ABC", + "DEF", + "", + "GHI", + "", + "JKL", + "", + "MNO", + }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + + local hints = precognition.build_gutter_hints(testBuf) + + eq({ + ["gg"] = 1, + ["{"] = 3, + ["}"] = 5, + ["G"] = 8, + }, hints) + + vim.api.nvim_win_set_cursor(0, { 6, 0 }) + hints = precognition.build_gutter_hints(testBuf) + eq({ + ["gg"] = 1, + ["{"] = 5, + ["}"] = 7, + ["G"] = 8, + }, hints) + end) + + it("adding a line will update the hints table", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { "ABC" }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + + local hints = precognition.build_gutter_hints(testBuf) + eq({ + ["gg"] = 1, + ["G"] = 1, + }, hints) + + vim.api.nvim_buf_set_lines(testBuf, 1, 1, false, { "DEF" }) + + hints = precognition.build_gutter_hints(testBuf) + eq({ + ["gg"] = 1, + ["G"] = 2, + }, hints) + + vim.api.nvim_buf_set_lines(testBuf, 2, 2, false, { "GHI" }) + + hints = precognition.build_gutter_hints(testBuf) + + eq({ + ["gg"] = 1, + ["G"] = 3, + }, hints) + + vim.api.nvim_buf_set_lines(testBuf, 3, 3, false, { "" }) + vim.api.nvim_buf_set_lines(testBuf, 4, 4, false, { "JKL" }) - local hints = precognition.build_gutter_hints(testBuf, 1, 1) + hints = precognition.build_gutter_hints(testBuf) - eq(4, hints["G"]) - eq(1, hints["gg"]) + eq({ + ["gg"] = 1, + ["}"] = 4, + ["G"] = 5, + }, hints) end) end) diff --git a/tests/precognition/vertical_motions_spec.lua b/tests/precognition/vertical_motions_spec.lua new file mode 100644 index 0000000..f0a6ca6 --- /dev/null +++ b/tests/precognition/vertical_motions_spec.lua @@ -0,0 +1,158 @@ +local vm = require("precognition.vertical_motions") +---@diagnostic disable-next-line: undefined-field +local eq = assert.are.same + +describe("gutter motion locations", function() + it("can find file start in a single line buffer", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { "ABC" }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + + local start = vm.file_start() + eq(1, start) + end) + + it("can find file start in a multi line buffer", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { + "ABC", + "DEF", + "", + "GHI", + "", + "JKL", + "", + "MNO", + }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + + local start = vm.file_start() + eq(1, start) + end) + + it("can find file end in a single line buffer", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { "ABC" }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + + local lines = vim.api.nvim_buf_get_lines(testBuf, 0, -1, false) + local end_ = vm.file_end(lines) + eq(1, end_) + eq(vim.api.nvim_buf_line_count(testBuf), end_) + end) + + it("can find file end in a multi line buffer", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { + "ABC", + "DEF", + "", + "GHI", + "", + "JKL", + "", + "MNO", + }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + + local lines = vim.api.nvim_buf_get_lines(testBuf, 0, -1, false) + local end_ = vm.file_end(lines) + eq(8, end_) + eq(vim.api.nvim_buf_line_count(testBuf), end_) + end) + + it("can find the next paragraph line", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { + "ABC", + "DEF", + "", + "GHI", + "", + "JKL", + "", + "MNO", + }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + + local next_line = vm.next_paragraph_line(testBuf) + eq(5, next_line) + end) + + it("can find the previous paragraph line", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { + "ABC", + "DEF", + "", + "GHI", + "", + "JKL", + "", + "MNO", + }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 6, 0 }) + + local prev_line = vm.prev_paragraph_line(testBuf) + eq(5, prev_line) + end) + + it( + "can find the prev paragraph in a file with multiple consecutive blank lines", + function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { + "ABC", + "DEF", + "", + "", + "GHI", + "", + "JKL", + "", + "", + "", + "MNO", + }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 10, 0 }) + + local prev_line = vm.prev_paragraph_line(testBuf) + -- TODO: This is a bug, it should be 5 + -- If there are multiple consecutive blank lines, it will currentlu return the next blankline + -- not the next blank line preceding content + eq(9, prev_line) + end + ) + + it( + "can find the next paragraph in a file with multiple consecutive blank lines", + function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { + "ABC", + "DEF", + "", + "", + "", + "", + "GHI", + "", + "JKL", + }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 3, 0 }) + + local next_line = vm.next_paragraph_line(testBuf) + -- TODO: This is a bug, it should be 8 + -- Same description as above + eq(4, next_line) + end + ) +end) From b940437ff9b75f8aaaa2d644f19d286914479f23 Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 21:28:05 +0100 Subject: [PATCH 09/17] fix: dont pass content around, just bufnr --- lua/precognition/init.lua | 2 +- lua/precognition/vertical_motions.lua | 6 +++--- tests/precognition/vertical_motions_spec.lua | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 9662a68..24e09fb 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -97,7 +97,7 @@ end ---@return Precognition.GutterHints local function build_gutter_hints(buf) local gutter_hints = { - ["G"] = vm.file_end(vim.api.nvim_buf_get_lines(buf, 0, -1, false)), + ["G"] = vm.file_end(vim.api.nvim_get_current_buf()), ["gg"] = vm.file_start(), ["{"] = vm.prev_paragraph_line(vim.api.nvim_get_current_buf()), ["}"] = vm.next_paragraph_line(vim.api.nvim_get_current_buf()), diff --git a/lua/precognition/vertical_motions.lua b/lua/precognition/vertical_motions.lua index 006f36a..6188006 100644 --- a/lua/precognition/vertical_motions.lua +++ b/lua/precognition/vertical_motions.lua @@ -5,10 +5,10 @@ function M.file_start() return 1 end ----@param lines table +---@param bufnr integer ---@return integer -function M.file_end(lines) - return #lines +function M.file_end(bufnr) + return vim.api.nvim_buf_line_count(bufnr) end ---@param buf integer diff --git a/tests/precognition/vertical_motions_spec.lua b/tests/precognition/vertical_motions_spec.lua index f0a6ca6..5606028 100644 --- a/tests/precognition/vertical_motions_spec.lua +++ b/tests/precognition/vertical_motions_spec.lua @@ -38,8 +38,7 @@ describe("gutter motion locations", function() vim.api.nvim_set_current_buf(testBuf) vim.api.nvim_win_set_cursor(0, { 1, 1 }) - local lines = vim.api.nvim_buf_get_lines(testBuf, 0, -1, false) - local end_ = vm.file_end(lines) + local end_ = vm.file_end(testBuf) eq(1, end_) eq(vim.api.nvim_buf_line_count(testBuf), end_) end) @@ -59,8 +58,7 @@ describe("gutter motion locations", function() vim.api.nvim_set_current_buf(testBuf) vim.api.nvim_win_set_cursor(0, { 4, 0 }) - local lines = vim.api.nvim_buf_get_lines(testBuf, 0, -1, false) - local end_ = vm.file_end(lines) + local end_ = vm.file_end(testBuf) eq(8, end_) eq(vim.api.nvim_buf_line_count(testBuf), end_) end) From 6e68e6a871e559d01f434494ab98c51105a9599e Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 21:30:04 +0100 Subject: [PATCH 10/17] fix: luacheck --- lua/precognition/init.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 24e09fb..6af20f8 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -93,9 +93,8 @@ local function build_virt_line(marks, line_len) return virt_line end ----@param buf integer == bufnr ---@return Precognition.GutterHints -local function build_gutter_hints(buf) +local function build_gutter_hints() local gutter_hints = { ["G"] = vm.file_end(vim.api.nvim_get_current_buf()), ["gg"] = vm.file_start(), @@ -195,7 +194,7 @@ local function on_cursor_hold() }) end apply_gutter_hints( - build_gutter_hints(vim.api.nvim_get_current_buf()), + build_gutter_hints(), vim.api.nvim_get_current_buf() ) @@ -225,7 +224,7 @@ end local function on_buf_edit() apply_gutter_hints( - build_gutter_hints(vim.api.nvim_get_current_buf()), + build_gutter_hints(), vim.api.nvim_get_current_buf() ) end From 4bac2b33e0cb12c7c56cef85a611e9ccc8478136 Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 21:37:24 +0100 Subject: [PATCH 11/17] chore: fmt --- lua/precognition/init.lua | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 6af20f8..66f97bf 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -193,10 +193,7 @@ local function on_cursor_hold() virt_lines = { virt_line }, }) end - apply_gutter_hints( - build_gutter_hints(), - vim.api.nvim_get_current_buf() - ) + apply_gutter_hints(build_gutter_hints(), vim.api.nvim_get_current_buf()) dirty = false end @@ -223,10 +220,7 @@ local function on_insert_enter(ev) end local function on_buf_edit() - apply_gutter_hints( - build_gutter_hints(), - vim.api.nvim_get_current_buf() - ) + apply_gutter_hints(build_gutter_hints(), vim.api.nvim_get_current_buf()) end local function on_buf_leave(ev) From 2b7fc2243b4a1da864c2a4be92f492a2c63cd206 Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 22:35:44 +0100 Subject: [PATCH 12/17] refactor(vertical_motions): unified interface --- lua/precognition/init.lua | 6 +++--- lua/precognition/vertical_motions.lua | 17 ++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 66f97bf..63986b0 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -96,10 +96,10 @@ end ---@return Precognition.GutterHints local function build_gutter_hints() local gutter_hints = { - ["G"] = vm.file_end(vim.api.nvim_get_current_buf()), + ["G"] = vm.file_end(), ["gg"] = vm.file_start(), - ["{"] = vm.prev_paragraph_line(vim.api.nvim_get_current_buf()), - ["}"] = vm.next_paragraph_line(vim.api.nvim_get_current_buf()), + ["{"] = vm.prev_paragraph_line(), + ["}"] = vm.next_paragraph_line(), } return gutter_hints diff --git a/lua/precognition/vertical_motions.lua b/lua/precognition/vertical_motions.lua index 6188006..19c6b91 100644 --- a/lua/precognition/vertical_motions.lua +++ b/lua/precognition/vertical_motions.lua @@ -5,17 +5,19 @@ function M.file_start() return 1 end ----@param bufnr integer +---@param bufnr? integer ---@return integer function M.file_end(bufnr) + bufnr = bufnr or vim.api.nvim_get_current_buf() return vim.api.nvim_buf_line_count(bufnr) end ----@param buf integer +---@param bufnr? integer ---@return integer | nil -function M.next_paragraph_line(buf) +function M.next_paragraph_line(bufnr) + bufnr = bufnr or vim.api.nvim_get_current_buf() local loc - vim.api.nvim_buf_call(buf, function() + vim.api.nvim_buf_call(bufnr, function() loc = vim.fn.search("^\\_s*$", "nW", vim.fn.line("w$")) end) if loc == 0 then @@ -24,11 +26,12 @@ function M.next_paragraph_line(buf) return loc end ----@param buf integer +---@param bufnr? integer ---@return integer | nil -function M.prev_paragraph_line(buf) +function M.prev_paragraph_line(bufnr) + bufnr = bufnr or vim.api.nvim_get_current_buf() local loc - vim.api.nvim_buf_call(buf, function() + vim.api.nvim_buf_call(bufnr, function() loc = vim.fn.search("^\\_s*$", "bnW", vim.fn.line("w0")) end) if loc == 0 then From d6a4d91521fb10e7afdd0a757499ea05cec8c6d2 Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 22:59:39 +0100 Subject: [PATCH 13/17] refactor(horizontal_motions): unified interface --- lua/precognition/horizontal_motions.lua | 33 +-- lua/precognition/init.lua | 10 +- .../precognition/horizontal_motions_spec.lua | 192 +++++++++--------- 3 files changed, 120 insertions(+), 115 deletions(-) diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua index ca59fa3..c1a64e0 100644 --- a/lua/precognition/horizontal_motions.lua +++ b/lua/precognition/horizontal_motions.lua @@ -3,8 +3,10 @@ local utils = require("precognition.utils") local M = {} ---@param str string ----@return integer -function M.line_start_non_whitespace(str) +---@param _cursorcol integer +---@param _linelen integer +---@return integer | nil +function M.line_start_non_whitespace(str, _cursorcol, _linelen) return str:find("%S") or 0 end @@ -15,10 +17,11 @@ function M.line_end(len) end ---@param str string ----@param start integer +---@param cursorcol integer +---@param _linelen integer ---@return integer | nil -function M.next_word_boundary(str, start) - local offset = start +function M.next_word_boundary(str, cursorcol, _linelen) + local offset = cursorcol local len = vim.fn.strcharlen(str) local char = vim.fn.strcharpart(str, offset - 1, 1) local c_class = utils.char_class(char) @@ -42,14 +45,15 @@ function M.next_word_boundary(str, start) end ---@param str string ----@param start integer +---@param cursorcol integer +---@param _linelen integer ---@return integer | nil -function M.end_of_word(str, start) +function M.end_of_word(str, cursorcol, _linelen) local len = vim.fn.strcharlen(str) - if start >= len then + if cursorcol >= len then return nil end - local offset = start + local offset = cursorcol local char = vim.fn.strcharpart(str, offset - 1, 1) local c_class = utils.char_class(char) local next_char_class = @@ -75,9 +79,9 @@ function M.end_of_word(str, start) end if c_class == 0 or next_char_class == 0 then - local next_word_start = M.next_word_boundary(str, offset) + local next_word_start = M.next_word_boundary(str, offset, 0) if next_word_start then - rev_offset = M.end_of_word(str, next_word_start + 1) + rev_offset = M.end_of_word(str, next_word_start + 1, 0) end end @@ -92,11 +96,12 @@ function M.end_of_word(str, start) end ---@param str string ----@param start integer +---@param cursorcol integer +---@param _linelen integer ---@return integer | nil -function M.prev_word_boundary(str, start) +function M.prev_word_boundary(str, cursorcol, _linelen) local len = vim.fn.strcharlen(str) - local offset = start - 1 + local offset = cursorcol - 1 local char = vim.fn.strcharpart(str, offset - 1, 1) local c_class = utils.char_class(char) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 63986b0..d8750dd 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -172,11 +172,11 @@ local function on_cursor_hold() -- get char offsets for more complex motions. local virt_line = build_virt_line({ - ["w"] = hm.next_word_boundary(cur_line, cursorcol), - ["e"] = hm.end_of_word(cur_line, cursorcol), - ["b"] = hm.prev_word_boundary(cur_line, cursorcol), - ["^"] = hm.line_start_non_whitespace(cur_line), - ["$"] = hm.line_end(line_len), + ["w"] = hm.next_word_boundary(cur_line, cursorcol, line_len), + ["e"] = hm.end_of_word(cur_line, cursorcol, line_len), + ["b"] = hm.prev_word_boundary(cur_line, cursorcol, line_len), + ["^"] = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), + ["$"] = hm.line_end(cur_line, cursorcol, line_len), }, line_len) -- TODO: can we add indent lines to the virt line to match indent-blankline or similar (if installed)? diff --git a/tests/precognition/horizontal_motions_spec.lua b/tests/precognition/horizontal_motions_spec.lua index 043b8a0..fee0666 100644 --- a/tests/precognition/horizontal_motions_spec.lua +++ b/tests/precognition/horizontal_motions_spec.lua @@ -4,36 +4,36 @@ local eq = assert.are.same describe("boundaries", function() it("finds the next word boundary", function() - eq(5, hm.next_word_boundary("abc efg", 1)) - eq(5, hm.next_word_boundary("abc efg", 2)) - eq(5, hm.next_word_boundary("abc efg", 3)) - eq(5, hm.next_word_boundary("abc efg", 4)) - eq(nil, hm.next_word_boundary("abc efg", 5)) - eq(nil, hm.next_word_boundary("abc efg", 6)) - eq(nil, hm.next_word_boundary("abc efg", 7)) + eq(5, hm.next_word_boundary("abc efg", 1, 7)) + eq(5, hm.next_word_boundary("abc efg", 2, 7)) + eq(5, hm.next_word_boundary("abc efg", 3, 7)) + eq(5, hm.next_word_boundary("abc efg", 4, 7)) + eq(nil, hm.next_word_boundary("abc efg", 5, 7)) + eq(nil, hm.next_word_boundary("abc efg", 6, 7)) + eq(nil, hm.next_word_boundary("abc efg", 7, 7)) - eq(9, hm.next_word_boundary("slighly more complex test", 1)) - eq(9, hm.next_word_boundary("slighly more complex test", 2)) - eq(14, hm.next_word_boundary("slighly more complex test", 10)) - eq(14, hm.next_word_boundary("slighly more complex test", 13)) - eq(22, hm.next_word_boundary("slighly more complex test", 15)) - eq(22, hm.next_word_boundary("slighly more complex test", 21)) + eq(9, hm.next_word_boundary("slighly more complex test", 1, 22)) + eq(9, hm.next_word_boundary("slighly more complex test", 2, 22)) + eq(14, hm.next_word_boundary("slighly more complex test", 10, 22)) + eq(14, hm.next_word_boundary("slighly more complex test", 13, 22)) + eq(22, hm.next_word_boundary("slighly more complex test", 15, 22)) + eq(22, hm.next_word_boundary("slighly more complex test", 21, 22)) - eq(5, hm.next_word_boundary(" myFunction(example, stuff)", 1)) - eq(5, hm.next_word_boundary(" myFunction(example, stuff)", 2)) - eq(5, hm.next_word_boundary(" myFunction(example, stuff)", 3)) - eq(15, hm.next_word_boundary(" myFunction(example, stuff)", 5)) - eq(16, hm.next_word_boundary(" myFunction(example, stuff)", 15)) - eq(23, hm.next_word_boundary(" myFunction(example, stuff)", 16)) - eq(25, hm.next_word_boundary(" myFunction(example, stuff)", 23)) - eq(25, hm.next_word_boundary(" myFunction(example, stuff)", 24)) - eq(30, hm.next_word_boundary(" myFunction(example, stuff)", 25)) - eq(nil, hm.next_word_boundary(" myFunction(example, stuff)", 30)) + eq(5, hm.next_word_boundary(" myFunction(example, stuff)", 1, 30)) + eq(5, hm.next_word_boundary(" myFunction(example, stuff)", 2, 30)) + eq(5, hm.next_word_boundary(" myFunction(example, stuff)", 3, 30)) + eq(15, hm.next_word_boundary(" myFunction(example, stuff)", 5, 30)) + eq(16, hm.next_word_boundary(" myFunction(example, stuff)", 15, 30)) + eq(23, hm.next_word_boundary(" myFunction(example, stuff)", 16, 30)) + eq(25, hm.next_word_boundary(" myFunction(example, stuff)", 23, 30)) + eq(25, hm.next_word_boundary(" myFunction(example, stuff)", 24, 30)) + eq(30, hm.next_word_boundary(" myFunction(example, stuff)", 25, 30)) + eq(nil, hm.next_word_boundary(" myFunction(example, stuff)", 30, 30)) end) it("can walk string with w", function() local test_string = "abcdefg hijklmn opqrstu vwxyz" - local pos = hm.next_word_boundary(test_string, 1) + local pos = hm.next_word_boundary(test_string, 1, #test_string) if pos == nil then error("pos is nil") end @@ -41,136 +41,136 @@ describe("boundaries", function() if pos == nil then error("pos is nil") end - pos = hm.next_word_boundary(test_string, pos) + pos = hm.next_word_boundary(test_string, pos, #test_string) if pos == nil then error("pos is nil") end eq("o", test_string:sub(pos, pos)) - pos = hm.next_word_boundary(test_string, pos) + pos = hm.next_word_boundary(test_string, pos, #test_string) if pos == nil then error("pos is nil") end eq("v", test_string:sub(pos, pos)) - pos = hm.next_word_boundary(test_string, pos) + pos = hm.next_word_boundary(test_string, pos, #test_string) eq(nil, pos) end) describe("previous word boundary", function() it("finds the previous word boundary", function() - eq(nil, hm.prev_word_boundary("abc efg", 1)) - eq(1, hm.prev_word_boundary("abc efg", 2)) - eq(1, hm.prev_word_boundary("abc efg", 3)) - eq(1, hm.prev_word_boundary("abc efg", 4)) - eq(1, hm.prev_word_boundary("abc efg", 5)) - eq(5, hm.prev_word_boundary("abc efg", 6)) - eq(5, hm.prev_word_boundary("abc efg", 7)) + eq(nil, hm.prev_word_boundary("abc efg", 1, 7)) + eq(1, hm.prev_word_boundary("abc efg", 2, 7)) + eq(1, hm.prev_word_boundary("abc efg", 3, 7)) + eq(1, hm.prev_word_boundary("abc efg", 4, 7)) + eq(1, hm.prev_word_boundary("abc efg", 5, 7)) + eq(5, hm.prev_word_boundary("abc efg", 6, 7)) + eq(5, hm.prev_word_boundary("abc efg", 7, 7)) - eq(9, hm.prev_word_boundary("slighly more complex test", 10)) - eq(9, hm.prev_word_boundary("slighly more complex test", 11)) - eq(14, hm.prev_word_boundary("slighly more complex test", 15)) - eq(14, hm.prev_word_boundary("slighly more complex test", 16)) - eq(22, hm.prev_word_boundary("slighly more complex test", 23)) - eq(22, hm.prev_word_boundary("slighly more complex test", 24)) - eq(22, hm.prev_word_boundary("slighly more complex test", 25)) - eq(nil, hm.prev_word_boundary("slighly more complex test", 1)) + eq(9, hm.prev_word_boundary("slighly more complex test", 10, 22)) + eq(9, hm.prev_word_boundary("slighly more complex test", 11, 22)) + eq(14, hm.prev_word_boundary("slighly more complex test", 15, 22)) + eq(14, hm.prev_word_boundary("slighly more complex test", 16, 22)) + eq(22, hm.prev_word_boundary("slighly more complex test", 23, 22)) + eq(22, hm.prev_word_boundary("slighly more complex test", 24, 22)) + eq(22, hm.prev_word_boundary("slighly more complex test", 25, 22)) + eq(nil, hm.prev_word_boundary("slighly more complex test", 1, 22)) - eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 1)) - eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 2)) - eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 3)) - eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 4)) - eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 5)) - eq(5, hm.prev_word_boundary(" myFunction(example, stuff)", 6)) - eq(5, hm.prev_word_boundary(" myFunction(example, stuff)", 15)) - eq(15, hm.prev_word_boundary(" myFunction(example, stuff)", 16)) - eq(16, hm.prev_word_boundary(" myFunction(example, stuff)", 17)) - eq(16, hm.prev_word_boundary(" myFunction(example, stuff)", 18)) - eq(16, hm.prev_word_boundary(" myFunction(example, stuff)", 19)) - eq(23, hm.prev_word_boundary(" myFunction(example, stuff)", 25)) - eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 26)) - eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 27)) - eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 28)) - eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 29)) - eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 30)) + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 1, 30)) + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 2, 30)) + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 3, 30)) + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 4, 30)) + eq(nil, hm.prev_word_boundary(" myFunction(example, stuff)", 5, 30)) + eq(5, hm.prev_word_boundary(" myFunction(example, stuff)", 6, 30)) + eq(5, hm.prev_word_boundary(" myFunction(example, stuff)", 15, 30)) + eq(15, hm.prev_word_boundary(" myFunction(example, stuff)", 16, 30)) + eq(16, hm.prev_word_boundary(" myFunction(example, stuff)", 17, 30)) + eq(16, hm.prev_word_boundary(" myFunction(example, stuff)", 18, 30)) + eq(16, hm.prev_word_boundary(" myFunction(example, stuff)", 19, 30)) + eq(23, hm.prev_word_boundary(" myFunction(example, stuff)", 25, 30)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 26, 30)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 27, 30)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 28, 30)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 29, 30)) + eq(25, hm.prev_word_boundary(" myFunction(example, stuff)", 30, 30)) end) it("can walk string with b", function() local test_string = "abcdefg hijklmn opqrstu vwxyz" - local pos = hm.prev_word_boundary(test_string, 29) + local pos = hm.prev_word_boundary(test_string, 29, #test_string) if pos == nil then error("pos is nil") end eq("v", test_string:sub(pos, pos)) - pos = hm.prev_word_boundary(test_string, pos) + pos = hm.prev_word_boundary(test_string, pos, #test_string) if pos == nil then error("pos is nil") end eq("o", test_string:sub(pos, pos)) - pos = hm.prev_word_boundary(test_string, pos) + pos = hm.prev_word_boundary(test_string, pos, #test_string) if pos == nil then error("pos is nil") end eq("h", test_string:sub(pos, pos)) - pos = hm.prev_word_boundary(test_string, pos) + pos = hm.prev_word_boundary(test_string, pos, #test_string) eq(1, pos) end) end) describe("end of current word", function() it("finds the end of words", function() - eq(3, hm.end_of_word("abc efg", 1)) - eq(3, hm.end_of_word("abc efg", 2)) - eq(7, hm.end_of_word("abc efg", 3)) + eq(3, hm.end_of_word("abc efg", 1, 7)) + eq(3, hm.end_of_word("abc efg", 2, 7)) + eq(7, hm.end_of_word("abc efg", 3, 7)) - eq(7, hm.end_of_word("slighly more complex test", 1)) - eq(7, hm.end_of_word("slighly more complex test", 2)) - eq(12, hm.end_of_word("slighly more complex test", 10)) - eq(20, hm.end_of_word("slighly more complex test", 13)) - eq(20, hm.end_of_word("slighly more complex test", 15)) - eq(25, hm.end_of_word("slighly more complex test", 21)) + eq(7, hm.end_of_word("slighly more complex test", 1, 22)) + eq(7, hm.end_of_word("slighly more complex test", 2, 22)) + eq(12, hm.end_of_word("slighly more complex test", 10, 22)) + eq(20, hm.end_of_word("slighly more complex test", 13, 22)) + eq(20, hm.end_of_word("slighly more complex test", 15, 22)) + eq(25, hm.end_of_word("slighly more complex test", 21, 22)) - eq(14, hm.end_of_word(" myFunction(example, stuff)", 1)) - eq(14, hm.end_of_word(" myFunction(example, stuff)", 2)) - eq(14, hm.end_of_word(" myFunction(example, stuff)", 3)) - eq(14, hm.end_of_word(" myFunction(example, stuff)", 5)) - eq(15, hm.end_of_word(" myFunction(example, stuff)", 14)) - eq(22, hm.end_of_word(" myFunction(example, stuff)", 15)) - eq(22, hm.end_of_word(" myFunction(example, stuff)", 16)) - eq(29, hm.end_of_word(" myFunction(example, stuff)", 23)) - eq(29, hm.end_of_word(" myFunction(example, stuff)", 24)) - eq(29, hm.end_of_word(" myFunction(example, stuff)", 25)) - eq(30, hm.end_of_word(" myFunction(example, stuff)", 29)) - eq(nil, hm.end_of_word(" myFunction(example, stuff)", 30)) + eq(14, hm.end_of_word(" myFunction(example, stuff)", 1, 30)) + eq(14, hm.end_of_word(" myFunction(example, stuff)", 2, 30)) + eq(14, hm.end_of_word(" myFunction(example, stuff)", 3, 30)) + eq(14, hm.end_of_word(" myFunction(example, stuff)", 5, 30)) + eq(15, hm.end_of_word(" myFunction(example, stuff)", 14, 30)) + eq(22, hm.end_of_word(" myFunction(example, stuff)", 15, 30)) + eq(22, hm.end_of_word(" myFunction(example, stuff)", 16, 30)) + eq(29, hm.end_of_word(" myFunction(example, stuff)", 23, 30)) + eq(29, hm.end_of_word(" myFunction(example, stuff)", 24, 30)) + eq(29, hm.end_of_word(" myFunction(example, stuff)", 25, 30)) + eq(30, hm.end_of_word(" myFunction(example, stuff)", 29, 30)) + eq(nil, hm.end_of_word(" myFunction(example, stuff)", 30, 30)) end) end) end) describe("edge case", function() it("can handle empty strings", function() - eq(nil, hm.next_word_boundary("", 1)) - eq(nil, hm.prev_word_boundary("", 1)) - eq(nil, hm.end_of_word("", 1)) + eq(nil, hm.next_word_boundary("", 1, 0)) + eq(nil, hm.prev_word_boundary("", 1, 0)) + eq(nil, hm.end_of_word("", 1, 0)) end) it("can handle strings with only whitespace", function() - eq(nil, hm.next_word_boundary(" ", 1)) - eq(nil, hm.prev_word_boundary(" ", 1)) - eq(nil, hm.end_of_word(" ", 1)) + eq(nil, hm.next_word_boundary(" ", 1, 1)) + eq(nil, hm.prev_word_boundary(" ", 1, 1)) + eq(nil, hm.end_of_word(" ", 1, 1)) 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)) - eq(1, hm.prev_word_boundary(str, 4)) - eq(10, hm.end_of_word(str, 4)) + eq(5, hm.next_word_boundary(str, 4, #str)) + eq(1, hm.prev_word_boundary(str, 4, #str)) + eq(10, hm.end_of_word(str, 4, #str)) end) it( "can handle strings with multiple consecutive special characters", function() local str = "this || that" - eq(9, hm.next_word_boundary(str, 6)) - eq(1, hm.prev_word_boundary(str, 6)) - eq(7, hm.end_of_word(str, 6)) + eq(9, hm.next_word_boundary(str, 6, #str)) + eq(1, hm.prev_word_boundary(str, 6, #str)) + eq(7, hm.end_of_word(str, 6, #str)) end ) end) From 62df2df97283231afe6b798c4746d6461615b315 Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 23:03:24 +0100 Subject: [PATCH 14/17] chore: wider formatting --- .stylua.toml | 2 +- lua/precognition/horizontal_motions.lua | 11 +-- lua/precognition/init.lua | 38 ++----- tests/precognition/gutter_hints_spec.lua | 56 +++++------ .../precognition/horizontal_motions_spec.lua | 15 ++- tests/precognition/vertical_motions_spec.lua | 98 +++++++++---------- tests/precognition/virtline_spec.lua | 27 +++-- 7 files changed, 100 insertions(+), 147 deletions(-) diff --git a/.stylua.toml b/.stylua.toml index 0b2e146..c537618 100644 --- a/.stylua.toml +++ b/.stylua.toml @@ -1,4 +1,4 @@ -column_width = 80 +column_width = 120 line_endings = "Unix" indent_type = "Spaces" indent_width = 4 diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua index c1a64e0..b9103c8 100644 --- a/lua/precognition/horizontal_motions.lua +++ b/lua/precognition/horizontal_motions.lua @@ -56,19 +56,14 @@ function M.end_of_word(str, cursorcol, _linelen) local offset = cursorcol local char = vim.fn.strcharpart(str, offset - 1, 1) local c_class = utils.char_class(char) - local next_char_class = - utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) + local next_char_class = utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) local rev_offset - if - (c_class == 1 and next_char_class ~= 1) - or (next_char_class == 1 and c_class ~= 1) - then + if (c_class == 1 and next_char_class ~= 1) or (next_char_class == 1 and c_class ~= 1) then offset = offset + 1 char = vim.fn.strcharpart(str, offset - 1, 1) c_class = utils.char_class(char) - next_char_class = - utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) + next_char_class = utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1)) end if c_class ~= 0 and next_char_class ~= 0 then diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index d8750dd..f15701f 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -78,11 +78,7 @@ local function build_virt_line(marks, line_len) if existing == " " and existing ~= hint then line = line:sub(1, col - 1) .. hint .. line:sub(col + 1) else -- if the character is not a space, then we need to check the prio - if - existing ~= "" - and config.hints[mark].prio - > config.hints[existing].prio - then + if existing ~= "" and config.hints[mark].prio > config.hints[existing].prio then line = line:sub(1, col - 1) .. hint .. line:sub(col + 1) end end @@ -115,36 +111,24 @@ local function apply_gutter_hints(gutter_hints, buf) for hint, loc in pairs(gutter_hints) do if config.gutterHints[hint] and loc ~= 0 and loc ~= nil then if gutter_signs_cache[hint] then - vim.fn.sign_unplace( - gutter_group, - { id = gutter_signs_cache[hint].id } - ) + vim.fn.sign_unplace(gutter_group, { id = gutter_signs_cache[hint].id }) gutter_signs_cache[hint] = nil end vim.fn.sign_define(gutter_name_prefix .. hint, { text = config.gutterHints[hint].text, texthl = "Comment", }) - local ok, res = pcall( - vim.fn.sign_place, - 0, - gutter_group, - gutter_name_prefix .. config.gutterHints[hint].text, - buf, - { + local ok, res = + pcall(vim.fn.sign_place, 0, gutter_group, gutter_name_prefix .. config.gutterHints[hint].text, buf, { lnum = loc, priority = 100, - } - ) + }) if ok then gutter_signs_cache[hint] = { line = loc, id = res } end if not ok and loc ~= 0 then vim.notify_once( - "Failed to place sign: " - .. config.gutterHints[hint].text - .. " at line " - .. loc, + "Failed to place sign: " .. config.gutterHints[hint].text .. " at line " .. loc, vim.log.levels.WARN ) end @@ -160,8 +144,7 @@ local function on_cursor_hold() end local tab_width = vim.bo.expandtab and vim.bo.shiftwidth or vim.bo.tabstop - local cur_line = - vim.api.nvim_get_current_line():gsub("\t", string.rep(" ", tab_width)) + local cur_line = vim.api.nvim_get_current_line():gsub("\t", string.rep(" ", tab_width)) local line_len = vim.fn.strcharlen(cur_line) -- local after_cursor = vim.fn.strcharpart(cur_line, cursorcol + 1) -- local before_cursor = vim.fn.strcharpart(cur_line, 0, cursorcol - 1) @@ -182,12 +165,7 @@ local function on_cursor_hold() -- TODO: can we add indent lines to the virt line to match indent-blankline or similar (if installed)? -- create (or overwrite) the extmark - if - vim.api.nvim_get_option_value( - "buftype", - { buf = vim.api.nvim_get_current_buf() } - ) == "" - then + if vim.api.nvim_get_option_value("buftype", { buf = vim.api.nvim_get_current_buf() }) == "" then extmark = vim.api.nvim_buf_set_extmark(0, ns, cursorline - 1, 0, { id = extmark, -- reuse the same extmark if it exists virt_lines = { virt_line }, diff --git a/tests/precognition/gutter_hints_spec.lua b/tests/precognition/gutter_hints_spec.lua index 8861871..6132fca 100644 --- a/tests/precognition/gutter_hints_spec.lua +++ b/tests/precognition/gutter_hints_spec.lua @@ -28,37 +28,31 @@ describe("Gutter hints table", function() }, hints) end) - it( - "should return a table with the correct keys when the buffer is empty", - function() - local testBuf = vim.api.nvim_create_buf(true, true) - vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, {}) - vim.api.nvim_set_current_buf(testBuf) - - local hints = precognition.build_gutter_hints(testBuf) - - eq({ - ["gg"] = 1, - ["G"] = 1, - }, hints) - end - ) - - it( - "should return a table with the correct keys when the buffer is a single line", - function() - local testBuf = vim.api.nvim_create_buf(true, true) - vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { "ABC" }) - vim.api.nvim_set_current_buf(testBuf) - vim.api.nvim_win_set_cursor(0, { 1, 1 }) - - local hints = precognition.build_gutter_hints(testBuf) - eq({ - ["gg"] = 1, - ["G"] = 1, - }, hints) - end - ) + it("should return a table with the correct keys when the buffer is empty", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, {}) + vim.api.nvim_set_current_buf(testBuf) + + local hints = precognition.build_gutter_hints(testBuf) + + eq({ + ["gg"] = 1, + ["G"] = 1, + }, hints) + end) + + it("should return a table with the correct keys when the buffer is a single line", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { "ABC" }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + + local hints = precognition.build_gutter_hints(testBuf) + eq({ + ["gg"] = 1, + ["G"] = 1, + }, hints) + end) it("moving the cursor will update the hints table", function() local testBuf = vim.api.nvim_create_buf(true, true) diff --git a/tests/precognition/horizontal_motions_spec.lua b/tests/precognition/horizontal_motions_spec.lua index fee0666..cd86e49 100644 --- a/tests/precognition/horizontal_motions_spec.lua +++ b/tests/precognition/horizontal_motions_spec.lua @@ -164,13 +164,10 @@ describe("edge case", function() eq(10, hm.end_of_word(str, 4, #str)) end) - it( - "can handle strings with multiple consecutive special characters", - function() - local str = "this || that" - eq(9, hm.next_word_boundary(str, 6, #str)) - eq(1, hm.prev_word_boundary(str, 6, #str)) - eq(7, hm.end_of_word(str, 6, #str)) - end - ) + it("can handle strings with multiple consecutive special characters", function() + local str = "this || that" + eq(9, hm.next_word_boundary(str, 6, #str)) + eq(1, hm.prev_word_boundary(str, 6, #str)) + eq(7, hm.end_of_word(str, 6, #str)) + end) end) diff --git a/tests/precognition/vertical_motions_spec.lua b/tests/precognition/vertical_motions_spec.lua index 5606028..89249f6 100644 --- a/tests/precognition/vertical_motions_spec.lua +++ b/tests/precognition/vertical_motions_spec.lua @@ -101,56 +101,50 @@ describe("gutter motion locations", function() eq(5, prev_line) end) - it( - "can find the prev paragraph in a file with multiple consecutive blank lines", - function() - local testBuf = vim.api.nvim_create_buf(true, true) - vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { - "ABC", - "DEF", - "", - "", - "GHI", - "", - "JKL", - "", - "", - "", - "MNO", - }) - vim.api.nvim_set_current_buf(testBuf) - vim.api.nvim_win_set_cursor(0, { 10, 0 }) - - local prev_line = vm.prev_paragraph_line(testBuf) - -- TODO: This is a bug, it should be 5 - -- If there are multiple consecutive blank lines, it will currentlu return the next blankline - -- not the next blank line preceding content - eq(9, prev_line) - end - ) - - it( - "can find the next paragraph in a file with multiple consecutive blank lines", - function() - local testBuf = vim.api.nvim_create_buf(true, true) - vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { - "ABC", - "DEF", - "", - "", - "", - "", - "GHI", - "", - "JKL", - }) - vim.api.nvim_set_current_buf(testBuf) - vim.api.nvim_win_set_cursor(0, { 3, 0 }) - - local next_line = vm.next_paragraph_line(testBuf) - -- TODO: This is a bug, it should be 8 - -- Same description as above - eq(4, next_line) - end - ) + it("can find the prev paragraph in a file with multiple consecutive blank lines", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { + "ABC", + "DEF", + "", + "", + "GHI", + "", + "JKL", + "", + "", + "", + "MNO", + }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 10, 0 }) + + local prev_line = vm.prev_paragraph_line(testBuf) + -- TODO: This is a bug, it should be 5 + -- If there are multiple consecutive blank lines, it will currentlu return the next blankline + -- not the next blank line preceding content + eq(9, prev_line) + end) + + it("can find the next paragraph in a file with multiple consecutive blank lines", function() + local testBuf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_lines(testBuf, 0, -1, false, { + "ABC", + "DEF", + "", + "", + "", + "", + "GHI", + "", + "JKL", + }) + vim.api.nvim_set_current_buf(testBuf) + vim.api.nvim_win_set_cursor(0, { 3, 0 }) + + local next_line = vm.next_paragraph_line(testBuf) + -- TODO: This is a bug, it should be 8 + -- Same description as above + eq(4, next_line) + end) end) diff --git a/tests/precognition/virtline_spec.lua b/tests/precognition/virtline_spec.lua index 777337a..d584c52 100644 --- a/tests/precognition/virtline_spec.lua +++ b/tests/precognition/virtline_spec.lua @@ -34,18 +34,15 @@ describe("Build Virtual Line", function() eq(10, #virtual_line[1][1]) end) - it( - "can build a virtual line with a single mark at the beginning", - function() - ---@type Precognition.VirtLine - local marks = { - ["^"] = 1, - } - local virtual_line = precognition.build_virt_line(marks, 10) - eq("^ ", virtual_line[1][1]) - eq(10, #virtual_line[1][1]) - end - ) + it("can build a virtual line with a single mark at the beginning", function() + ---@type Precognition.VirtLine + local marks = { + ["^"] = 1, + } + local virtual_line = precognition.build_virt_line(marks, 10) + eq("^ ", virtual_line[1][1]) + eq(10, #virtual_line[1][1]) + end) it("can build a complex virtual line", function() ---@type Precognition.VirtLine @@ -77,8 +74,7 @@ describe("Build Virtual Line", function() it("example virtual line", function() local line = "abcdef ghijkl mnopqr stuvwx yz" local cursorcol = 2 - local tab_width = vim.bo.expandtab and vim.bo.shiftwidth - or vim.bo.tabstop + local tab_width = vim.bo.expandtab and vim.bo.shiftwidth or vim.bo.tabstop local cur_line = line:gsub("\t", string.rep(" ", tab_width)) local line_len = vim.fn.strcharlen(cur_line) @@ -98,8 +94,7 @@ describe("Build Virtual Line", function() local line = " abc def" -- abc def local cursorcol = 5 - local tab_width = vim.bo.expandtab and vim.bo.shiftwidth - or vim.bo.tabstop + local tab_width = vim.bo.expandtab and vim.bo.shiftwidth or vim.bo.tabstop local cur_line = line:gsub("\t", string.rep(" ", tab_width)) local line_len = vim.fn.strcharlen(cur_line) From 06831a1ffd040cf9a165a60f266d9b9edcd347a1 Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 23:14:25 +0100 Subject: [PATCH 15/17] refactor(gutter_hints): unified interface --- lua/precognition/init.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index f15701f..187960c 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -102,10 +102,11 @@ local function build_gutter_hints() end ---@param gutter_hints Precognition.GutterHints ----@param buf integer == bufnr +---@param bufnr? integer -- buffer number ---@return nil -local function apply_gutter_hints(gutter_hints, buf) - if vim.api.nvim_get_option_value("buftype", { buf = buf }) ~= "" then +local function apply_gutter_hints(gutter_hints, bufnr) + bufnr = bufnr or vim.api.nvim_get_current_buf() + if vim.api.nvim_get_option_value("buftype", { buf = bufnr }) ~= "" then return end for hint, loc in pairs(gutter_hints) do @@ -119,7 +120,7 @@ local function apply_gutter_hints(gutter_hints, buf) texthl = "Comment", }) local ok, res = - pcall(vim.fn.sign_place, 0, gutter_group, gutter_name_prefix .. config.gutterHints[hint].text, buf, { + pcall(vim.fn.sign_place, 0, gutter_group, gutter_name_prefix .. config.gutterHints[hint].text, bufnr, { lnum = loc, priority = 100, }) @@ -171,7 +172,7 @@ local function on_cursor_hold() virt_lines = { virt_line }, }) end - apply_gutter_hints(build_gutter_hints(), vim.api.nvim_get_current_buf()) + apply_gutter_hints(build_gutter_hints()) dirty = false end From 432b9339295a3d099a6a15e7ebf67030de577b63 Mon Sep 17 00:00:00 2001 From: tris203 Date: Fri, 3 May 2024 23:15:07 +0100 Subject: [PATCH 16/17] fix(line_end): fix unified interface --- lua/precognition/horizontal_motions.lua | 10 ++++++---- tests/precognition/virtline_spec.lua | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua index b9103c8..e30c117 100644 --- a/lua/precognition/horizontal_motions.lua +++ b/lua/precognition/horizontal_motions.lua @@ -10,10 +10,12 @@ function M.line_start_non_whitespace(str, _cursorcol, _linelen) return str:find("%S") or 0 end ----@param len integer ----@return integer -function M.line_end(len) - return len +---@param _str string +---@param _cursorcol integer +---@param linelen integer +---@return integer | nil +function M.line_end(_str, _cursorcol, linelen) + return linelen or nil end ---@param str string diff --git a/tests/precognition/virtline_spec.lua b/tests/precognition/virtline_spec.lua index d584c52..221c9ab 100644 --- a/tests/precognition/virtline_spec.lua +++ b/tests/precognition/virtline_spec.lua @@ -79,11 +79,11 @@ describe("Build Virtual Line", function() local line_len = vim.fn.strcharlen(cur_line) local virt_line = precognition.build_virt_line({ - ["w"] = hm.next_word_boundary(cur_line, cursorcol), - ["e"] = hm.end_of_word(cur_line, cursorcol), - ["b"] = hm.prev_word_boundary(cur_line, cursorcol), - ["^"] = hm.line_start_non_whitespace(cur_line), - ["$"] = hm.line_end(line_len), + ["w"] = hm.next_word_boundary(cur_line, cursorcol, line_len), + ["e"] = hm.end_of_word(cur_line, cursorcol, line_len), + ["b"] = hm.prev_word_boundary(cur_line, cursorcol, line_len), + ["^"] = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), + ["$"] = hm.line_end(cur_line, cursorcol, line_len), }, line_len) eq("b e w $", virt_line[1][1]) @@ -99,11 +99,11 @@ describe("Build Virtual Line", function() local line_len = vim.fn.strcharlen(cur_line) local virt_line = precognition.build_virt_line({ - ["w"] = hm.next_word_boundary(cur_line, cursorcol), - ["e"] = hm.end_of_word(cur_line, cursorcol), - ["b"] = hm.prev_word_boundary(cur_line, cursorcol), - ["^"] = hm.line_start_non_whitespace(cur_line), - ["$"] = hm.line_end(line_len), + ["w"] = hm.next_word_boundary(cur_line, cursorcol, line_len), + ["e"] = hm.end_of_word(cur_line, cursorcol, line_len), + ["b"] = hm.prev_word_boundary(cur_line, cursorcol, line_len), + ["^"] = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), + ["$"] = hm.line_end(cur_line, cursorcol, line_len), }, line_len) eq(" ^ e w $", virt_line[1][1]) From d0f77cc015cee8b720d9bcae98fa2253f0c7b8f5 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 00:21:18 +0100 Subject: [PATCH 17/17] refactor(vertical_motions): lua implementation of { and } --- lua/precognition/vertical_motions.lua | 50 ++++++++++++++++---- tests/precognition/gutter_hints_spec.lua | 11 +++++ tests/precognition/vertical_motions_spec.lua | 9 +--- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/lua/precognition/vertical_motions.lua b/lua/precognition/vertical_motions.lua index 19c6b91..fd2d724 100644 --- a/lua/precognition/vertical_motions.lua +++ b/lua/precognition/vertical_motions.lua @@ -18,11 +18,27 @@ function M.next_paragraph_line(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() local loc vim.api.nvim_buf_call(bufnr, function() - loc = vim.fn.search("^\\_s*$", "nW", vim.fn.line("w$")) + local found + local visibleline = vim.fn.line("w$") + local buffcontent = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local cursorline, _ = unpack(vim.api.nvim_win_get_cursor(0)) + while not found and cursorline < visibleline do + local cursorlinecontent = buffcontent[cursorline] + while cursorline < visibleline and cursorlinecontent:match("^%s*$") do + cursorline = cursorline + 1 + cursorlinecontent = buffcontent[cursorline] + end + -- find next blank line below + while cursorline < visibleline and not found do + cursorline = cursorline + 1 + cursorlinecontent = buffcontent[cursorline] + if cursorlinecontent:match("^%s*$") then + found = true + end + end + end + loc = cursorline end) - if loc == 0 then - return nil - end return loc end @@ -32,11 +48,29 @@ function M.prev_paragraph_line(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() local loc vim.api.nvim_buf_call(bufnr, function() - loc = vim.fn.search("^\\_s*$", "bnW", vim.fn.line("w0")) + local found + local visibleline = vim.fn.line("w0") + local buffcontent = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local cursorline, _ = unpack(vim.api.nvim_win_get_cursor(0)) + while not found and cursorline > visibleline do + local cursorlinecontent = buffcontent[cursorline] + while cursorline > visibleline and cursorlinecontent:match("^%s*$") do + cursorline = cursorline - 1 + cursorlinecontent = buffcontent[cursorline] + end + -- find next blank line above + while cursorline > visibleline and not found do + cursorline = cursorline - 1 + cursorlinecontent = buffcontent[cursorline] + if cursorlinecontent:match("^%s*$") then + found = true + end + end + end + loc = cursorline end) - if loc == 0 then - return nil - end + --check if line above is empty + --if so, return the line above that return loc end diff --git a/tests/precognition/gutter_hints_spec.lua b/tests/precognition/gutter_hints_spec.lua index 6132fca..0c88d59 100644 --- a/tests/precognition/gutter_hints_spec.lua +++ b/tests/precognition/gutter_hints_spec.lua @@ -37,6 +37,8 @@ describe("Gutter hints table", function() eq({ ["gg"] = 1, + ["{"] = 1, + ["}"] = 1, ["G"] = 1, }, hints) end) @@ -50,6 +52,8 @@ describe("Gutter hints table", function() local hints = precognition.build_gutter_hints(testBuf) eq({ ["gg"] = 1, + ["{"] = 1, + ["}"] = 1, ["G"] = 1, }, hints) end) @@ -97,6 +101,8 @@ describe("Gutter hints table", function() local hints = precognition.build_gutter_hints(testBuf) eq({ ["gg"] = 1, + ["{"] = 1, + ["}"] = 1, ["G"] = 1, }, hints) @@ -105,6 +111,8 @@ describe("Gutter hints table", function() hints = precognition.build_gutter_hints(testBuf) eq({ ["gg"] = 1, + ["{"] = 1, + ["}"] = 2, ["G"] = 2, }, hints) @@ -114,6 +122,8 @@ describe("Gutter hints table", function() eq({ ["gg"] = 1, + ["{"] = 1, + ["}"] = 3, ["G"] = 3, }, hints) @@ -124,6 +134,7 @@ describe("Gutter hints table", function() eq({ ["gg"] = 1, + ["{"] = 1, ["}"] = 4, ["G"] = 5, }, hints) diff --git a/tests/precognition/vertical_motions_spec.lua b/tests/precognition/vertical_motions_spec.lua index 89249f6..ac8e03f 100644 --- a/tests/precognition/vertical_motions_spec.lua +++ b/tests/precognition/vertical_motions_spec.lua @@ -120,10 +120,7 @@ describe("gutter motion locations", function() vim.api.nvim_win_set_cursor(0, { 10, 0 }) local prev_line = vm.prev_paragraph_line(testBuf) - -- TODO: This is a bug, it should be 5 - -- If there are multiple consecutive blank lines, it will currentlu return the next blankline - -- not the next blank line preceding content - eq(9, prev_line) + eq(6, prev_line) end) it("can find the next paragraph in a file with multiple consecutive blank lines", function() @@ -143,8 +140,6 @@ describe("gutter motion locations", function() vim.api.nvim_win_set_cursor(0, { 3, 0 }) local next_line = vm.next_paragraph_line(testBuf) - -- TODO: This is a bug, it should be 8 - -- Same description as above - eq(4, next_line) + eq(8, next_line) end) end)