Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: % support #8

Merged
merged 25 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading