Skip to content

Commit

Permalink
feat: % support (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
tris203 authored May 21, 2024
1 parent d895349 commit fd62c75
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 8 deletions.
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
150 changes: 150 additions & 0 deletions lua/precognition/horizontal_motions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ local cc = utils.char_classes

local M = {}

local supportedBrackets = {
open = { "(", "[", "{" },
middle = { nil, nil, nil },
close = { ")", "]", "}" },
}

---@param str string
---@param _cursorcol integer
---@param _linelen integer
Expand Down Expand Up @@ -138,4 +144,148 @@ function M.prev_word_boundary(str, cursorcol, _linelen)
return offset + 1
end

---@param str string
---@param cursorcol integer
---@param linelen integer
---@return Precognition.PlaceLoc
function M.matching_bracket(str, cursorcol, linelen)
local under_cursor = vim.fn.strcharpart(str, cursorcol - 1, 1)
local offset = cursorcol

if
not vim.tbl_contains(supportedBrackets.open, under_cursor)
and not vim.tbl_contains(supportedBrackets.close, under_cursor)
then
-- walk until we find a bracket
return 0
end
local idxFound = false
local bracketIdx
if not idxFound then
for i, bracket in ipairs(supportedBrackets.open) do
if bracket == under_cursor then
bracketIdx = i
idxFound = true
break
end
end
end

if not idxFound then
for i, bracket in ipairs(supportedBrackets.close) do
if bracket == under_cursor then
bracketIdx = i
idxFound = true
break
end
end
end

if not idxFound then
return 0
end

local openBracket = supportedBrackets.open[bracketIdx] or ""
local closeBracket = supportedBrackets.close[bracketIdx] or ""
local middleBracket = supportedBrackets.middle[bracketIdx] or ""

if under_cursor == openBracket then
local depth = 1
offset = offset + 1
while offset <= linelen do
local char = vim.fn.strcharpart(str, offset - 1, 1)
if char == openBracket then
depth = depth + 1
end
if char == closeBracket or char == middleBracket then
depth = depth - 1
if depth == 0 then
break
end
end
offset = offset + 1
end
end

if under_cursor == closeBracket then
local depth = 1
offset = offset - 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 0
end
return offset
end

---@param str string
---@param cursorcol integer
---@param linelen integer
---@return Precognition.PlaceLoc
function M.matching_comment(str, cursorcol, linelen)
local offset = cursorcol
local char = vim.fn.strcharpart(str, offset - 1, 1)
local next_char = vim.fn.strcharpart(str, (offset - 1) + 1, 1)
local prev_char = vim.fn.strcharpart(str, (offset - 1) - 1, 1)

if (char == "/" and next_char == "*") or (prev_char == "/" and char == "*") then
offset = offset + 1
while offset <= linelen do
char = vim.fn.strcharpart(str, offset - 1, 1)
next_char = vim.fn.strcharpart(str, offset, 1)
if char == "*" and next_char == "/" then
-- return the slash of the closing comment
return offset + 1
end
offset = offset + 1
end
end

if (char == "*" and next_char == "/") or (prev_char == "*" and char == "/") then
offset = offset - 1
while offset >= 0 do
char = vim.fn.strcharpart(str, offset - 1, 1)
next_char = vim.fn.strcharpart(str, offset, 1)
if char == "/" and next_char == "*" then
return offset
end
offset = offset - 1
end
end

return 0
end

---@param str string
---@param cursorcol integer
---@param _linelen integer
---@return function
function M.matching_pair(str, cursorcol, _linelen)
local char = vim.fn.strcharpart(str, cursorcol - 1, 1)
if char == "/" or char == "*" then
return M.matching_comment
end

if vim.tbl_contains(supportedBrackets.open, char) or vim.tbl_contains(supportedBrackets.close, char) then
return M.matching_bracket
end

return function()
return 0
end
end

return M
10 changes: 9 additions & 1 deletion lua/precognition/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ local M = {}
---@field w Precognition.HintOpts
---@field e Precognition.HintOpts
---@field b Precognition.HintOpts
---@field Zero Precognition.HintOpts
---@field MatchingPair Precognition.HintOpts
---@field Caret Precognition.HintOpts
---@field Dollar Precognition.HintOpts

Expand All @@ -39,8 +41,10 @@ local M = {}
---@field w Precognition.PlaceLoc
---@field e Precognition.PlaceLoc
---@field b Precognition.PlaceLoc
---@field Zero Precognition.PlaceLoc
---@field Caret Precognition.PlaceLoc
---@field Dollar Precognition.PlaceLoc
---@field MatchingPair Precognition.PlaceLoc

---@class (exact) Precognition.GutterHints
---@field G Precognition.PlaceLoc
Expand All @@ -50,8 +54,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 },
Expand Down Expand Up @@ -210,7 +216,9 @@ local function display_marks()
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_pair(cur_line, cursorcol, line_len)(cur_line, cursorcol, line_len),
Dollar = hm.line_end(cur_line, cursorcol, line_len),
Zero = 1,
}

local virt_line = build_virt_line(virtual_line_marks, line_len)
Expand Down
87 changes: 87 additions & 0 deletions tests/precognition/horizontal_motions_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,93 @@ describe("boundaries", function()
end)
end)

describe("matching_pair returns the correction function", function()
it("returns the correct function for the given character", function()
local test_string = "()[]{}/*"
eq(hm.matching_pair(test_string, 1, #test_string), hm.matching_bracket)
eq(hm.matching_pair(test_string, 2, #test_string), hm.matching_bracket)
eq(hm.matching_pair(test_string, 3, #test_string), hm.matching_bracket)
eq(hm.matching_pair(test_string, 4, #test_string), hm.matching_bracket)
eq(hm.matching_pair(test_string, 5, #test_string), hm.matching_bracket)
eq(hm.matching_pair(test_string, 6, #test_string), hm.matching_bracket)
eq(hm.matching_pair(test_string, 7, #test_string), hm.matching_comment)
eq(hm.matching_pair(test_string, 8, #test_string), hm.matching_comment)
end)

it("returns a function that returns 0 for other characters", function()
local test_string = "abcdefghijklmnopqrstuvwxyz!@#$%^&*_+-=,.<>?|\\~`"
for i = 1, #test_string do
local func = hm.matching_pair(test_string, i, #test_string)
eq(0, func(test_string, i, #test_string))
end
end)
end)

describe("matching brackets", function()
it("if cursor is over a bracket it can find the pair", function()
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)

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()
it("can handle empty strings", function()
eq(0, hm.next_word_boundary("", 1, 0))
Expand Down

0 comments on commit fd62c75

Please sign in to comment.