range: extmarks/tsquery; renderer: text-change
This commit is contained in:
@@ -238,6 +238,128 @@ describe('Range', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('from_tsquery_caps', function()
|
||||
withbuf({
|
||||
'-- a comment',
|
||||
'',
|
||||
'function foo(bar) end',
|
||||
'',
|
||||
'-- a middle comment',
|
||||
'',
|
||||
'function bar(baz) end',
|
||||
'',
|
||||
'-- another comment',
|
||||
}, function()
|
||||
vim.cmd.setfiletype 'lua'
|
||||
|
||||
--- @param contains_cursor? boolean
|
||||
local function get_caps(contains_cursor)
|
||||
if contains_cursor == nil then contains_cursor = true end
|
||||
return (Range.from_tsquery_caps(
|
||||
0,
|
||||
'(function_declaration) @f',
|
||||
{ contains_cursor = contains_cursor }
|
||||
)).f or {}
|
||||
end
|
||||
|
||||
local caps = get_caps(false)
|
||||
assert.are.same(#caps, 2)
|
||||
assert.are.same(vim.iter(caps):map(function(c) return c:text() end):totable(), {
|
||||
'function foo(bar) end',
|
||||
'function bar(baz) end',
|
||||
})
|
||||
|
||||
Pos.new(0, 1, 1):save_to_pos '.'
|
||||
caps = get_caps()
|
||||
assert.are.same(#caps, 0)
|
||||
|
||||
Pos.new(0, 3, 18):save_to_pos '.'
|
||||
caps = get_caps()
|
||||
assert.are.same(#caps, 1)
|
||||
assert.are.same(caps[1]:text(), 'function foo(bar) end')
|
||||
|
||||
Pos.new(0, 5, 1):save_to_pos '.'
|
||||
caps = get_caps()
|
||||
assert.are.same(#caps, 0)
|
||||
|
||||
Pos.new(0, 7, 1):save_to_pos '.'
|
||||
caps = get_caps()
|
||||
assert.are.same(#caps, 1)
|
||||
assert.are.same(caps[1]:text(), 'function bar(baz) end')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('from_tsquery_caps with string array filter', function()
|
||||
withbuf({
|
||||
'{',
|
||||
' sample_key1 = "sample-value1",',
|
||||
' sample_key2 = "sample-value2",',
|
||||
'}',
|
||||
}, function()
|
||||
vim.cmd.setfiletype 'lua'
|
||||
|
||||
-- Place cursor in "sample-value1"
|
||||
Pos.new(0, 2, 25):save_to_pos '.'
|
||||
|
||||
-- Query that captures both keys and values in pairs
|
||||
local query = [[
|
||||
(field
|
||||
name: _ @key
|
||||
value: _ @value)
|
||||
]]
|
||||
|
||||
local ranges = Range.from_line(0, 2):tsquery(query)
|
||||
|
||||
-- Should have both @key and @value captures for the first pair only
|
||||
-- (since cursor is in sample-value1)
|
||||
assert(ranges, 'Range should not be nil')
|
||||
assert(ranges.key, 'Range.key should not be nil')
|
||||
assert(ranges.value, 'Range.value should not be nil')
|
||||
|
||||
-- Should have exactly one key and one value
|
||||
assert.are.same(#ranges.key, 1)
|
||||
assert.are.same(#ranges.value, 1)
|
||||
|
||||
-- Check that we got sample-key1 and sample-value1
|
||||
assert.are.same(ranges.key[1]:text(), 'sample_key1')
|
||||
assert.are.same(ranges.value[1]:text(), '"sample-value1"')
|
||||
end)
|
||||
|
||||
-- Make sure this works when the match is on the last line:
|
||||
withbuf({
|
||||
'{sample_key1= "sample-value1",',
|
||||
'sample_key2= "sample-value2"}',
|
||||
}, function()
|
||||
vim.cmd.setfiletype 'lua'
|
||||
|
||||
-- Place cursor in "sample-value1"
|
||||
Pos.new(0, 2, 25):save_to_pos '.'
|
||||
|
||||
-- Query that captures both keys and values in pairs
|
||||
local query = [[
|
||||
(field
|
||||
name: _ @key
|
||||
value: _ @value)
|
||||
]]
|
||||
|
||||
local ranges = Range.from_line(0, 2):tsquery(query)
|
||||
|
||||
-- Should have both @key and @value captures for the first pair only
|
||||
-- (since cursor is in sample-value1)
|
||||
assert(ranges, 'Range should not be nil')
|
||||
assert(ranges.key, 'Range.key should not be nil')
|
||||
assert(ranges.value, 'Range.value should not be nil')
|
||||
|
||||
-- Should have exactly one key and one value
|
||||
assert.are.same(#ranges.key, 1)
|
||||
assert.are.same(#ranges.value, 1)
|
||||
|
||||
-- Check that we got sample-key2 and sample-value2
|
||||
assert.are.same(ranges.key[1]:text(), 'sample_key2')
|
||||
assert.are.same(ranges.value[1]:text(), '"sample-value2"')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('should get nearest block', function()
|
||||
withbuf({
|
||||
'this is a {',
|
||||
@@ -318,18 +440,89 @@ describe('Range', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('from_cmd_args', function()
|
||||
local args = { range = 1 }
|
||||
it('from_cmd_args: range=0', function()
|
||||
local args = { range = 0 }
|
||||
withbuf(
|
||||
{ 'line one', 'and line two' },
|
||||
function() assert.are.same(Range.from_cmd_args(args), nil) end
|
||||
)
|
||||
end)
|
||||
|
||||
it('from_cmd_args: range=1', function()
|
||||
local args = { range = 1, line1 = 1 }
|
||||
withbuf({ 'line one', 'and line two' }, function()
|
||||
local a = Pos.new(nil, 1, 1)
|
||||
local b = Pos.new(nil, 2, 2)
|
||||
local b = Pos.new(nil, 1, Pos.MAX_COL)
|
||||
|
||||
local range = Range.from_cmd_args(args) --[[@as u.Range]]
|
||||
assert.are.same(range.start, a)
|
||||
assert.are.same(range.stop, b)
|
||||
assert.are.same(range.mode, 'V')
|
||||
assert.are.same(range:text(), 'line one')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('from_cmd_args: range=2: no-visual', function()
|
||||
local args = { range = 2, line1 = 1, line2 = 2 }
|
||||
withbuf({ 'line one', 'and line two' }, function()
|
||||
local range = Range.from_cmd_args(args) --[[@as u.Range]]
|
||||
assert.are.same(range.start, Pos.new(nil, 1, 1))
|
||||
assert.are.same(range.stop, Pos.new(nil, 2, Pos.MAX_COL))
|
||||
assert.are.same(range.mode, 'V')
|
||||
assert.are.same(range:text(), 'line one\nand line two')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('from_cmd_args: range=2: visual: linewise', function()
|
||||
local args = { range = 2, line1 = 1, line2 = 2 }
|
||||
withbuf({ 'line one', 'and line two' }, function()
|
||||
local a = Pos.new(nil, 1, 1)
|
||||
local b = Pos.new(nil, 2, Pos.MAX_COL)
|
||||
a:save_to_pos "'<"
|
||||
b:save_to_pos "'>"
|
||||
local range = Range.from_cmd_args(args) --[[@as u.Range]]
|
||||
assert.are.same(range.start, a)
|
||||
assert.are.same(range.stop, b)
|
||||
assert.are.same(range.mode, 'V')
|
||||
assert.are.same(range:text(), 'line one\nand line two')
|
||||
end)
|
||||
end)
|
||||
|
||||
local range = Range.from_cmd_args(args)
|
||||
it('from_cmd_args: range=2: visual: charwise', function()
|
||||
local args = { range = 2, line1 = 1, line2 = 1 }
|
||||
withbuf({ 'line one', 'and line two' }, function()
|
||||
-- Simulate a visual selection:
|
||||
local a = Pos.new(nil, 1, 1)
|
||||
local b = Pos.new(nil, 1, 4)
|
||||
a:save_to_pos "'<"
|
||||
b:save_to_pos "'>"
|
||||
Range.new(a, b, 'v'):set_visual_selection()
|
||||
assert.are.same(vim.fn.mode(), 'v')
|
||||
|
||||
-- In this simulated setup, we need to force visualmode() to return
|
||||
-- 'v' and histget() to return [['<,'>]]:
|
||||
|
||||
-- visualmode()
|
||||
local orig_visualmode = vim.fn.visualmode
|
||||
--- @diagnostic disable-next-line: duplicate-set-field
|
||||
function vim.fn.visualmode() return 'v' end
|
||||
assert.are.same(vim.fn.visualmode(), 'v')
|
||||
|
||||
-- histget()
|
||||
local orig_histget = vim.fn.histget
|
||||
--- @diagnostic disable-next-line: duplicate-set-field
|
||||
function vim.fn.histget(x, y) return [['<,'>]] end
|
||||
|
||||
-- Now run the test:
|
||||
local range = Range.from_cmd_args(args) --[[@as u.Range]]
|
||||
assert.are.same(range.start, a)
|
||||
assert.are.same(range.stop, b)
|
||||
assert.are.same(range.mode, 'v')
|
||||
assert.are.same(range:text(), 'line')
|
||||
|
||||
-- Reset visualmode() and histget():
|
||||
vim.fn.visualmode = orig_visualmode
|
||||
vim.fn.histget = orig_histget
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -617,4 +810,86 @@ describe('Range', function()
|
||||
}, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
||||
end)
|
||||
end)
|
||||
|
||||
it('can save to extmark', function()
|
||||
withbuf({
|
||||
'The quick brown',
|
||||
'fox',
|
||||
'jumps',
|
||||
'over',
|
||||
'the lazy dog',
|
||||
}, function()
|
||||
-- Construct a range over 'fox jumps'
|
||||
local r = Range.new(Pos.new(nil, 2, 1), Pos.new(nil, 3, 5), 'v')
|
||||
local extrange = r:save_to_extmark()
|
||||
assert.are.same({ 'fox', 'jumps' }, extrange:range():lines())
|
||||
-- change 'jumps' to 'leaps':
|
||||
vim.api.nvim_buf_set_text(extrange.bufnr, 2, 0, 2, 4, { 'leap' })
|
||||
assert.are.same({
|
||||
'The quick brown',
|
||||
'fox',
|
||||
'leaps',
|
||||
'over',
|
||||
'the lazy dog',
|
||||
}, vim.api.nvim_buf_get_lines(extrange.bufnr, 0, -1, false))
|
||||
assert.are.same({ 'fox', 'leaps' }, extrange:range():lines())
|
||||
end)
|
||||
end)
|
||||
|
||||
it('can save linewise extmark', function()
|
||||
withbuf({
|
||||
'The quick brown',
|
||||
'fox',
|
||||
'jumps',
|
||||
'over',
|
||||
'the lazy dog',
|
||||
}, function()
|
||||
-- Construct a range over 'fox jumps'
|
||||
local r = Range.new(Pos.new(nil, 2, 1), Pos.new(nil, 3, Pos.MAX_COL), 'V')
|
||||
local extrange = r:save_to_extmark()
|
||||
assert.are.same({ 'fox', 'jumps' }, extrange:range():lines())
|
||||
|
||||
local extmark = vim.api.nvim_buf_get_extmark_by_id(
|
||||
extrange.bufnr,
|
||||
vim.api.nvim_create_namespace 'u.range',
|
||||
extrange.id,
|
||||
{ details = true }
|
||||
)
|
||||
local row0, col0, details = unpack(extmark)
|
||||
assert.are.same({ 1, 0 }, { row0, col0 })
|
||||
assert.are.same({ 3, 0 }, { details.end_row, details.end_col })
|
||||
end)
|
||||
end)
|
||||
|
||||
it('discerns range bounds from extmarks beyond the end of the buffer', function()
|
||||
local Buffer = require 'u.buffer'
|
||||
|
||||
vim.cmd.vnew()
|
||||
local left = Buffer.current()
|
||||
left:set_tmp_options()
|
||||
vim.cmd.vnew()
|
||||
local right = Buffer.current()
|
||||
right:set_tmp_options()
|
||||
|
||||
left:all():replace {
|
||||
'one',
|
||||
'two',
|
||||
'three',
|
||||
}
|
||||
local left_all_ext = left:all():save_to_extmark()
|
||||
|
||||
right:all():replace {
|
||||
'foo',
|
||||
'bar',
|
||||
}
|
||||
|
||||
vim.api.nvim_set_current_buf(right.bufnr)
|
||||
vim.cmd [[normal! ggyG]]
|
||||
vim.api.nvim_set_current_buf(left.bufnr)
|
||||
vim.cmd [[normal! ggVGp]]
|
||||
|
||||
assert.are.same({ 'foo', 'bar' }, left_all_ext:range():lines())
|
||||
vim.api.nvim_buf_delete(left.bufnr, { force = true })
|
||||
vim.api.nvim_buf_delete(right.bufnr, { force = true })
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -83,13 +83,13 @@ describe('Renderer', function()
|
||||
end)
|
||||
|
||||
--
|
||||
-- get_pos_infos
|
||||
-- get_tags_at
|
||||
--
|
||||
|
||||
it('should return no extmarks for an empty buffer', function()
|
||||
withbuf({}, function()
|
||||
local r = R.Renderer.new(0)
|
||||
local pos_infos = r:get_pos_infos { 0, 0 }
|
||||
local pos_infos = r:get_tags_at { 0, 0 }
|
||||
assert.are.same(pos_infos, {})
|
||||
end)
|
||||
end)
|
||||
@@ -102,12 +102,28 @@ describe('Renderer', function()
|
||||
R.h('text', { hl = 'HighlightGroup2' }, ' World'),
|
||||
}
|
||||
|
||||
local pos_infos = r:get_pos_infos { 0, 2 }
|
||||
|
||||
local pos_infos = r:get_tags_at { 0, 2 }
|
||||
assert.are.same(#pos_infos, 1)
|
||||
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup1')
|
||||
assert.are.same(pos_infos[1].extmark.start, { 0, 0 })
|
||||
assert.are.same(pos_infos[1].extmark.stop, { 0, 5 })
|
||||
pos_infos = r:get_tags_at { 0, 4 }
|
||||
assert.are.same(#pos_infos, 1)
|
||||
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup1')
|
||||
assert.are.same(pos_infos[1].extmark.start, { 0, 0 })
|
||||
assert.are.same(pos_infos[1].extmark.stop, { 0, 5 })
|
||||
|
||||
pos_infos = r:get_tags_at { 0, 5 }
|
||||
assert.are.same(#pos_infos, 1)
|
||||
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup2')
|
||||
assert.are.same(pos_infos[1].extmark.start, { 0, 5 })
|
||||
assert.are.same(pos_infos[1].extmark.stop, { 0, 11 })
|
||||
|
||||
-- In insert mode, bounds are eagerly included:
|
||||
pos_infos = r:get_tags_at({ 0, 5 }, 'i')
|
||||
assert.are.same(#pos_infos, 2)
|
||||
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup1')
|
||||
assert.are.same(pos_infos[2].tag.attributes.hl, 'HighlightGroup2')
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -125,11 +141,122 @@ describe('Renderer', function()
|
||||
}),
|
||||
}
|
||||
|
||||
local pos_infos = r:get_pos_infos { 0, 5 }
|
||||
local pos_infos = r:get_tags_at { 0, 5 }
|
||||
|
||||
assert.are.same(#pos_infos, 2)
|
||||
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup2')
|
||||
assert.are.same(pos_infos[2].tag.attributes.hl, 'HighlightGroup1')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('repeated patch_lines calls should not change the buffer content', function()
|
||||
local lines = {
|
||||
[[{ {]],
|
||||
[[ bounds = {]],
|
||||
[[ start1 = { 1, 1 },]],
|
||||
[[ stop1 = { 4, 1 }]],
|
||||
[[ },]],
|
||||
[[ end_right_gravity = true,]],
|
||||
[[ id = 1,]],
|
||||
[[ ns_id = 623,]],
|
||||
[[ ns_name = "my.renderer:91",]],
|
||||
[[ right_gravity = false]],
|
||||
[[ } }]],
|
||||
[[]],
|
||||
}
|
||||
withbuf(lines, function()
|
||||
local Buffer = require 'u.buffer'
|
||||
R.Renderer.patch_lines(0, nil, lines)
|
||||
assert.are.same(Buffer.current():all():lines(), lines)
|
||||
|
||||
R.Renderer.patch_lines(0, lines, lines)
|
||||
assert.are.same(Buffer.current():all():lines(), lines)
|
||||
|
||||
R.Renderer.patch_lines(0, lines, lines)
|
||||
assert.are.same(Buffer.current():all():lines(), lines)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('should fire text-changed events', function()
|
||||
withbuf({}, function()
|
||||
local Buffer = require 'u.buffer'
|
||||
local buf = Buffer.current()
|
||||
local r = R.Renderer.new(0)
|
||||
local captured_changed_text = ''
|
||||
r:render {
|
||||
R.h('text', {
|
||||
on_change = function(txt) captured_changed_text = txt end,
|
||||
}, {
|
||||
'one\n',
|
||||
'two\n',
|
||||
'three\n',
|
||||
}),
|
||||
}
|
||||
|
||||
vim.fn.setreg('"', 'bleh')
|
||||
vim.cmd [[normal! ggVGp]]
|
||||
-- For some reason, the autocmd does not fire in the busted environment.
|
||||
-- We'll call the handler ourselves:
|
||||
r:_on_text_changed()
|
||||
|
||||
assert.are.same(buf:all():text(), 'bleh')
|
||||
assert.are.same(captured_changed_text, 'bleh')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('should find tags by position', function()
|
||||
withbuf({}, function()
|
||||
local r = R.Renderer.new(0)
|
||||
r:render {
|
||||
'pre',
|
||||
R.h('text', {
|
||||
id = 'outer',
|
||||
}, {
|
||||
'inner-pre',
|
||||
R.h('text', {
|
||||
id = 'inner',
|
||||
}, {
|
||||
'inner-text',
|
||||
}),
|
||||
'inner-post',
|
||||
}),
|
||||
'post',
|
||||
}
|
||||
|
||||
local tags = r:get_tags_at { 0, 11 }
|
||||
assert.are.same(#tags, 1)
|
||||
assert.are.same(tags[1].tag.attributes.id, 'outer')
|
||||
|
||||
tags = r:get_tags_at { 0, 12 }
|
||||
assert.are.same(#tags, 2)
|
||||
assert.are.same(tags[1].tag.attributes.id, 'inner')
|
||||
assert.are.same(tags[2].tag.attributes.id, 'outer')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('should find tags by id', function()
|
||||
withbuf({}, function()
|
||||
local r = R.Renderer.new(0)
|
||||
r:render {
|
||||
R.h('text', {
|
||||
id = 'outer',
|
||||
}, {
|
||||
'inner-pre',
|
||||
R.h('text', {
|
||||
id = 'inner',
|
||||
}, {
|
||||
'inner-text',
|
||||
}),
|
||||
'inner-post',
|
||||
}),
|
||||
'post',
|
||||
}
|
||||
|
||||
local bounds = r:get_tag_bounds 'outer'
|
||||
assert.are.same(bounds, { start = { 0, 0 }, stop = { 0, 29 } })
|
||||
|
||||
bounds = r:get_tag_bounds 'inner'
|
||||
assert.are.same(bounds, { start = { 0, 9 }, stop = { 0, 19 } })
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user