mirror of
https://github.com/nvim-treesitter/nvim-treesitter.git
synced 2026-07-02 03:26:52 -04:00
The range from ts nodes are a little different than neovim's nodes. They start at 0 and the end is exclusive. For example, a nvim range (1, 3, 2, 4) is the equivalent to the ts range (0, 2, 1, 4). Since we may hit parent nodes that have the same range as its child, we skip those till we find one that actually changes the selection (since this is the relevant part for the user). Fixes https://github.com/nvim-treesitter/nvim-treesitter/issues/232
124 lines
3.6 KiB
Lua
124 lines
3.6 KiB
Lua
local api = vim.api
|
|
|
|
local configs = require'nvim-treesitter.configs'
|
|
local ts_utils = require'nvim-treesitter.ts_utils'
|
|
local locals = require'nvim-treesitter.locals'
|
|
local parsers = require'nvim-treesitter.parsers'
|
|
|
|
local M = {}
|
|
|
|
local selections = {}
|
|
|
|
function M.init_selection()
|
|
local buf = api.nvim_get_current_buf()
|
|
local node = ts_utils.get_node_at_cursor()
|
|
selections[buf] = { [1] = node }
|
|
ts_utils.update_selection(buf, node)
|
|
end
|
|
|
|
--- Get a ts compatible range of the current visual selection.
|
|
--
|
|
-- The range of ts nodes start with 0 and the ending range is exclusive.
|
|
local function visual_selection_range()
|
|
local _, csrow, cscol, _ = unpack(vim.fn.getpos("'<"))
|
|
local _, cerow, cecol, _ = unpack(vim.fn.getpos("'>"))
|
|
if csrow < cerow or (csrow == cerow and cscol <= cecol) then
|
|
return csrow - 1, cscol - 1, cerow - 1, cecol
|
|
else
|
|
return cerow - 1, cecol - 1, csrow - 1, cscol
|
|
end
|
|
end
|
|
|
|
local function range_matches(node)
|
|
local csrow, cscol, cerow, cecol = visual_selection_range()
|
|
local srow, scol, erow, ecol = node:range()
|
|
return srow == csrow and scol == cscol and erow == cerow and ecol == cecol
|
|
end
|
|
|
|
local function select_incremental(get_parent)
|
|
return function()
|
|
local buf = api.nvim_get_current_buf()
|
|
local nodes = selections[buf]
|
|
|
|
local csrow, cscol, cerow, cecol = visual_selection_range()
|
|
-- Initialize incremental selection with current selection
|
|
if not nodes or #nodes == 0 or not range_matches(nodes[#nodes]) then
|
|
local root = parsers.get_parser().tree:root()
|
|
local node = root:named_descendant_for_range(csrow, cscol, cerow, cecol)
|
|
ts_utils.update_selection(buf, node)
|
|
if nodes and #nodes > 0 then
|
|
table.insert(selections[buf], node)
|
|
else
|
|
selections[buf] = { [1] = node }
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Find a node that changes the current selection.
|
|
local node = nodes[#nodes]
|
|
while true do
|
|
node = get_parent(node)
|
|
if not node then return end
|
|
local srow, scol, erow, ecol = 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)
|
|
if node ~= nodes[#nodes] then
|
|
table.insert(nodes, node)
|
|
end
|
|
ts_utils.update_selection(buf, node)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
M.node_incremental = select_incremental(function(node)
|
|
return node:parent() or node
|
|
end)
|
|
|
|
M.scope_incremental = select_incremental(function(node)
|
|
return locals.containing_scope(node:parent() or node)
|
|
end)
|
|
|
|
function M.node_decremental()
|
|
local buf = api.nvim_get_current_buf()
|
|
local nodes = selections[buf]
|
|
if not nodes or #nodes < 2 then return end
|
|
|
|
table.remove(selections[buf])
|
|
local node = nodes[#nodes]
|
|
ts_utils.update_selection(buf, node)
|
|
end
|
|
|
|
function M.attach(bufnr)
|
|
local config = configs.get_module('incremental_selection')
|
|
for funcname, mapping in pairs(config.keymaps) do
|
|
local mode
|
|
if funcname == "init_selection" then
|
|
mode = 'n'
|
|
else
|
|
mode = 'x'
|
|
end
|
|
local cmd = string.format(":lua require'nvim-treesitter.incremental_selection'.%s()<CR>", funcname)
|
|
api.nvim_buf_set_keymap(bufnr, mode, mapping, cmd, { silent = true, noremap = true })
|
|
end
|
|
end
|
|
|
|
function M.detach(bufnr)
|
|
local config = configs.get_module('incremental_selection')
|
|
for f, mapping in pairs(config.keymaps) do
|
|
if f == "init_selection" then
|
|
api.nvim_buf_del_keymap(bufnr, 'n', mapping)
|
|
else
|
|
api.nvim_buf_del_keymap(bufnr, 'x', mapping)
|
|
end
|
|
end
|
|
end
|
|
|
|
return M
|