Use vim-range style (1-index based) when possible (#1841)

https://github.com/nvim-treesitter/nvim-treesitter/pull/1829
half fixed incremental selection for the vim parser,
but other bugs still remain (infinite selection and skip selecting the
root node).

Problems can be replicated with these two files:

(missing selecting the root node)

```vim
set scrolloff=7
set scrolloff=7
```

(infinite loop)

```vim
set scrolloff=7
```

The main problem is that we try to map
the current selection range to a TS range,
but the TS range of a node could include the EOL/EOL marks
so it's impossible to know when to change the vim range
to match the TS range, is more easy to transform the
TS range to a vim range and do the comparison.
This commit is contained in:
Santos Gallegos 2021-09-24 17:46:44 -05:00 committed by GitHub
parent 6b3f908751
commit e4c56e691a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 38 additions and 42 deletions

View file

@ -17,9 +17,9 @@ function M.init_selection()
ts_utils.update_selection(buf, node)
end
--- Get a ts compatible range of the current visual selection.
--- Get the range of the current visual selection.
--
-- The range of ts nodes start with 0 and the ending range is exclusive.
-- The range start with 1 and the ending is inclusive.
local function visual_selection_range()
local _, csrow, cscol, _ = unpack(vim.fn.getpos "'<")
local _, cerow, cecol, _ = unpack(vim.fn.getpos "'>")
@ -27,33 +27,23 @@ local function visual_selection_range()
local start_row, start_col, end_row, end_col
if csrow < cerow or (csrow == cerow and cscol <= cecol) then
start_row = csrow - 1
start_col = cscol - 1
end_row = cerow - 1
start_row = csrow
start_col = cscol
end_row = cerow
end_col = cecol
else
start_row = cerow - 1
start_col = cecol - 1
end_row = csrow - 1
start_row = cerow
start_col = cecol
end_row = csrow
end_col = cscol
end
-- The last char in ts is equivalent to the EOF in another line.
local last_row = vim.fn.line "$"
local last_col = vim.fn.col { last_row, "$" }
last_row = last_row - 1
last_col = last_col - 1
if end_row == last_row and end_col == last_col then
end_row = end_row + 1
end_col = 0
end
return start_row, start_col, end_row, end_col
end
local function range_matches(node)
local csrow, cscol, cerow, cecol = visual_selection_range()
local srow, scol, erow, ecol = node:range()
local srow, scol, erow, ecol = ts_utils.get_vim_range { node:range() }
return srow == csrow and scol == cscol and erow == cerow and ecol == cecol
end
@ -66,7 +56,7 @@ local function select_incremental(get_parent)
-- Initialize incremental selection with current selection
if not nodes or #nodes == 0 or not range_matches(nodes[#nodes]) then
local root = parsers.get_parser():parse()[1]:root()
local node = root:named_descendant_for_range(csrow, cscol, cerow, cecol)
local node = root:named_descendant_for_range(csrow - 1, cscol - 1, cerow - 1, cecol)
ts_utils.update_selection(buf, node)
if nodes and #nodes > 0 then
table.insert(selections[buf], node)
@ -84,14 +74,14 @@ local function select_incremental(get_parent)
-- Keep searching in the main tree
-- TODO: we should search on the parent tree of the current node.
local root = parsers.get_parser():parse()[1]:root()
parent = root:named_descendant_for_range(csrow, cscol, cerow, cecol)
if not parent or parent == node then
parent = root:named_descendant_for_range(csrow - 1, cscol - 1, cerow - 1, cecol)
if not parent or root == node or parent == node then
ts_utils.update_selection(buf, node)
return
end
end
node = parent
local srow, scol, erow, ecol = node:range()
local srow, scol, erow, ecol = ts_utils.get_vim_range { node:range() }
local same_range = (srow == csrow and scol == cscol and erow == cerow and ecol == cecol)
if not same_range then
table.insert(selections[buf], node)

View file

@ -175,6 +175,29 @@ function M.highlight_node(node, buf, hl_namespace, hl_group)
M.highlight_range({ node:range() }, buf, hl_namespace, hl_group)
end
--- Get a compatible vim range (1 index based) from a TS node range.
--
-- TS nodes start with 0 and the end col is ending exclusive.
-- They also treat a EOF/EOL char as a char ending in the first
-- col of the next row.
function M.get_vim_range(range, buf)
local srow, scol, erow, ecol = unpack(range)
srow = srow + 1
scol = scol + 1
erow = erow + 1
if ecol == 0 then
-- Use the value of the last col of the previous row instead.
erow = erow - 1
if not buf or buf == 0 then
ecol = vim.fn.col { erow, "$" } - 1
else
ecol = #api.nvim_buf_get_lines(buf, erow - 1, erow, false)[1]
end
end
return srow, scol, erow, ecol
end
function M.highlight_range(range, buf, hl_namespace, hl_group)
local start_row, start_col, end_row, end_col = unpack(range)
vim.highlight.range(buf, hl_namespace, hl_group, { start_row, start_col }, { end_row, end_col })
@ -185,17 +208,7 @@ end
-- "blockwise" or "<C-v>" (as a string with 5 characters or a single character)
function M.update_selection(buf, node, selection_mode)
selection_mode = selection_mode or "charwise"
local start_row, start_col, end_row, end_col = M.get_node_range(node)
if end_row == vim.fn.line "$" then
end_col = #vim.fn.getline "$"
end
-- Convert to 1-based indices
start_row = start_row + 1
start_col = start_col + 1
end_row = end_row + 1
end_col = end_col + 1
local start_row, start_col, end_row, end_col = M.get_vim_range({ M.get_node_range(node) }, buf)
vim.fn.setpos(".", { buf, start_row, start_col, 0 })
@ -205,14 +218,7 @@ function M.update_selection(buf, node, selection_mode)
---- command to enter blockwise mode
local mode_string = vim.api.nvim_replace_termcodes(v_table[selection_mode] or selection_mode, true, true, true)
vim.cmd("normal! " .. mode_string)
-- Convert exclusive end position to inclusive
if end_col == 1 then
local previous_col = vim.fn.col { end_row - 1, "$" } - 1
vim.fn.setpos(".", { buf, end_row - 1, previous_col, 0 })
else
vim.fn.setpos(".", { buf, end_row, end_col - 1, 0 })
end
vim.fn.setpos(".", { buf, end_row, end_col, 0 })
end
-- Byte length of node range