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(tests): dts testing #97

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions .github/workflows/dts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: DTS

on:
pull_request:
branches:
- "main"

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
nvim-versions: ['nightly']
os: [ubuntu-latest]
fail-fast: false
name: DTS Tests
steps:
- name: checkout
uses: actions/checkout@v4

- uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.nvim-versions }}

- name: run dts
run: make dts
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
TESTS_INIT=tests/minimal.lua
TESTS_DIR=tests/
DTS_SCRIPT=tests/precognition/dts.lua
SEED_START=0
NUM_TESTS=500000

.PHONY: test

Expand All @@ -9,3 +12,10 @@ test:
--noplugin \
-u ${TESTS_INIT} \
-c "PlenaryBustedDirectory ${TESTS_DIR} { minimal_init = '${TESTS_INIT}' }" \

dts:
@nvim \
--headless \
--noplugin \
-u ${TESTS_INIT} \
-l ${DTS_SCRIPT} ${SEED_START} ${NUM_TESTS} \
47 changes: 32 additions & 15 deletions lua/precognition/horizontal_motions.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
local M = {}

local pairs = vim.split(vim.o.matchpairs, ",")

-- local match_bracket_escape = not vim.o.cpoptions:find("M")

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

for _, pair in ipairs(pairs) do
local open, close = pair:match("(.):(.)")
if open and close then
table.insert(supportedBrackets.open, open)
table.insert(supportedBrackets.middle, nil)
table.insert(supportedBrackets.close, close)
end
end

---@param str string
---@param _cursorcol integer
---@param _linelen integer
Expand Down Expand Up @@ -57,8 +70,9 @@ end
---@param cursorcol integer
---@param linelen integer
---@param big_word boolean
---@param recursive boolean?
---@return Precognition.PlaceLoc
function M.end_of_word(str, cursorcol, linelen, big_word)
function M.end_of_word(str, cursorcol, linelen, big_word, recursive)
if cursorcol >= linelen then
return 0
end
Expand All @@ -70,15 +84,18 @@ function M.end_of_word(str, cursorcol, linelen, big_word)
local c_class = utils.char_class(char, big_word)
local next_char_class = utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1), big_word)
local rev_offset

if
(c_class == cc.punctuation and next_char_class ~= cc.punctuation)
or (next_char_class == cc.punctuation and c_class ~= cc.punctuation)
then
offset = offset + 1
char = vim.fn.strcharpart(str, offset - 1, 1)
c_class = utils.char_class(char, big_word)
next_char_class = utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1), big_word)
if not recursive then
if
(c_class == cc.punctuation and next_char_class ~= cc.punctuation)
or (next_char_class == cc.punctuation and c_class ~= cc.punctuation)
or (c_class == cc.emoji and next_char_class ~= cc.emoji)
or (next_char_class == cc.emoji and c_class ~= cc.emoji)
then
offset = offset + 1
char = vim.fn.strcharpart(str, offset - 1, 1)
c_class = utils.char_class(char, big_word)
next_char_class = utils.char_class(vim.fn.strcharpart(str, (offset - 1) + 1, 1), big_word)
end
end

if c_class ~= cc.whitespace and next_char_class ~= cc.whitespace then
Expand All @@ -100,7 +117,7 @@ function M.end_of_word(str, cursorcol, linelen, big_word)
--next word starts with punctuation
rev_offset = next_word_start
else
rev_offset = M.end_of_word(str, next_word_start, linelen, big_word)
rev_offset = M.end_of_word(str, next_word_start, linelen, big_word, true)
end
end
end
Expand Down Expand Up @@ -260,7 +277,7 @@ function M.matching_comment(str, cursorcol, linelen)
next_char = vim.fn.strcharpart(str, offset, 1)
if char == "*" and next_char == "/" then
-- return the slash of the closing comment
return offset + 1
return offset
end
offset = offset + 1
end
Expand Down
3 changes: 3 additions & 0 deletions lua/precognition/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,9 @@ local state = {
ns = function()
return ns
end,
default_hint_config = function()
return defaultHintConfig
end,
is_visible = function()
return visible
end,
Expand Down
90 changes: 90 additions & 0 deletions tests/precognition/dts.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
local precognition = require("precognition")
local hm = require("precognition.horizontal_motions")
local dts = require("tests.precognition.utils.dts")

local USAGE = [[
Runs dts testing for precognition marks

USAGE:
nvim -u tests/minimal.lua -l tests/precognition/dts.lua SEED_START NUM_SIMS

]]

local M = {}

function M.test(seed)
local data = dts.generate_random_line(seed)

--TODO: Currently bracket matching only works with M cpoptions
--see `:h %`
vim.o.cpoptions = vim.o.cpoptions .. "M"

local cur_line = data.line
local cursorcol = data.cursor_col
local line_len = vim.fn.strcharlen(cur_line)

local virtual_line_marks = {
Caret = hm.line_start_non_whitespace(cur_line, cursorcol, line_len),
w = hm.next_word_boundary(cur_line, cursorcol, line_len, false),
e = hm.end_of_word(cur_line, cursorcol, line_len, false),
b = hm.prev_word_boundary(cur_line, cursorcol, line_len, false),
W = hm.next_word_boundary(cur_line, cursorcol, line_len, true),
E = hm.end_of_word(cur_line, cursorcol, line_len, true),
B = hm.prev_word_boundary(cur_line, cursorcol, line_len, true),
-- TODO: fix some edge cases around pairs and we can enable this
-- 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 temp_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(temp_buf, 0, -1, false, { cur_line })

for loc, col in pairs(virtual_line_marks) do
local key = precognition.default_hint_config[loc].text
vim.api.nvim_set_current_buf(temp_buf)
vim.fn.setcursorcharpos(1, cursorcol)
local cur_before = vim.fn.getcursorcharpos(0)
vim.api.nvim_feedkeys(key, "ntx", true)
local cur_after = vim.fn.getcursorcharpos(0)
local actual_col = cur_after[3]
if col ~= 0 then
if col ~= actual_col then
vim.print(string.format("[SEED: %d]%s", seed, cur_line))
vim.print(
string.format("with cursor at %s, motion %s, expected %s, got %s", cursorcol, key, col, actual_col)
)
vim.print(
string.format(
"before: %s, input %s, after: %s",
vim.inspect(cur_before),
key,
vim.inspect(cur_after)
)
)
vim.print(vim.inspect(virtual_line_marks))
os.exit(1)
end
end
end
if seed % 10000 == 0 then
vim.print(string.format("[SEED: %d]", seed))
end
vim.api.nvim_buf_delete(temp_buf, { force = true })
end

local seed_start = tonumber(_G.arg[1])
local num_sims = tonumber(_G.arg[2])

if (not num_sims or type(num_sims) ~= "number") or (not seed_start or type(seed_start) ~= "number") then
print(USAGE)
else
local seed = seed_start
local seed_end = seed_start + num_sims
while seed <= seed_end do
M.test(seed)
seed = seed + 1
end
end

return M
20 changes: 18 additions & 2 deletions tests/precognition/horizontal_motions_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ end)
describe("matching comments", function()
it("if cursor is over a comment it can find the pair", function()
local str = "abc /*efg*/"
eq(11, hm.matching_comment(str, 5, #str))
eq(11, hm.matching_comment(str, 6, #str))
eq(10, hm.matching_comment(str, 5, #str))
eq(10, hm.matching_comment(str, 6, #str))
eq(0, hm.matching_comment(str, 7, #str))
eq(5, hm.matching_comment(str, 10, #str))
eq(5, hm.matching_comment(str, 11, #str))
Expand Down Expand Up @@ -317,11 +317,27 @@ describe("edge case", function()
eq(3, hm.prev_word_boundary(str, 5, len, false))
end)

it("multibyte end_of_word", function()
local str = "# 💭👀precognition.nvim"
local len = vim.fn.strcharlen(str)
eq(4, hm.end_of_word(str, 1, len, false))
eq(16, hm.end_of_word(str, 4, len, false))

str = "🌞7😘aa"
len = vim.fn.strcharlen(str)
eq(3, hm.end_of_word(str, 2, len, false))
end)

it("quoted strings", function()
local str = 'this = "that"'
eq(8, hm.end_of_word(str, 6, #str, false))

str = 'b = "^", c = 2 },'
eq(8, hm.end_of_word(str, 3, #str, false))
end)

it("end of words with specials", function()
local str = "b t~a"
eq(3, hm.end_of_word(str, 1, #str, false))
end)
end)
52 changes: 52 additions & 0 deletions tests/precognition/utils/dts.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
local M = {}

local ranges = {
{ 32, 126 }, -- Basic Latin (ASCII)
--TODO: Add other character ranges but this opens a load of multibyte edge cases
-- { 160, 591 }, -- Latin-1 Supplement and Latin Extended-A
-- { 880, 2047 }, -- Greek, Cyrillic, Armenian, Hebrew
-- { 8192, 8303 }, -- General Punctuation
-- { 9728, 9983 }, -- Miscellaneous Symbols
-- { 12352, 12447 }, -- Hiragana
-- { 19904, 19967 }, -- Mahjong Tiles
-- { 0x1F300, 0x1F6FF }, -- Emoji
}

---@class dts.Random
---@field cursor_col number
---@field line string

---Generate a random line with Unicode characters.
---@param seed number
---@return dts.Random
function M.generate_random_line(seed)
math.randomseed(seed) -- Set the seed for reproducibility

-- Randomize the line length (e.g., between 20 and 100 characters)
local line_length = math.random(20, 100)

-- Function to generate a random printable Unicode character
local function random_unicode_char()
-- Randomly pick a range
local range = ranges[math.random(1, #ranges)]
-- Generate a random codepoint within the selected range
local codepoint = math.random(range[1], range[2])
return vim.fn.nr2char(codepoint) -- Convert codepoint to UTF-8 character
end

-- Generate the random line with Unicode characters
local line = ""
for _ = 1, line_length do
line = line .. random_unicode_char()
end

-- Choose a random cursor position within the line
local cursor_col = math.random(1, vim.fn.strcharlen(line)) -- Ensure valid cursor position
---@type dts.Random
return {
cursor_col = cursor_col,
line = line,
}
end

return M
Loading