From f1f6b48de4f9479ad13d27f03d6de26011cf70a2 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 10:04:12 +0100 Subject: [PATCH 01/17] fix: luadoc defs --- lua/precognition/init.lua | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 187960c..075e8cd 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -3,18 +3,35 @@ local vm = require("precognition.vertical_motions") local M = {} ----@alias SupportedHints "'^'" | "'b'" | "'w'" | "'$'" +---@alias SupportedHints "^" | "$" | "w" | "e" | "b" ---@alias SupportedGutterHints "G" | "gg" | "{" | "}" +---@class HintOpts +---@field text string +---@field prio integer + +---@class HintConfig +---@field w HintOpts +---@field e HintOpts +---@field b HintOpts +---@field ["^"] HintOpts +---@field ["$"] HintOpts + +---@class GutterHintConfig +---@field G HintOpts +---@field gg HintOpts +---@field ["{"] HintOpts +---@field ["}"] HintOpts + ---@class Precognition.Config ---@field startVisible boolean ----@field hints { SupportedHints: { text: string, prio: integer } } ----@field gutterHints { SupportedGutterHints: { text: string, prio: integer } } +---@field hints HintConfig +---@field gutterHints GutterHintConfig ---@class Precognition.PartialConfig ---@field startVisible? boolean ----@field hints? { SupportedHints: { text: string, prio: integer } } ----@field gutterHints? { SupportedGutterHints: { text: string, prio: integer } } +---@field hints? HintConfig +---@field gutterHints? GutterHintConfig ---@alias Precognition.VirtLine { SupportedHints: integer | nil } From e720d53404d36ed1b1768e1fb048635a7185dba6 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 14:18:29 +0100 Subject: [PATCH 02/17] fix: more type safety for DX --- lua/precognition/horizontal_motions.lua | 20 +-- lua/precognition/init.lua | 116 +++++++++++------- lua/precognition/vertical_motions.lua | 12 +- tests/precognition/gutter_hints_spec.lua | 36 +++--- .../precognition/horizontal_motions_spec.lua | 66 +++++----- tests/precognition/virtline_spec.lua | 45 ++++--- 6 files changed, 161 insertions(+), 134 deletions(-) diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua index e30c117..3f7b91e 100644 --- a/lua/precognition/horizontal_motions.lua +++ b/lua/precognition/horizontal_motions.lua @@ -5,7 +5,7 @@ local M = {} ---@param str string ---@param _cursorcol integer ---@param _linelen integer ----@return integer | nil +---@return PlaceLoc function M.line_start_non_whitespace(str, _cursorcol, _linelen) return str:find("%S") or 0 end @@ -13,7 +13,7 @@ end ---@param _str string ---@param _cursorcol integer ---@param linelen integer ----@return integer | nil +---@return PlaceLoc function M.line_end(_str, _cursorcol, linelen) return linelen or nil end @@ -21,7 +21,7 @@ end ---@param str string ---@param cursorcol integer ---@param _linelen integer ----@return integer | nil +---@return PlaceLoc function M.next_word_boundary(str, cursorcol, _linelen) local offset = cursorcol local len = vim.fn.strcharlen(str) @@ -40,7 +40,7 @@ function M.next_word_boundary(str, cursorcol, _linelen) char = vim.fn.strcharpart(str, offset - 1, 1) end if offset > len then - return nil + return 0 end return offset @@ -49,11 +49,11 @@ end ---@param str string ---@param cursorcol integer ---@param _linelen integer ----@return integer | nil +---@return PlaceLoc function M.end_of_word(str, cursorcol, _linelen) local len = vim.fn.strcharlen(str) if cursorcol >= len then - return nil + return 0 end local offset = cursorcol local char = vim.fn.strcharpart(str, offset - 1, 1) @@ -83,7 +83,7 @@ function M.end_of_word(str, cursorcol, _linelen) end if rev_offset ~= nil and rev_offset <= 0 then - return nil + return 0 end if rev_offset ~= nil then @@ -95,7 +95,7 @@ end ---@param str string ---@param cursorcol integer ---@param _linelen integer ----@return integer | nil +---@return PlaceLoc function M.prev_word_boundary(str, cursorcol, _linelen) local len = vim.fn.strcharlen(str) local offset = cursorcol - 1 @@ -116,12 +116,12 @@ function M.prev_word_boundary(str, cursorcol, _linelen) --if remaining string is whitespace, return nil_wrap local remaining = string.sub(str, offset) if remaining:match("^%s*$") and #remaining > 0 then - return nil + return 0 end end if offset == nil or offset > len or offset < 0 then - return nil + return 0 end return offset + 1 end diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 075e8cd..f1786b8 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -3,25 +3,28 @@ local vm = require("precognition.vertical_motions") local M = {} ----@alias SupportedHints "^" | "$" | "w" | "e" | "b" ----@alias SupportedGutterHints "G" | "gg" | "{" | "}" +---@alias Dollar "$" +---@alias PrevParagraph "{" +---@alias NextParagraph "}" ---@class HintOpts ---@field text string ---@field prio integer ----@class HintConfig +---@alias PlaceLoc integer + +---@class (exact) HintConfig ---@field w HintOpts ---@field e HintOpts ---@field b HintOpts ----@field ["^"] HintOpts ----@field ["$"] HintOpts +---@field Carat HintOpts +---@field Dollar HintOpts ---@class GutterHintConfig ---@field G HintOpts ---@field gg HintOpts ----@field ["{"] HintOpts ----@field ["}"] HintOpts +---@field PrevParagraph HintOpts +---@field NextParagraph HintOpts ---@class Precognition.Config ---@field startVisible boolean @@ -33,28 +36,38 @@ local M = {} ---@field hints? HintConfig ---@field gutterHints? GutterHintConfig ----@alias Precognition.VirtLine { SupportedHints: integer | nil } - ----@alias Precognition.GutterHints { SupportedGutterHints: integer | nil } +---@class (exact) Precognition.VirtLine +---@field w PlaceLoc +---@field e PlaceLoc +---@field b PlaceLoc +---@field Carat PlaceLoc +---@field Dollar PlaceLoc + +---@class (exact) Precognition.GutterHints +---@field G PlaceLoc +---@field gg PlaceLoc +---@field PrevParagraph PlaceLoc +---@field NextParagraph PlaceLoc + +---@type HintConfig +local defaultHintConfig = { + Carat = { text = "^", prio = 1 }, + Dollar = { text = "$", prio = 1 }, + w = { text = "w", prio = 10 }, + b = { text = "b", prio = 9 }, + e = { text = "e", prio = 8 }, +} ---@type Precognition.Config local default = { startVisible = true, - hints = { - ["^"] = { text = "^", prio = 1 }, - ["$"] = { text = "$", prio = 1 }, - ["w"] = { text = "w", prio = 10 }, - -- ["W"] = "W", - ["b"] = { text = "b", prio = 9 }, - ["e"] = { text = "e", prio = 8 }, - -- ["ge"] = "ge", -- should we support multi-char / multi-byte hints? - }, + hints = defaultHintConfig, gutterHints = { --prio is not currentlt used for gutter hints - ["G"] = { text = "G", prio = 1 }, - ["gg"] = { text = "gg", prio = 1 }, - ["{"] = { text = "{", prio = 1 }, - ["}"] = { text = "}", prio = 1 }, + G = { text = "G", prio = 1 }, + gg = { text = "gg", prio = 1 }, + PrevParagraph = { text = "{", prio = 1 }, + NextParagraph = { text = "}", prio = 1 }, }, } @@ -83,6 +96,12 @@ local gutter_group = "precognition_gutter" ---@param line_len integer ---@return table local function build_virt_line(marks, line_len) + if not marks then + return {} + end + if line_len == 0 then + return {} + end local virt_line = {} local line = string.rep(" ", line_len) @@ -90,12 +109,19 @@ local function build_virt_line(marks, line_len) local hint = config.hints[mark].text or mark local col = loc - if col ~= nil then + if col ~= 0 then local existing = line:sub(col, col) 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 + local existingKey + for key, value in pairs(config.hints) do + if value.text == existing then + existingKey = key + break + end + end + if existing ~= " " and config.hints[mark].prio > config.hints[existingKey].prio then line = line:sub(1, col - 1) .. hint .. line:sub(col + 1) end end @@ -108,13 +134,13 @@ end ---@return Precognition.GutterHints local function build_gutter_hints() + ---@type Precognition.GutterHints local gutter_hints = { - ["G"] = vm.file_end(), - ["gg"] = vm.file_start(), - ["{"] = vm.prev_paragraph_line(), - ["}"] = vm.next_paragraph_line(), + G = vm.file_end(), + gg = vm.file_start(), + PrevParagraph = vm.prev_paragraph_line(), + NextParagraph = vm.next_paragraph_line(), } - return gutter_hints end @@ -136,17 +162,16 @@ local function apply_gutter_hints(gutter_hints, bufnr) 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, bufnr, { - lnum = loc, - priority = 100, - }) + local ok, res = pcall(vim.fn.sign_place, 0, gutter_group, gutter_name_prefix .. hint, bufnr, { + 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: " .. hint .. " at line " .. loc .. vim.inspect(res), vim.log.levels.WARN ) end @@ -171,14 +196,17 @@ local function on_cursor_hold() -- FIXME: Lua patterns don't play nice with utf-8, we need a better way to -- get char offsets for more complex motions. + -- + ---@type Precognition.VirtLine + local virtual_line_marks = { + Carat = hm.line_start_non_whitespace(cur_line, cursorcol, 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), + Dollar = hm.line_end(cur_line, cursorcol, line_len), + } - local virt_line = build_virt_line({ - ["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) + local virt_line = build_virt_line(virtual_line_marks, line_len) -- TODO: can we add indent lines to the virt line to match indent-blankline or similar (if installed)? @@ -216,7 +244,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()) end local function on_buf_leave(ev) diff --git a/lua/precognition/vertical_motions.lua b/lua/precognition/vertical_motions.lua index fd2d724..279d00f 100644 --- a/lua/precognition/vertical_motions.lua +++ b/lua/precognition/vertical_motions.lua @@ -1,22 +1,22 @@ local M = {} ----@return integer +---@return PlaceLoc function M.file_start() return 1 end ---@param bufnr? integer ----@return integer +---@return PlaceLoc function M.file_end(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() return vim.api.nvim_buf_line_count(bufnr) end ---@param bufnr? integer ----@return integer | nil +---@return PlaceLoc function M.next_paragraph_line(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() - local loc + local loc = 0 vim.api.nvim_buf_call(bufnr, function() local found local visibleline = vim.fn.line("w$") @@ -43,10 +43,10 @@ function M.next_paragraph_line(bufnr) end ---@param bufnr? integer ----@return integer | nil +---@return PlaceLoc function M.prev_paragraph_line(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() - local loc + local loc = 0 vim.api.nvim_buf_call(bufnr, function() local found local visibleline = vim.fn.line("w0") diff --git a/tests/precognition/gutter_hints_spec.lua b/tests/precognition/gutter_hints_spec.lua index 0c88d59..5db1f0c 100644 --- a/tests/precognition/gutter_hints_spec.lua +++ b/tests/precognition/gutter_hints_spec.lua @@ -22,8 +22,8 @@ describe("Gutter hints table", function() eq({ ["gg"] = 1, - ["{"] = 3, - ["}"] = 5, + PrevParagraph = 3, + NextParagraph = 5, ["G"] = 8, }, hints) end) @@ -37,8 +37,8 @@ describe("Gutter hints table", function() eq({ ["gg"] = 1, - ["{"] = 1, - ["}"] = 1, + NextParagraph = 1, + PrevParagraph = 1, ["G"] = 1, }, hints) end) @@ -52,8 +52,8 @@ describe("Gutter hints table", function() local hints = precognition.build_gutter_hints(testBuf) eq({ ["gg"] = 1, - ["{"] = 1, - ["}"] = 1, + NextParagraph = 1, + PrevParagraph = 1, ["G"] = 1, }, hints) end) @@ -77,8 +77,8 @@ describe("Gutter hints table", function() eq({ ["gg"] = 1, - ["{"] = 3, - ["}"] = 5, + PrevParagraph = 3, + NextParagraph = 5, ["G"] = 8, }, hints) @@ -86,8 +86,8 @@ describe("Gutter hints table", function() hints = precognition.build_gutter_hints(testBuf) eq({ ["gg"] = 1, - ["{"] = 5, - ["}"] = 7, + PrevParagraph = 5, + NextParagraph = 7, ["G"] = 8, }, hints) end) @@ -101,8 +101,8 @@ describe("Gutter hints table", function() local hints = precognition.build_gutter_hints(testBuf) eq({ ["gg"] = 1, - ["{"] = 1, - ["}"] = 1, + NextParagraph = 1, + PrevParagraph = 1, ["G"] = 1, }, hints) @@ -111,8 +111,8 @@ describe("Gutter hints table", function() hints = precognition.build_gutter_hints(testBuf) eq({ ["gg"] = 1, - ["{"] = 1, - ["}"] = 2, + PrevParagraph = 1, + NextParagraph = 2, ["G"] = 2, }, hints) @@ -122,8 +122,8 @@ describe("Gutter hints table", function() eq({ ["gg"] = 1, - ["{"] = 1, - ["}"] = 3, + PrevParagraph = 1, + NextParagraph = 3, ["G"] = 3, }, hints) @@ -134,8 +134,8 @@ describe("Gutter hints table", function() eq({ ["gg"] = 1, - ["{"] = 1, - ["}"] = 4, + PrevParagraph = 1, + NextParagraph = 4, ["G"] = 5, }, hints) end) diff --git a/tests/precognition/horizontal_motions_spec.lua b/tests/precognition/horizontal_motions_spec.lua index cd86e49..de992f0 100644 --- a/tests/precognition/horizontal_motions_spec.lua +++ b/tests/precognition/horizontal_motions_spec.lua @@ -8,9 +8,9 @@ describe("boundaries", function() 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(0, hm.next_word_boundary("abc efg", 5, 7)) + eq(0, hm.next_word_boundary("abc efg", 6, 7)) + eq(0, hm.next_word_boundary("abc efg", 7, 7)) eq(9, hm.next_word_boundary("slighly more complex test", 1, 22)) eq(9, hm.next_word_boundary("slighly more complex test", 2, 22)) @@ -28,36 +28,36 @@ describe("boundaries", function() 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)) + eq(0, 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, #test_string) - if pos == nil then - error("pos is nil") + if pos == 0 then + error("pos is 0") end eq("h", test_string:sub(pos, pos)) - if pos == nil then - error("pos is nil") + if pos == 0 then + error("pos is 0") end pos = hm.next_word_boundary(test_string, pos, #test_string) - if pos == nil then - error("pos is nil") + if pos == 0 then + error("pos is 0") end eq("o", test_string:sub(pos, pos)) pos = hm.next_word_boundary(test_string, pos, #test_string) - if pos == nil then - error("pos is nil") + if pos == 0 then + error("pos is 0") end eq("v", test_string:sub(pos, pos)) pos = hm.next_word_boundary(test_string, pos, #test_string) - eq(nil, pos) + eq(0, pos) end) describe("previous word boundary", function() it("finds the previous word boundary", function() - eq(nil, hm.prev_word_boundary("abc efg", 1, 7)) + eq(0, 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)) @@ -72,13 +72,13 @@ describe("boundaries", function() 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(0, hm.prev_word_boundary("slighly more complex test", 1, 22)) - 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(0, hm.prev_word_boundary(" myFunction(example, stuff)", 1, 30)) + eq(0, hm.prev_word_boundary(" myFunction(example, stuff)", 2, 30)) + eq(0, hm.prev_word_boundary(" myFunction(example, stuff)", 3, 30)) + eq(0, hm.prev_word_boundary(" myFunction(example, stuff)", 4, 30)) + eq(0, 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)) @@ -96,18 +96,18 @@ describe("boundaries", function() it("can walk string with b", function() local test_string = "abcdefg hijklmn opqrstu vwxyz" local pos = hm.prev_word_boundary(test_string, 29, #test_string) - if pos == nil then - error("pos is nil") + if pos == 0 then + error("pos is 0") end eq("v", test_string:sub(pos, pos)) pos = hm.prev_word_boundary(test_string, pos, #test_string) - if pos == nil then - error("pos is nil") + if pos == 0 then + error("pos is 0") end eq("o", test_string:sub(pos, pos)) pos = hm.prev_word_boundary(test_string, pos, #test_string) - if pos == nil then - error("pos is nil") + if pos == 0 then + error("pos is 0") end eq("h", test_string:sub(pos, pos)) pos = hm.prev_word_boundary(test_string, pos, #test_string) @@ -139,22 +139,22 @@ describe("boundaries", function() 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)) + eq(0, 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, 0)) - eq(nil, hm.prev_word_boundary("", 1, 0)) - eq(nil, hm.end_of_word("", 1, 0)) + eq(0, hm.next_word_boundary("", 1, 0)) + eq(0, hm.prev_word_boundary("", 1, 0)) + eq(0, hm.end_of_word("", 1, 0)) end) it("can handle strings with only whitespace", function() - eq(nil, hm.next_word_boundary(" ", 1, 1)) - eq(nil, hm.prev_word_boundary(" ", 1, 1)) - eq(nil, hm.end_of_word(" ", 1, 1)) + eq(0, hm.next_word_boundary(" ", 1, 1)) + eq(0, hm.prev_word_boundary(" ", 1, 1)) + eq(0, hm.end_of_word(" ", 1, 1)) end) it("can handle strings with special characters in the middle", function() diff --git a/tests/precognition/virtline_spec.lua b/tests/precognition/virtline_spec.lua index 221c9ab..a977f08 100644 --- a/tests/precognition/virtline_spec.lua +++ b/tests/precognition/virtline_spec.lua @@ -4,10 +4,9 @@ local hm = require("precognition.horizontal_motions") local eq = assert.are.same describe("Build Virtual Line", function() it("can build a simple virtual line", function() - ---@type Precognition.VirtLine local marks = { - ["^"] = 4, - ["$"] = 10, + Carat = 4, + Dollar = 10, } local virtual_line = precognition.build_virt_line(marks, 10) eq(" ^ $", virtual_line[1][1]) @@ -15,9 +14,8 @@ describe("Build Virtual Line", function() end) it("can build a virtual line with a single mark", function() - ---@type Precognition.VirtLine local marks = { - ["^"] = 4, + Carat = 4, } local virtual_line = precognition.build_virt_line(marks, 10) eq(" ^ ", virtual_line[1][1]) @@ -25,9 +23,8 @@ describe("Build Virtual Line", function() end) it("can build a virtual line with a single mark at the end", function() - ---@type Precognition.VirtLine local marks = { - ["$"] = 10, + Dollar = 10, } local virtual_line = precognition.build_virt_line(marks, 10) eq(" $", virtual_line[1][1]) @@ -35,9 +32,8 @@ describe("Build Virtual Line", function() end) it("can build a virtual line with a single mark at the beginning", function() - ---@type Precognition.VirtLine local marks = { - ["^"] = 1, + Carat = 1, } local virtual_line = precognition.build_virt_line(marks, 10) eq("^ ", virtual_line[1][1]) @@ -47,10 +43,11 @@ describe("Build Virtual Line", function() it("can build a complex virtual line", function() ---@type Precognition.VirtLine local marks = { - ["^"] = 1, - ["b"] = 4, - ["w"] = 10, - ["$"] = 50, + Carat = 1, + e = 6, + b = 4, + w = 10, + Dollar = 50, } local virtual_line = precognition.build_virt_line(marks, 50) local line_num = 1 @@ -59,6 +56,8 @@ describe("Build Virtual Line", function() eq("^", char) elseif line_num == 4 then eq("b", char) + elseif line_num == 6 then + eq("e", char) elseif line_num == 10 then eq("w", char) elseif line_num == 50 then @@ -79,11 +78,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, 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), + 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), + Carat = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), + Dollar = hm.line_end(cur_line, cursorcol, line_len), }, line_len) eq("b e w $", virt_line[1][1]) @@ -99,11 +98,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, 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), + 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), + Carat = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), + Dollar = hm.line_end(cur_line, cursorcol, line_len), }, line_len) eq(" ^ e w $", virt_line[1][1]) From 018126731ae7972c2e8bc6ec780aeb0cf30d2f2c Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 14:23:33 +0100 Subject: [PATCH 03/17] docs: update fieldnames --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 262bad9..144eaeb 100644 --- a/README.md +++ b/README.md @@ -20,18 +20,18 @@ return { config = { -- startVisible = true, -- hints = { - -- ["^"] = { text = "^", prio = 1 }, - -- ["$"] = { text = "$", prio = 1 }, - -- ["w"] = { text = "w", prio = 10 }, - -- ["b"] = { text = "b", prio = 10 }, - -- ["e"] = { text = "e", prio = 10 }, + -- Carat = { text = "^", prio = 1 }, + -- Dollar = { text = "$", prio = 1 }, + -- w = { text = "w", prio = 10 }, + -- b = { text = "b", prio = 10 }, + -- e = { text = "e", prio = 10 }, -- }, -- gutterHints = { -- --prio is not currentlt used for gutter hints - -- ["G"] = { text = "G", prio = 1 }, - -- ["gg"] = { text = "gg", prio = 1 }, - -- ["{"] = { text = "{", prio = 1 }, - -- ["}"] = { text = "}", prio = 1 }, + -- G = { text = "G", prio = 1 }, + -- gg = { text = "gg", prio = 1 }, + -- PrevParagraph = { text = "{", prio = 1 }, + -- NextParagraph = { text = "}", prio = 1 }, -- }, }, } From efdc042378fd707a328707c456e7734c5f6ea502 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 14:53:45 +0100 Subject: [PATCH 04/17] feat: hiding blank virtual lines and disabling items --- README.md | 8 ++++++++ lua/precognition/init.lua | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 144eaeb..f0bfcba 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ return { "tris203/precognition.nvim", config = { -- startVisible = true, + -- showBlankVirtLine = true, -- hints = { -- Carat = { text = "^", prio = 1 }, -- Dollar = { text = "$", prio = 1 }, @@ -37,6 +38,13 @@ return { } ``` +## ⚙️ Config + +- Items can be hidden by settings their priority to 0 +- `lua showBlankVirtLine = false` + Setting this option will mean that if a Virtual Line would be blank it wont be + rendered + ## ❔Usage ### Toggling diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index f1786b8..6a25a26 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -29,6 +29,7 @@ local M = {} ---@class Precognition.Config ---@field startVisible boolean ---@field hints HintConfig +---@field showBlankVirtLine boolean ---@field gutterHints GutterHintConfig ---@class Precognition.PartialConfig @@ -61,6 +62,7 @@ local defaultHintConfig = { ---@type Precognition.Config local default = { startVisible = true, + showBlankVirtLine = true, hints = defaultHintConfig, gutterHints = { --prio is not currentlt used for gutter hints @@ -107,9 +109,10 @@ local function build_virt_line(marks, line_len) for mark, loc in pairs(marks) do local hint = config.hints[mark].text or mark + local prio = config.hints[mark].prio or 0 local col = loc - if col ~= 0 then + if col ~= 0 and prio > 0 then local existing = line:sub(col, col) if existing == " " and existing ~= hint then line = line:sub(1, col - 1) .. hint .. line:sub(col + 1) @@ -127,8 +130,10 @@ local function build_virt_line(marks, line_len) end end end + if line:match("^%s+$") then + return {} + end table.insert(virt_line, { line, "Comment" }) - return virt_line end @@ -211,12 +216,18 @@ 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 not config.showBlankVirtLine then + if virt_line == nil or #virt_line == 0 then + goto continue + end + end 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 }, }) end + ::continue:: apply_gutter_hints(build_gutter_hints()) dirty = false @@ -336,6 +347,8 @@ end function M.setup(opts) config = vim.tbl_deep_extend("force", default, opts or {}) + print("config", vim.inspect(config)) + ns = vim.api.nvim_create_namespace("precognition") au = vim.api.nvim_create_augroup("precognition", { clear = true }) if config.startVisible then From 330cc0182b97ac74758e7318579bfa33b8cddf0f Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 17:56:08 +0100 Subject: [PATCH 05/17] refactor: remove goto --- lua/precognition/init.lua | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 6a25a26..b1b1c1d 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -216,18 +216,14 @@ 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 not config.showBlankVirtLine then - if virt_line == nil or #virt_line == 0 then - goto continue + if config.showBlankVirtLine or (virt_line and #virt_line > 0) 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 }, + }) end end - 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 }, - }) - end - ::continue:: apply_gutter_hints(build_gutter_hints()) dirty = false From fbdcf0fef824768ed67eb8cdf1d017397a1f77d3 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 18:00:21 +0100 Subject: [PATCH 06/17] chore: typo --- README.md | 2 +- lua/precognition/init.lua | 12 ++++-------- tests/precognition/virtline_spec.lua | 12 ++++++------ 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f0bfcba..84b27ca 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ return { -- startVisible = true, -- showBlankVirtLine = true, -- hints = { - -- Carat = { text = "^", prio = 1 }, + -- Caret = { text = "^", prio = 1 }, -- Dollar = { text = "$", prio = 1 }, -- w = { text = "w", prio = 10 }, -- b = { text = "b", prio = 10 }, diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index b1b1c1d..b6bd757 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -3,10 +3,6 @@ local vm = require("precognition.vertical_motions") local M = {} ----@alias Dollar "$" ----@alias PrevParagraph "{" ----@alias NextParagraph "}" - ---@class HintOpts ---@field text string ---@field prio integer @@ -17,7 +13,7 @@ local M = {} ---@field w HintOpts ---@field e HintOpts ---@field b HintOpts ----@field Carat HintOpts +---@field Caret HintOpts ---@field Dollar HintOpts ---@class GutterHintConfig @@ -41,7 +37,7 @@ local M = {} ---@field w PlaceLoc ---@field e PlaceLoc ---@field b PlaceLoc ----@field Carat PlaceLoc +---@field Caret PlaceLoc ---@field Dollar PlaceLoc ---@class (exact) Precognition.GutterHints @@ -52,7 +48,7 @@ local M = {} ---@type HintConfig local defaultHintConfig = { - Carat = { text = "^", prio = 1 }, + Caret = { text = "^", prio = 1 }, Dollar = { text = "$", prio = 1 }, w = { text = "w", prio = 10 }, b = { text = "b", prio = 9 }, @@ -204,7 +200,7 @@ local function on_cursor_hold() -- ---@type Precognition.VirtLine local virtual_line_marks = { - Carat = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), + Caret = hm.line_start_non_whitespace(cur_line, cursorcol, 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), diff --git a/tests/precognition/virtline_spec.lua b/tests/precognition/virtline_spec.lua index a977f08..77c704a 100644 --- a/tests/precognition/virtline_spec.lua +++ b/tests/precognition/virtline_spec.lua @@ -5,7 +5,7 @@ local eq = assert.are.same describe("Build Virtual Line", function() it("can build a simple virtual line", function() local marks = { - Carat = 4, + Caret = 4, Dollar = 10, } local virtual_line = precognition.build_virt_line(marks, 10) @@ -15,7 +15,7 @@ describe("Build Virtual Line", function() it("can build a virtual line with a single mark", function() local marks = { - Carat = 4, + Caret = 4, } local virtual_line = precognition.build_virt_line(marks, 10) eq(" ^ ", virtual_line[1][1]) @@ -33,7 +33,7 @@ describe("Build Virtual Line", function() it("can build a virtual line with a single mark at the beginning", function() local marks = { - Carat = 1, + Caret = 1, } local virtual_line = precognition.build_virt_line(marks, 10) eq("^ ", virtual_line[1][1]) @@ -43,7 +43,7 @@ describe("Build Virtual Line", function() it("can build a complex virtual line", function() ---@type Precognition.VirtLine local marks = { - Carat = 1, + Caret = 1, e = 6, b = 4, w = 10, @@ -81,7 +81,7 @@ describe("Build Virtual Line", function() 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), - Carat = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), + Caret = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), Dollar = hm.line_end(cur_line, cursorcol, line_len), }, line_len) @@ -101,7 +101,7 @@ describe("Build Virtual Line", function() 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), - Carat = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), + Caret = hm.line_start_non_whitespace(cur_line, cursorcol, line_len), Dollar = hm.line_end(cur_line, cursorcol, line_len), }, line_len) From 3ac4c90504c7c7d77d528e5d87fdbe5f9b8f8439 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 20:44:04 +0100 Subject: [PATCH 07/17] refactor: nanmespace types --- lua/precognition/horizontal_motions.lua | 10 ++--- lua/precognition/init.lua | 54 ++++++++++++------------- lua/precognition/vertical_motions.lua | 8 ++-- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua index 3f7b91e..0e21dca 100644 --- a/lua/precognition/horizontal_motions.lua +++ b/lua/precognition/horizontal_motions.lua @@ -5,7 +5,7 @@ local M = {} ---@param str string ---@param _cursorcol integer ---@param _linelen integer ----@return PlaceLoc +---@return Precognition.PlaceLoc function M.line_start_non_whitespace(str, _cursorcol, _linelen) return str:find("%S") or 0 end @@ -13,7 +13,7 @@ end ---@param _str string ---@param _cursorcol integer ---@param linelen integer ----@return PlaceLoc +---@return Precognition.PlaceLoc function M.line_end(_str, _cursorcol, linelen) return linelen or nil end @@ -21,7 +21,7 @@ end ---@param str string ---@param cursorcol integer ---@param _linelen integer ----@return PlaceLoc +---@return Precognition.PlaceLoc function M.next_word_boundary(str, cursorcol, _linelen) local offset = cursorcol local len = vim.fn.strcharlen(str) @@ -49,7 +49,7 @@ end ---@param str string ---@param cursorcol integer ---@param _linelen integer ----@return PlaceLoc +---@return Precognition.PlaceLoc function M.end_of_word(str, cursorcol, _linelen) local len = vim.fn.strcharlen(str) if cursorcol >= len then @@ -95,7 +95,7 @@ end ---@param str string ---@param cursorcol integer ---@param _linelen integer ----@return PlaceLoc +---@return Precognition.PlaceLoc function M.prev_word_boundary(str, cursorcol, _linelen) local len = vim.fn.strcharlen(str) local offset = cursorcol - 1 diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index b6bd757..8df2809 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -3,50 +3,50 @@ local vm = require("precognition.vertical_motions") local M = {} ----@class HintOpts +---@class Precognition.HintOpts ---@field text string ---@field prio integer ----@alias PlaceLoc integer +---@alias Precognition.PlaceLoc integer ----@class (exact) HintConfig ----@field w HintOpts ----@field e HintOpts ----@field b HintOpts ----@field Caret HintOpts ----@field Dollar HintOpts +---@class (exact) Precognition.HintConfig +---@field w Precognition.HintOpts +---@field e Precognition.HintOpts +---@field b Precognition.HintOpts +---@field Caret Precognition.HintOpts +---@field Dollar Precognition.HintOpts ----@class GutterHintConfig ----@field G HintOpts ----@field gg HintOpts ----@field PrevParagraph HintOpts ----@field NextParagraph HintOpts +---@class Precognition.GutterHintConfig +---@field G Precognition.HintOpts +---@field gg Precognition.HintOpts +---@field PrevParagraph Precognition.HintOpts +---@field NextParagraph Precognition.HintOpts ---@class Precognition.Config ---@field startVisible boolean ----@field hints HintConfig +---@field hints Precognition.HintConfig ---@field showBlankVirtLine boolean ----@field gutterHints GutterHintConfig +---@field gutterHints Precognition.GutterHintConfig ---@class Precognition.PartialConfig ---@field startVisible? boolean ----@field hints? HintConfig ----@field gutterHints? GutterHintConfig +---@field hints? Precognition.HintConfig +---@field gutterHints? Precognition.GutterHintConfig ---@class (exact) Precognition.VirtLine ----@field w PlaceLoc ----@field e PlaceLoc ----@field b PlaceLoc ----@field Caret PlaceLoc ----@field Dollar PlaceLoc +---@field w Precognition.PlaceLoc +---@field e Precognition.PlaceLoc +---@field b Precognition.PlaceLoc +---@field Caret Precognition.PlaceLoc +---@field Dollar Precognition.PlaceLoc ---@class (exact) Precognition.GutterHints ----@field G PlaceLoc ----@field gg PlaceLoc ----@field PrevParagraph PlaceLoc ----@field NextParagraph PlaceLoc +---@field G Precognition.PlaceLoc +---@field gg Precognition.PlaceLoc +---@field PrevParagraph Precognition.PlaceLoc +---@field NextParagraph Precognition.PlaceLoc ----@type HintConfig +---@type Precognition.HintConfig local defaultHintConfig = { Caret = { text = "^", prio = 1 }, Dollar = { text = "$", prio = 1 }, diff --git a/lua/precognition/vertical_motions.lua b/lua/precognition/vertical_motions.lua index 279d00f..6e5da66 100644 --- a/lua/precognition/vertical_motions.lua +++ b/lua/precognition/vertical_motions.lua @@ -1,19 +1,19 @@ local M = {} ----@return PlaceLoc +---@return Precognition.PlaceLoc function M.file_start() return 1 end ---@param bufnr? integer ----@return PlaceLoc +---@return Precognition.PlaceLoc function M.file_end(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() return vim.api.nvim_buf_line_count(bufnr) end ---@param bufnr? integer ----@return PlaceLoc +---@return Precognition.PlaceLoc function M.next_paragraph_line(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() local loc = 0 @@ -43,7 +43,7 @@ function M.next_paragraph_line(bufnr) end ---@param bufnr? integer ----@return PlaceLoc +---@return Precognition.PlaceLoc function M.prev_paragraph_line(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() local loc = 0 From 4cdcc11366c3041f938d21f35cbe3176e961317f Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 20:59:53 +0100 Subject: [PATCH 08/17] tests: virt line priority tests --- lua/precognition/init.lua | 2 - tests/precognition/virtline_spec.lua | 94 ++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 8df2809..90bfcf3 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -339,8 +339,6 @@ end function M.setup(opts) config = vim.tbl_deep_extend("force", default, opts or {}) - print("config", vim.inspect(config)) - ns = vim.api.nvim_create_namespace("precognition") au = vim.api.nvim_create_augroup("precognition", { clear = true }) if config.startVisible then diff --git a/tests/precognition/virtline_spec.lua b/tests/precognition/virtline_spec.lua index 77c704a..96525ea 100644 --- a/tests/precognition/virtline_spec.lua +++ b/tests/precognition/virtline_spec.lua @@ -109,3 +109,97 @@ describe("Build Virtual Line", function() eq(#line, #virt_line[1][1]) end) end) + + +describe("Priority", function() + it("0 priority item is not added", function() + precognition.setup({ + hints = { + Caret = { + prio = 0, + text = "^", + }, + Dollar = { + prio = 0, + text = "$", + }, + }, + }) + + local marks = { + Caret = 4, + w = 6, + Dollar = 10, + } + + local virtual_line = precognition.build_virt_line(marks, 10) + eq(" w ", virtual_line[1][1]) + eq(10, #virtual_line[1][1]) + end) + + it("a higher priority mark in the same space takes priority", function() + precognition.setup({ + hints = { + Caret = { + prio = 0, + text = "^", + }, + Dollar = { + prio = 1, + text = "$", + }, + }, + }) + + local marks = { + Caret = 4, + w = 6, + Dollar = 10, + } + + local virtual_line = precognition.build_virt_line(marks, 10) + eq(" w $", virtual_line[1][1]) + eq(10, #virtual_line[1][1]) + end) + + it("a higher priority mark in the same space takes priority", function() + precognition.setup({ + hints = { + Caret = { + prio = 1, + text = "^", + }, + Dollar = { + prio = 100, + text = "$", + }, + }, + }) + + local marks = { + Caret = 1, + Dollar = 1, + } + + local virtual_line = precognition.build_virt_line(marks, 1) + eq("$", virtual_line[1][1]) + eq(1, #virtual_line[1][1]) + + precognition.setup({ + hints = { + Caret = { + prio = 100, + text = "^", + }, + Dollar = { + prio = 1, + text = "$", + }, + }, + }) + + virtual_line = precognition.build_virt_line(marks, 1) + eq("^", virtual_line[1][1]) + eq(1, #virtual_line[1][1]) + end) +end) From ce32f5d732d6556238fe2bef893dfbfc1676d1f4 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 21:00:37 +0100 Subject: [PATCH 09/17] chore: fmt --- tests/precognition/virtline_spec.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/precognition/virtline_spec.lua b/tests/precognition/virtline_spec.lua index 96525ea..ce816d6 100644 --- a/tests/precognition/virtline_spec.lua +++ b/tests/precognition/virtline_spec.lua @@ -110,7 +110,6 @@ describe("Build Virtual Line", function() end) end) - describe("Priority", function() it("0 priority item is not added", function() precognition.setup({ @@ -137,7 +136,7 @@ describe("Priority", function() eq(10, #virtual_line[1][1]) end) - it("a higher priority mark in the same space takes priority", function() + it("a higher priority mark in the same space takes priority", function() precognition.setup({ hints = { Caret = { From 210a55d8662c6bab8adebea205405aa74a22ba6a Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 11:27:08 +0100 Subject: [PATCH 10/17] feat: begin % support --- lua/precognition/horizontal_motions.lua | 92 +++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua index 0e21dca..3c5a620 100644 --- a/lua/precognition/horizontal_motions.lua +++ b/lua/precognition/horizontal_motions.lua @@ -126,4 +126,96 @@ function M.prev_word_boundary(str, cursorcol, _linelen) return offset + 1 end +---@param str string +---@param cursorcol integer +---@param linelen integer +---@return integer | nil +function M.matching_bracket(str, cursorcol, linelen) + local supportedBrackets = { + open = { "(", "[", "{" }, + middle = { "", "", "" }, + close = { ")", "]", "}" }, + } + local under_cursor = vim.fn.strcharpart(str, cursorcol - 1, 1) + local offset = cursorcol + + if + not vim.tbl_contains(supportedBrackets.open, under_cursor) + and not vim.tbl_contains(supportedBrackets.close, under_cursor) + then + -- walk until we find a bracket + return nil + end + local idxFound = false + local bracketIdx + if not idxFound then + for i, bracket in ipairs(supportedBrackets.open) do + if bracket == under_cursor then + bracketIdx = i + idxFound = true + break + end + end + end + + if not idxFound then + for i, bracket in ipairs(supportedBrackets.close) do + if bracket == under_cursor then + bracketIdx = i + idxFound = true + break + end + end + end + + if not idxFound then + return nil + end + + local openBracket = supportedBrackets.open[bracketIdx] + local closeBracket = supportedBrackets.close[bracketIdx] + local middleBracket = supportedBrackets.middle[bracketIdx] + + if under_cursor == openBracket then + local depth = 1 + offset = offset + 1 + while offset <= linelen do + local char = vim.fn.strcharpart(str, offset - 1, 1) + if char == openBracket then + depth = depth + 1 + end + if char == closeBracket or char == middleBracket then + depth = depth - 1 + if depth == 0 then + break + end + end + offset = offset + 1 + end + end + + if under_cursor == closeBracket then + local depth = 1 + offset = offset - 2 + while offset >= 0 do + local char = vim.fn.strcharpart(str, offset - 1, 1) + if char == closeBracket then + depth = depth + 1 + end + if char == openBracket or char == middleBracket then + depth = depth - 1 + if depth == 0 then + break + end + end + offset = offset - 1 + end + end + + if offset < 0 or offset > linelen then + return nil + end + return offset +end + return M From 3fe03eb264c059ae284672dfcd2d94b34783103f Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 21:11:06 +0100 Subject: [PATCH 11/17] chore: rebase fix --- lua/precognition/horizontal_motions.lua | 8 ++++---- lua/precognition/init.lua | 8 +++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua index 3c5a620..2903924 100644 --- a/lua/precognition/horizontal_motions.lua +++ b/lua/precognition/horizontal_motions.lua @@ -129,7 +129,7 @@ end ---@param str string ---@param cursorcol integer ---@param linelen integer ----@return integer | nil +---@return Precognition.PlaceLoc function M.matching_bracket(str, cursorcol, linelen) local supportedBrackets = { open = { "(", "[", "{" }, @@ -144,7 +144,7 @@ function M.matching_bracket(str, cursorcol, linelen) and not vim.tbl_contains(supportedBrackets.close, under_cursor) then -- walk until we find a bracket - return nil + return 0 end local idxFound = false local bracketIdx @@ -169,7 +169,7 @@ function M.matching_bracket(str, cursorcol, linelen) end if not idxFound then - return nil + return 0 end local openBracket = supportedBrackets.open[bracketIdx] @@ -213,7 +213,7 @@ function M.matching_bracket(str, cursorcol, linelen) end if offset < 0 or offset > linelen then - return nil + return 0 end return offset end diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 90bfcf3..f26e776 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -13,8 +13,10 @@ local M = {} ---@field w Precognition.HintOpts ---@field e Precognition.HintOpts ---@field b Precognition.HintOpts +---@field Zero Precognition.HintOpts ---@field Caret Precognition.HintOpts ---@field Dollar Precognition.HintOpts +---@field MatchingPair Precognition.HintOpts ---@class Precognition.GutterHintConfig ---@field G Precognition.HintOpts @@ -39,6 +41,7 @@ local M = {} ---@field b Precognition.PlaceLoc ---@field Caret Precognition.PlaceLoc ---@field Dollar Precognition.PlaceLoc +---@field MatchingPair Precognition.PlaceLoc ---@class (exact) Precognition.GutterHints ---@field G Precognition.PlaceLoc @@ -48,8 +51,10 @@ local M = {} ---@type Precognition.HintConfig local defaultHintConfig = { - Caret = { text = "^", prio = 1 }, + Caret = { text = "^", prio = 2 }, Dollar = { text = "$", prio = 1 }, + MatchingPair = { text = "%", prio = 5 }, + Zero = { text = "0", prio = 1 }, w = { text = "w", prio = 10 }, b = { text = "b", prio = 9 }, e = { text = "e", prio = 8 }, @@ -204,6 +209,7 @@ local function on_cursor_hold() 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), + MatchingPair = hm.matching_bracket(cur_line, cursorcol, line_len), Dollar = hm.line_end(cur_line, cursorcol, line_len), } From 460aeb76417de8a0aee4b411793eceab92f77617 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 21:20:10 +0100 Subject: [PATCH 12/17] tests: add tests --- .../precognition/horizontal_motions_spec.lua | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/precognition/horizontal_motions_spec.lua b/tests/precognition/horizontal_motions_spec.lua index de992f0..63b1656 100644 --- a/tests/precognition/horizontal_motions_spec.lua +++ b/tests/precognition/horizontal_motions_spec.lua @@ -144,6 +144,52 @@ describe("boundaries", function() end) end) + +describe("matching pair tests", function() + it("if cursor is over a bracket it can find the pair", function() + eq(9, hm.matching_bracket("abc (efg)", 5, 9)) + eq(0, hm.matching_bracket("abc (efg)", 6, 9)) + eq(0, hm.matching_bracket("abc (efg)", 7, 9)) + eq(0, hm.matching_bracket("abc (efg)", 8, 9)) + eq(5, hm.matching_bracket("abc (efg)", 9, 9)) + end) + + it("if cursor is over a square bracket it can find the pair", function() + eq(9, hm.matching_bracket("abc [efg]", 5, 9)) + eq(0, hm.matching_bracket("abc [efg]", 6, 9)) + eq(0, hm.matching_bracket("abc [efg]", 7, 9)) + eq(0, hm.matching_bracket("abc [efg]", 8, 9)) + eq(5, hm.matching_bracket("abc [efg]", 9, 9)) + end) + + it("if cursor is over a curly bracket it can find the pair", function() + eq(9, hm.matching_bracket("abc {efg}", 5, 9)) + eq(0, hm.matching_bracket("abc {efg}", 6, 9)) + eq(0, hm.matching_bracket("abc {efg}", 7, 9)) + eq(0, hm.matching_bracket("abc {efg}", 8, 9)) + eq(5, hm.matching_bracket("abc {efg}", 9, 9)) + end) + + it("nested brackets find the correct pair", function() + eq(19, hm.matching_bracket("abc (efg [hij] klm)", 5, 19)) + eq(0, hm.matching_bracket("abc (efg [hij] klm)", 6, 19)) + eq(14, hm.matching_bracket("abc (efg [hij] klm)", 10, 19)) + eq(10, hm.matching_bracket("abc (efg [hij] klm)", 14, 19)) + eq(0, hm.matching_bracket("abc (efg [hij] klm)", 15, 19)) + eq(5, hm.matching_bracket("abc (efg [hij] klm)", 19, 19)) + end) + + it ("nested brackets of the same type find the correct pair", function() + eq(19, hm.matching_bracket("abc (efg (hij) klm)", 5, 19)) + eq(0, hm.matching_bracket("abc (efg (hij) klm)", 6, 19)) + eq(14, hm.matching_bracket("abc (efg (hij) klm)", 10, 19)) + eq(10, hm.matching_bracket("abc (efg (hij) klm)", 14, 19)) + eq(0, hm.matching_bracket("abc (efg (hij) klm)", 15, 19)) + eq(5, hm.matching_bracket("abc (efg (hij) klm)", 19, 19)) + end) + +end) + describe("edge case", function() it("can handle empty strings", function() eq(0, hm.next_word_boundary("", 1, 0)) From dc18a73657f1583addcd5bfd434ca2a94f4a9ad3 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 21:26:30 +0100 Subject: [PATCH 13/17] chore: fmt --- tests/precognition/horizontal_motions_spec.lua | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/precognition/horizontal_motions_spec.lua b/tests/precognition/horizontal_motions_spec.lua index 63b1656..16654f7 100644 --- a/tests/precognition/horizontal_motions_spec.lua +++ b/tests/precognition/horizontal_motions_spec.lua @@ -144,7 +144,6 @@ describe("boundaries", function() end) end) - describe("matching pair tests", function() it("if cursor is over a bracket it can find the pair", function() eq(9, hm.matching_bracket("abc (efg)", 5, 9)) @@ -179,15 +178,14 @@ describe("matching pair tests", function() eq(5, hm.matching_bracket("abc (efg [hij] klm)", 19, 19)) end) - it ("nested brackets of the same type find the correct pair", function() - eq(19, hm.matching_bracket("abc (efg (hij) klm)", 5, 19)) - eq(0, hm.matching_bracket("abc (efg (hij) klm)", 6, 19)) - eq(14, hm.matching_bracket("abc (efg (hij) klm)", 10, 19)) - eq(10, hm.matching_bracket("abc (efg (hij) klm)", 14, 19)) - eq(0, hm.matching_bracket("abc (efg (hij) klm)", 15, 19)) - eq(5, hm.matching_bracket("abc (efg (hij) klm)", 19, 19)) + it("nested brackets of the same type find the correct pair", function() + eq(19, hm.matching_bracket("abc (efg (hij) klm)", 5, 19)) + eq(0, hm.matching_bracket("abc (efg (hij) klm)", 6, 19)) + eq(14, hm.matching_bracket("abc (efg (hij) klm)", 10, 19)) + eq(10, hm.matching_bracket("abc (efg (hij) klm)", 14, 19)) + eq(0, hm.matching_bracket("abc (efg (hij) klm)", 15, 19)) + eq(5, hm.matching_bracket("abc (efg (hij) klm)", 19, 19)) end) - end) describe("edge case", function() From 5987245d1015cba7efc576413c4c7effaee205d7 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 21:36:22 +0100 Subject: [PATCH 14/17] docs: update readme --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 84b27ca..f798a2f 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,13 @@ return { -- startVisible = true, -- showBlankVirtLine = true, -- hints = { - -- Caret = { text = "^", prio = 1 }, - -- Dollar = { text = "$", prio = 1 }, - -- w = { text = "w", prio = 10 }, - -- b = { text = "b", prio = 10 }, - -- e = { text = "e", prio = 10 }, + Caret = { text = "^", prio = 2 }, + Dollar = { text = "$", prio = 1 }, + MatchingPair = { text = "%", prio = 5 }, + Zero = { text = "0", prio = 1 }, + w = { text = "w", prio = 10 }, + b = { text = "b", prio = 9 }, + e = { text = "e", prio = 8 }, -- }, -- gutterHints = { -- --prio is not currentlt used for gutter hints @@ -40,8 +42,10 @@ return { ## ⚙️ Config -- Items can be hidden by settings their priority to 0 -- `lua showBlankVirtLine = false` +- Items can be hidden by settings their priority to 0, if you want to hide the + entire virtual line. Set all elements to `prio = 0` in combination with the + below. +- `showBlankVirtLine = false` Setting this option will mean that if a Virtual Line would be blank it wont be rendered From d0748d0c3db7dab76ba297442735a845cd70cac7 Mon Sep 17 00:00:00 2001 From: tris203 Date: Sun, 5 May 2024 21:39:31 +0100 Subject: [PATCH 15/17] docs: update --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f798a2f..e2e149d 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ return { -- startVisible = true, -- showBlankVirtLine = true, -- hints = { - Caret = { text = "^", prio = 2 }, - Dollar = { text = "$", prio = 1 }, - MatchingPair = { text = "%", prio = 5 }, - Zero = { text = "0", prio = 1 }, - w = { text = "w", prio = 10 }, - b = { text = "b", prio = 9 }, - e = { text = "e", prio = 8 }, + -- Caret = { text = "^", prio = 2 }, + -- Dollar = { text = "$", prio = 1 }, + -- MatchingPair = { text = "%", prio = 5 }, + -- Zero = { text = "0", prio = 1 }, + -- w = { text = "w", prio = 10 }, + -- b = { text = "b", prio = 9 }, + -- e = { text = "e", prio = 8 }, -- }, -- gutterHints = { -- --prio is not currentlt used for gutter hints From 9634cbe4779c3da425b1ae88856c9df0c45e426e Mon Sep 17 00:00:00 2001 From: tris203 Date: Tue, 7 May 2024 16:25:10 +0100 Subject: [PATCH 16/17] feat: comment support --- lua/precognition/horizontal_motions.lua | 68 +++++++++++++++++-- lua/precognition/init.lua | 2 +- .../precognition/horizontal_motions_spec.lua | 45 +++++++++++- 3 files changed, 108 insertions(+), 7 deletions(-) diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua index 2903924..2ed7fad 100644 --- a/lua/precognition/horizontal_motions.lua +++ b/lua/precognition/horizontal_motions.lua @@ -2,6 +2,12 @@ local utils = require("precognition.utils") local M = {} +local supportedBrackets = { + open = { "(", "[", "{" }, + middle = { "", "", "" }, + close = { ")", "]", "}" }, +} + ---@param str string ---@param _cursorcol integer ---@param _linelen integer @@ -131,11 +137,6 @@ end ---@param linelen integer ---@return Precognition.PlaceLoc function M.matching_bracket(str, cursorcol, linelen) - local supportedBrackets = { - open = { "(", "[", "{" }, - middle = { "", "", "" }, - close = { ")", "]", "}" }, - } local under_cursor = vim.fn.strcharpart(str, cursorcol - 1, 1) local offset = cursorcol @@ -218,4 +219,61 @@ function M.matching_bracket(str, cursorcol, linelen) return offset end +---@param str string +---@param cursorcol integer +---@param linelen integer +---@return Precognition.PlaceLoc +function M.matching_comment(str, cursorcol, linelen) + local offset = cursorcol + local char = vim.fn.strcharpart(str, offset - 1, 1) + local next_char = vim.fn.strcharpart(str, (offset - 1) + 1, 1) + local prev_char = vim.fn.strcharpart(str, (offset - 1) - 1, 1) + + if (char == "/" and next_char == "*") or (prev_char == "/" and char == "*") then + offset = offset + 1 + while offset <= linelen do + char = vim.fn.strcharpart(str, offset - 1, 1) + next_char = vim.fn.strcharpart(str, offset, 1) + if char == "*" and next_char == "/" then + -- return the slash of the closing comment + return offset + 1 + end + offset = offset + 1 + end + end + + if (char == "*" and next_char == "/") or (prev_char == "*" and char == "/") then + offset = offset - 1 + while offset >= 0 do + char = vim.fn.strcharpart(str, offset - 1, 1) + next_char = vim.fn.strcharpart(str, offset, 1) + if char == "/" and next_char == "*" then + return offset + end + offset = offset - 1 + end + end + + return 0 +end + +---@param str string +---@param cursorcol integer +---@param _linelen integer +---@return function +function M.matching_pair(str, cursorcol, _linelen) + local char = vim.fn.strcharpart(str, cursorcol - 1, 1) + if char == "/" or char == "*" then + return M.matching_comment + end + + if vim.tbl_contains(supportedBrackets.open, char) or vim.tbl_contains(supportedBrackets.close, char) then + return M.matching_bracket + end + + return function() + return 0 + end +end + return M diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 8d311fb..bf8dda3 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -211,7 +211,7 @@ local function on_cursor_hold() 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), - MatchingPair = hm.matching_bracket(cur_line, cursorcol, line_len), + MatchingPair = hm.matching_pair(cur_line, cursorcol, line_len)(cur_line, cursorcol, line_len), Dollar = hm.line_end(cur_line, cursorcol, line_len), Zero = 1, } diff --git a/tests/precognition/horizontal_motions_spec.lua b/tests/precognition/horizontal_motions_spec.lua index 16654f7..44ecdb6 100644 --- a/tests/precognition/horizontal_motions_spec.lua +++ b/tests/precognition/horizontal_motions_spec.lua @@ -144,7 +144,29 @@ describe("boundaries", function() end) end) -describe("matching pair tests", function() +describe("matching_pair returns the correction function", function() + it("returns the correct function for the given character", function() + local test_string = "()[]{}/*" + eq(hm.matching_pair(test_string, 1, #test_string), hm.matching_bracket) + eq(hm.matching_pair(test_string, 2, #test_string), hm.matching_bracket) + eq(hm.matching_pair(test_string, 3, #test_string), hm.matching_bracket) + eq(hm.matching_pair(test_string, 4, #test_string), hm.matching_bracket) + eq(hm.matching_pair(test_string, 5, #test_string), hm.matching_bracket) + eq(hm.matching_pair(test_string, 6, #test_string), hm.matching_bracket) + eq(hm.matching_pair(test_string, 7, #test_string), hm.matching_comment) + eq(hm.matching_pair(test_string, 8, #test_string), hm.matching_comment) + end) + + it("returns a function that returns 0 for other characters", function() + local test_string = "abcdefghijklmnopqrstuvwxyz!@#$%^&*_+-=,.<>?|\\~`" + for i = 1, #test_string do + local func = hm.matching_pair(test_string, i, #test_string) + eq(0, func(test_string, i, #test_string)) + end + end) +end) + +describe("matching brackets", function() it("if cursor is over a bracket it can find the pair", function() eq(9, hm.matching_bracket("abc (efg)", 5, 9)) eq(0, hm.matching_bracket("abc (efg)", 6, 9)) @@ -186,6 +208,27 @@ describe("matching pair tests", function() eq(0, hm.matching_bracket("abc (efg (hij) klm)", 15, 19)) eq(5, hm.matching_bracket("abc (efg (hij) klm)", 19, 19)) end) + + it("if cursor is over an unclosed bracket it returns 0", function() + eq(0, hm.matching_bracket("abc (efg", 5, 8)) + eq(0, hm.matching_bracket("abc [efg", 5, 8)) + eq(0, hm.matching_bracket("abc {efg", 5, 8)) + end) +end) + +describe("matching comments", function() + it("if cursor is over a comment it can find the pair", function() + eq(11, hm.matching_comment("abc /*efg*/", 5, 11)) + eq(11, hm.matching_comment("abc /*efg*/", 6, 11)) + eq(0, hm.matching_comment("abc /*efg*/", 7, 11)) + eq(5, hm.matching_comment("abc /*efg*/", 10, 11)) + eq(5, hm.matching_comment("abc /*efg*/", 11, 11)) + end) + + it("if cursor is over an unclosed comment it returns 0", function() + eq(0, hm.matching_comment("abc /*efg", 5, 9)) + eq(0, hm.matching_comment("abc /*efg", 6, 9)) + end) end) describe("edge case", function() From 79b93ddaedcc737a29f653cd0c4f4321b76d3953 Mon Sep 17 00:00:00 2001 From: tris203 Date: Tue, 21 May 2024 15:37:19 +0100 Subject: [PATCH 17/17] chore: final fix ups --- lua/precognition/horizontal_motions.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/precognition/horizontal_motions.lua b/lua/precognition/horizontal_motions.lua index b033991..1052630 100644 --- a/lua/precognition/horizontal_motions.lua +++ b/lua/precognition/horizontal_motions.lua @@ -5,7 +5,7 @@ local M = {} local supportedBrackets = { open = { "(", "[", "{" }, - middle = { "", "", "" }, + middle = { nil, nil, nil }, close = { ")", "]", "}" }, } @@ -185,9 +185,9 @@ function M.matching_bracket(str, cursorcol, linelen) return 0 end - local openBracket = supportedBrackets.open[bracketIdx] - local closeBracket = supportedBrackets.close[bracketIdx] - local middleBracket = supportedBrackets.middle[bracketIdx] + local openBracket = supportedBrackets.open[bracketIdx] or "" + local closeBracket = supportedBrackets.close[bracketIdx] or "" + local middleBracket = supportedBrackets.middle[bracketIdx] or "" if under_cursor == openBracket then local depth = 1