mirror of
https://github.com/nvim-treesitter/nvim-treesitter.git
synced 2026-07-14 17:30:08 -04:00
refacto/feat: better handling of parser updates
features:
- node_movement is moving between scopes.
- add selection initialization from normal mode
- add a decremental selection
improvements:
- attach to buffer to run tree parsing on change
- run state update on CursorMoved
- the buffer state is:
```
{
cursor_pos = { row=row, col=col },
current_node = node_under_cursor,
selection = {
range = nil, -- activates when starting a selection
nodes = {} -- filling up when starting an incremental selection
},
parser = parser, -- parser for current buffer
}
```
- refacto all the modules reliant on parsing the tree, update the current nodes, get the current nodes...
fixes:
- fix has_parser to look for .so libraries
- fix should select the whole file when selection root in selection
This commit is contained in:
parent
307c78aa1e
commit
45dcebb15f
14 changed files with 555 additions and 333 deletions
26
README.md
26
README.md
|
|
@ -89,25 +89,27 @@ in your `init.vim`:
|
||||||
lua <<EOF
|
lua <<EOF
|
||||||
require'nvim-treesitter.configs'.setup {
|
require'nvim-treesitter.configs'.setup {
|
||||||
highlight = {
|
highlight = {
|
||||||
enable = true, -- false will disable the whole extension
|
enable = true, -- false will disable the whole extension
|
||||||
disable = { 'c', 'rust' }, -- list of language that will be disabled
|
disable = { 'c', 'rust' }, -- list of language that will be disabled
|
||||||
},
|
},
|
||||||
incremental_selection = { -- this enables incremental selection
|
incremental_selection = {
|
||||||
enable = true,
|
enable = true,
|
||||||
disable = { 'cpp', 'lua' },
|
disable = { 'cpp', 'lua' },
|
||||||
keymaps = { -- mappings for incremental selection (visual mappings)
|
keymaps = { -- mappings for incremental selection (visual mappings)
|
||||||
node_incremental = "<leader>e", -- "grn" by default,
|
init_selection = 'gnn', -- maps in normal mode to init the node/scope selection
|
||||||
scope_incremental = "<leader>f" -- "grc" by default
|
node_incremental = "grn", -- increment to the upper named parent
|
||||||
|
scope_incremental = "grc", -- increment to the upper scope (as defined in locals.scm)
|
||||||
|
scope_decremental = "grm", -- decrement to the previous scope
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
node_movement = { -- this enables cursor movement in node hierarchy
|
node_movement = { -- allows cursor movement in node hierarchy
|
||||||
enable = true,
|
enable = true,
|
||||||
disable = { 'cpp', 'rust' },
|
disable = { 'cpp', 'rust' },
|
||||||
keymaps = { -- mappings for node movement (normal mappings)
|
keymaps = { -- mappings for scope movement (normal mappings)
|
||||||
move_up = "<a-k>", -- default is to move with alt key hold
|
parent_scope = "<a-k>", -- default is to move with alt key hold
|
||||||
move_down = "<a-j>",
|
child_scope = "<a-j>",
|
||||||
move_left = "<a-h>",
|
next_scope = "<a-h>",
|
||||||
move_right = "<a-l>",
|
previous_scope = "<a-l>",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ensure_installed = 'all' -- one of 'all', 'language', or a list of languages
|
ensure_installed = 'all' -- one of 'all', 'language', or a list of languages
|
||||||
|
|
@ -134,7 +136,7 @@ Some of these features are :
|
||||||
- [x] Incremental selection
|
- [x] Incremental selection
|
||||||
- [ ] Syntax based code folding
|
- [ ] Syntax based code folding
|
||||||
- [x] Consistent syntax highlighting (the api is not quite stable yet)
|
- [x] Consistent syntax highlighting (the api is not quite stable yet)
|
||||||
- [x] Cursor movement in node hierachy
|
- [x] Cursor movement in scope hierachy
|
||||||
- [x] Statusline indicator (`require'nvim-treesitter'.statusline(size)`)
|
- [x] Statusline indicator (`require'nvim-treesitter'.statusline(size)`)
|
||||||
|
|
||||||
You can find the roadmap [here](https://github.com/nvim-treesitter/nvim-treesitter/projects/1).
|
You can find the roadmap [here](https://github.com/nvim-treesitter/nvim-treesitter/projects/1).
|
||||||
|
|
|
||||||
|
|
@ -31,25 +31,27 @@ By default, everything is disabled. To enable support for features, in your `ini
|
||||||
lua <<EOF
|
lua <<EOF
|
||||||
require'nvim-treesitter.configs'.setup {
|
require'nvim-treesitter.configs'.setup {
|
||||||
highlight = {
|
highlight = {
|
||||||
enable = true, -- false will disable the whole extension
|
enable = true, -- false will disable the whole extension
|
||||||
disable = { 'c', 'rust' }, -- list of language that will be disabled
|
disable = { 'c', 'rust' }, -- list of language that will be disabled
|
||||||
},
|
},
|
||||||
incremental_selection = { -- this enables incremental selection
|
incremental_selection = {
|
||||||
enable = true,
|
enable = true,
|
||||||
disable = { 'cpp', 'lua' },
|
disable = { 'cpp', 'lua' },
|
||||||
keymaps = { -- mappings for incremental selection (visual mappings)
|
keymaps = { -- mappings for incremental selection (visual mappings)
|
||||||
node_incremental = "<leader>e", -- "grn" by default,
|
init_selection = 'gnn', -- maps in normal mode to init the node/scope selection
|
||||||
scope_incremental = "<leader>f" -- "grc" by default
|
node_incremental = "grn", -- increment to the upper named parent
|
||||||
|
scope_incremental = "grc", -- increment to the upper scope (as defined in locals.scm)
|
||||||
|
scope_decremental = "grm", -- decrement to the previous scope
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
node_movement = { -- this cursor movement in node hierachy
|
node_movement = { -- this cursor movement in node hierachy
|
||||||
enable = true,
|
enable = true,
|
||||||
disable = { 'cpp', 'rust' },
|
disable = { 'cpp', 'rust' },
|
||||||
keymaps = { -- mappings for node movement (normal mappings)
|
keymaps = { -- mappings for scope movement
|
||||||
move_up = "<a-k>", -- default is to move with alt key hold
|
parent_scope = "<a-k>", -- default is to move with alt key hold
|
||||||
move_down = "<a-j>",
|
child_scope = "<a-j>",
|
||||||
move_left = "<a-h>",
|
next_scope = "<a-h>",
|
||||||
move_right = "<a-l>",
|
previous_scope = "<a-l>",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ensure_installed = 'all' -- one of 'all', 'language', or a list of languages
|
ensure_installed = 'all' -- one of 'all', 'language', or a list of languages
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
|
|
||||||
local install = require'nvim-treesitter.install'
|
local install = require'nvim-treesitter.install'
|
||||||
local locals = require'nvim-treesitter.locals'
|
|
||||||
local utils = require'nvim-treesitter.utils'
|
local utils = require'nvim-treesitter.utils'
|
||||||
local info = require'nvim-treesitter.info'
|
local info = require'nvim-treesitter.info'
|
||||||
local configs = require'nvim-treesitter.configs'
|
local configs = require'nvim-treesitter.configs'
|
||||||
|
local state = require'nvim-treesitter.state'
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
-- This function sets up everythin needed for a given language
|
function M.setup()
|
||||||
-- this is the main interface through the plugin
|
|
||||||
function M.setup(lang)
|
|
||||||
utils.setup_commands('install', install.commands)
|
utils.setup_commands('install', install.commands)
|
||||||
utils.setup_commands('info', info.commands)
|
utils.setup_commands('info', info.commands)
|
||||||
utils.setup_commands('configs', configs.commands)
|
utils.setup_commands('configs', configs.commands)
|
||||||
|
|
@ -18,33 +17,34 @@ function M.setup(lang)
|
||||||
for _, mod in pairs(configs.available_modules()) do
|
for _, mod in pairs(configs.available_modules()) do
|
||||||
if configs.is_enabled(mod, ft) then
|
if configs.is_enabled(mod, ft) then
|
||||||
local cmd = string.format("lua require'nvim-treesitter.%s'.attach()", mod)
|
local cmd = string.format("lua require'nvim-treesitter.%s'.attach()", mod)
|
||||||
api.nvim_command(string.format("autocmd FileType %s %s", ft, cmd))
|
api.nvim_command(string.format("autocmd NvimTreesitter FileType %s %s", ft, cmd))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local cmd = string.format("lua require'nvim-treesitter.state'.attach_to_buffer(%s)", ft)
|
||||||
|
api.nvim_command(string.format('autocmd NvimTreesitter FileType %s %s', ft, cmd))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
state.run_update()
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.statusline(indicator_size)
|
function M.statusline(indicator_size)
|
||||||
local indicator_size = indicator_size or 1000
|
local indicator_size = indicator_size or 100
|
||||||
local expr = require"nvim-treesitter.utils".expression_at_point()
|
local bufnr = api.nvim_get_current_buf()
|
||||||
local current_node =
|
local buf_state = state.get_buf_state(bufnr)
|
||||||
require'nvim-treesitter.node_movement'.current_node[api.nvim_get_current_buf()]
|
if not buf_state then return "" end
|
||||||
|
|
||||||
local indicator = ""
|
local current_node = buf_state.current_node
|
||||||
|
if not current_node then return "" end
|
||||||
|
|
||||||
|
local expr = current_node:parent()
|
||||||
|
local prefix = ""
|
||||||
|
if expr then
|
||||||
|
prefix = "->"
|
||||||
|
end
|
||||||
|
|
||||||
|
local indicator = current_node:type()
|
||||||
while expr and (#indicator + #(expr:type()) + 5) < indicator_size do
|
while expr and (#indicator + #(expr:type()) + 5) < indicator_size do
|
||||||
|
indicator = expr:type() .. prefix .. indicator
|
||||||
local prefix = ""
|
|
||||||
if expr:parent() then
|
|
||||||
prefix = "->"
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
if expr == current_node then
|
|
||||||
indicator = string.format("%s[%s]%s", prefix, expr:type(), indicator)
|
|
||||||
else
|
|
||||||
indicator = prefix .. expr:type() .. indicator
|
|
||||||
end
|
|
||||||
|
|
||||||
expr = expr:parent()
|
expr = expr:parent()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
|
|
||||||
local queries = require'nvim-treesitter.query'
|
local queries = require'nvim-treesitter.query'
|
||||||
local parser_utils = require'nvim-treesitter.parsers'
|
local utils = require'nvim-treesitter.utils'
|
||||||
|
|
||||||
local parsers = {}
|
local parsers = {}
|
||||||
|
|
||||||
parsers.javascript = {
|
parsers.javascript = {
|
||||||
|
|
@ -218,28 +220,28 @@ local config = {
|
||||||
enable = false,
|
enable = false,
|
||||||
disable = {},
|
disable = {},
|
||||||
keymaps = {
|
keymaps = {
|
||||||
|
init_selection="gnn",
|
||||||
node_incremental="grn",
|
node_incremental="grn",
|
||||||
scope_incremental="grc"
|
scope_incremental="grc",
|
||||||
|
node_decremental="grm"
|
||||||
},
|
},
|
||||||
is_supported = function() return true end
|
is_supported = function(ft)
|
||||||
|
return queries.get_query(ft, 'locals')
|
||||||
|
end
|
||||||
},
|
},
|
||||||
node_movement = {
|
node_movement = {
|
||||||
enable = false,
|
enable = false,
|
||||||
disable = {},
|
disable = {},
|
||||||
is_supported = function() return true end,
|
is_supported = function(ft)
|
||||||
|
return queries.get_query(ft, 'locals')
|
||||||
|
end,
|
||||||
keymaps = {
|
keymaps = {
|
||||||
move_up = "<a-k>",
|
parent_scope = "<a-k>",
|
||||||
move_down = "<a-j>",
|
child_scope = "<a-j>",
|
||||||
move_left = "<a-h>",
|
next_scope = "<a-l>",
|
||||||
move_right = "<a-l>",
|
previous_scope = "<a-h>",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
-- folding = {
|
|
||||||
-- enable = false,
|
|
||||||
-- disable = {},
|
|
||||||
-- keymaps = {},
|
|
||||||
-- is_supported = function() return false end
|
|
||||||
-- }
|
|
||||||
},
|
},
|
||||||
ensure_installed = nil
|
ensure_installed = nil
|
||||||
}
|
}
|
||||||
|
|
@ -279,12 +281,12 @@ local function enable_all(mod, ft)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if ft then
|
if ft then
|
||||||
if parser_utils.has_parser(ft) then
|
if utils.has_parser(ft) then
|
||||||
enable_mod_conf_autocmd(mod, ft)
|
enable_mod_conf_autocmd(mod, ft)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
for _, ft in pairs(M.available_parsers()) do
|
for _, ft in pairs(M.available_parsers()) do
|
||||||
if parser_utils.has_parser(ft) then
|
if utils.has_parser(ft) then
|
||||||
enable_mod_conf_autocmd(mod, ft)
|
enable_mod_conf_autocmd(mod, ft)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -364,7 +366,7 @@ M.commands = {
|
||||||
-- @param mod: module (string)
|
-- @param mod: module (string)
|
||||||
-- @param ft: filetype (string)
|
-- @param ft: filetype (string)
|
||||||
function M.is_enabled(mod, ft)
|
function M.is_enabled(mod, ft)
|
||||||
if not M.get_parser_configs()[ft] or not parser_utils.has_parser(ft) then
|
if not M.get_parser_configs()[ft] or not utils.has_parser(ft) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -378,6 +380,7 @@ function M.is_enabled(mod, ft)
|
||||||
for _, parser in pairs(module_config.disable) do
|
for _, parser in pairs(module_config.disable) do
|
||||||
if ft == parser then return false end
|
if ft == parser then return false end
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -393,11 +396,15 @@ function M.setup(user_data)
|
||||||
config.modules[mod].disable = data.disable
|
config.modules[mod].disable = data.disable
|
||||||
end
|
end
|
||||||
if config.modules[mod].keymaps and type(data.keymaps) == 'table' then
|
if config.modules[mod].keymaps and type(data.keymaps) == 'table' then
|
||||||
config.modules[mod].keymaps = data.keymaps
|
for f, map in pairs(data.keymaps) do
|
||||||
|
if config.modules[mod].keymaps[f] then
|
||||||
|
config.modules[mod].keymaps[f] = map
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
elseif mod == 'ensure_installed' then
|
elseif mod == 'ensure_installed' then
|
||||||
config.ensure_installed = data
|
config.ensure_installed = data
|
||||||
require'nvim-treesitter/install'.ensure_installed(data)
|
require'nvim-treesitter.install'.ensure_installed(data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,6 @@ function locals_health(lang)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- TODO(vigoux): Maybe we should move each check to be perform in its own module
|
|
||||||
function M.checkhealth()
|
function M.checkhealth()
|
||||||
-- Installation dependency checks
|
-- Installation dependency checks
|
||||||
health_start('Installation')
|
health_start('Installation')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
local ts = vim.treesitter
|
local ts = vim.treesitter
|
||||||
|
|
||||||
local queries = require'nvim-treesitter.query'
|
local queries = require'nvim-treesitter.query'
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,102 @@
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
local utils = require'nvim-treesitter.utils'
|
|
||||||
local parsers = require'nvim-treesitter.parsers'
|
local state = require'nvim-treesitter.state'
|
||||||
|
local configs = require'nvim-treesitter.configs'
|
||||||
|
local ts_utils = require'nvim-treesitter.ts_utils'
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function node_range_to_vim(node)
|
local function update_selection(buf, node)
|
||||||
if not node then return end
|
|
||||||
|
|
||||||
local start_row, start_col, end_row, end_col = node:range()
|
local start_row, start_col, end_row, end_col = node:range()
|
||||||
|
|
||||||
local select_range = [[
|
if end_row == vim.fn.line('$') then
|
||||||
call cursor(%d, %d)
|
end_col = #vim.fn.getline('$')
|
||||||
normal v
|
end
|
||||||
call cursor(%d, %d)
|
|
||||||
]]
|
|
||||||
local exec_command = string.format(select_range,
|
|
||||||
start_row+1, start_col+1,
|
|
||||||
end_row+1, end_col+1)
|
|
||||||
|
|
||||||
api.nvim_exec(exec_command, false)
|
vim.fn.setpos(".", { buf, start_row+1, start_col+1, 0 })
|
||||||
|
vim.fn.nvim_exec("normal v", false)
|
||||||
|
vim.fn.setpos(".", { buf, end_row+1, end_col+1, 0 })
|
||||||
end
|
end
|
||||||
|
|
||||||
local function select_incremental(increment_func)
|
local function select_incremental(get_parent)
|
||||||
return function()
|
return function()
|
||||||
local buf, sel_start_line, sel_start_col, _ = unpack(vim.fn.getpos("'<"))
|
local buf = api.nvim_get_current_buf()
|
||||||
local buf, sel_end_line, sel_end_col, _ = unpack(vim.fn.getpos("'>"))
|
local buf_state = state.get_buf_state(buf)
|
||||||
|
|
||||||
local node = nil
|
local node
|
||||||
if parsers.has_parser() then
|
-- initialize incremental selection with current range
|
||||||
local root = parsers.get_parser():parse():root()
|
if #buf_state.selection.nodes == 0 then
|
||||||
node = root:named_descendant_for_range(sel_start_line-1, sel_start_col-1, sel_end_line-1, sel_end_col)
|
local cur_range = buf_state.selection.range
|
||||||
local node_start_row, node_start_col, node_end_row, node_end_col = node:range()
|
if not cur_range then
|
||||||
|
local _, cursor_row, cursor_col, _ = unpack(vim.fn.getpos("."))
|
||||||
if (sel_start_line-1) == node_start_row and (sel_start_col-1) == node_start_col
|
cur_range = { cursor_row, cursor_col, cursor_row, cursor_col + 1 }
|
||||||
and (sel_end_line-1) == node_end_row and sel_end_col == node_end_col then
|
|
||||||
node = increment_func(node)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local root = buf_state.parser.tree:root()
|
||||||
|
if not root then return end
|
||||||
|
|
||||||
|
node = root:named_descendant_for_range(cur_range[1]-1, cur_range[2]-1, cur_range[3]-1, cur_range[4]-1)
|
||||||
|
else
|
||||||
|
node = get_parent(buf_state.selection.nodes[#buf_state.selection.nodes])
|
||||||
end
|
end
|
||||||
|
|
||||||
return node_range_to_vim(node)
|
if not node then return end
|
||||||
|
|
||||||
|
if node ~= buf_state.selection.nodes[#buf_state.selection.nodes] then
|
||||||
|
state.insert_selection_node(buf, node)
|
||||||
|
end
|
||||||
|
|
||||||
|
update_selection(buf, node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.node_incremental = select_incremental(function(node)
|
M.node_incremental = select_incremental(function(node)
|
||||||
if node then
|
return node:parent() or node
|
||||||
return node:parent() or node
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
M.scope_incremental = select_incremental(function(node)
|
M.scope_incremental = select_incremental(function(node)
|
||||||
if node then
|
return ts_utils.containing_scope(node:parent() or node)
|
||||||
return utils.smallest_containing_scope(node:parent() or node)
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
function M.node_decremental()
|
||||||
|
local buf = api.nvim_get_current_buf()
|
||||||
|
local buf_state = state.get_buf_state(buf)
|
||||||
|
|
||||||
|
local nodes = buf_state.selection.nodes
|
||||||
|
if #nodes < 2 then return end
|
||||||
|
|
||||||
|
state.pop_selection_node(buf)
|
||||||
|
|
||||||
|
local node = nodes[#nodes]
|
||||||
|
update_selection(buf, node)
|
||||||
|
end
|
||||||
|
|
||||||
function M.attach(bufnr)
|
function M.attach(bufnr)
|
||||||
local buf = bufnr or api.nvim_get_current_buf()
|
local buf = bufnr or api.nvim_get_current_buf()
|
||||||
|
|
||||||
local config = require'nvim-treesitter.configs'.get_module('incremental_selection')
|
local config = configs.get_module('incremental_selection')
|
||||||
for funcname, mapping in pairs(config.keymaps) do
|
for funcname, mapping in pairs(config.keymaps) do
|
||||||
api.nvim_buf_set_keymap(buf, 'v', mapping,
|
|
||||||
string.format(":lua require'nvim-treesitter.incremental_selection'.%s()<CR>", funcname), { silent = true })
|
if funcname == "init_selection" then
|
||||||
api.nvim_buf_set_keymap(buf, 'o', mapping,
|
local cmd = ":lua require'nvim-treesitter.incremental_selection'.node_incremental()<CR>"
|
||||||
string.format(":normal v%s<CR>", mapping), { silent = true })
|
api.nvim_buf_set_keymap(buf, 'n', mapping, cmd, { silent = true })
|
||||||
|
else
|
||||||
|
local cmd = string.format(":lua require'nvim-treesitter.incremental_selection'.%s()<CR>", funcname)
|
||||||
|
api.nvim_buf_set_keymap(buf, 'v', mapping, cmd, { silent = true })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.detach(bufnr)
|
function M.detach(bufnr)
|
||||||
local buf = bufnr or api.nvim_get_current_buf()
|
local buf = bufnr or api.nvim_get_current_buf()
|
||||||
|
|
||||||
local config = require'nvim-treesitter.configs'.get_module('incremental_selection')
|
local config = configs.get_module('incremental_selection')
|
||||||
for _, mapping in pairs(config.keymaps) do
|
for f, mapping in pairs(config.keymaps) do
|
||||||
api.nvim_buf_del_keymap(buf, 'v', mapping)
|
if f == "init_selection" then
|
||||||
api.nvim_buf_del_keymap(buf, 'o', mapping)
|
api.nvim_buf_del_keymap(buf, 'n', mapping)
|
||||||
|
else
|
||||||
|
api.nvim_buf_del_keymap(buf, 'v', mapping)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,12 @@
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
local fn = vim.fn
|
local fn = vim.fn
|
||||||
local luv = vim.loop
|
local luv = vim.loop
|
||||||
local configs = require'nvim-treesitter/configs'
|
|
||||||
local parsers = configs.get_parser_configs()
|
local configs = require'nvim-treesitter.configs'
|
||||||
local has_parser = require'nvim-treesitter/parsers'.has_parser
|
local utils = require'nvim-treesitter.utils'
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function get_package_path()
|
|
||||||
for _, path in pairs(api.nvim_list_runtime_paths()) do
|
|
||||||
if string.match(path, '.*/nvim%-treesitter') then
|
|
||||||
return path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil, 'Plugin runtime path not found.'
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_cache_dir()
|
|
||||||
local home = fn.get(fn.environ(), 'HOME')
|
|
||||||
local xdg_cache = fn.get(fn.environ(), 'XDG_CACHE_HOME')
|
|
||||||
|
|
||||||
if xdg_cache == 0 then
|
|
||||||
xdg_cache = home .. '/.cache'
|
|
||||||
end
|
|
||||||
|
|
||||||
if luv.fs_access(xdg_cache, 'RW') then
|
|
||||||
return xdg_cache
|
|
||||||
elseif luv.fs_access('/tmp', 'RW') then
|
|
||||||
return '/tmp'
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil, 'Invalid cache rights, $XDG_CACHE_HOME or /tmp should be read/write'
|
|
||||||
end
|
|
||||||
|
|
||||||
local function iter_cmd(cmd_list, i, ft)
|
local function iter_cmd(cmd_list, i, ft)
|
||||||
if i == #cmd_list + 1 then return print('Treesitter parser for '..ft..' has been installed') end
|
if i == #cmd_list + 1 then return print('Treesitter parser for '..ft..' has been installed') end
|
||||||
|
|
||||||
|
|
@ -122,7 +95,7 @@ local function install(ft)
|
||||||
if not string.match(yesno, '^y.*') then return end
|
if not string.match(yesno, '^y.*') then return end
|
||||||
end
|
end
|
||||||
|
|
||||||
local parser_config = parsers[ft]
|
local parser_config = configs.get_parser_configs()[ft]
|
||||||
if not parser_config then
|
if not parser_config then
|
||||||
return api.nvim_err_writeln('Parser not available for language '..ft)
|
return api.nvim_err_writeln('Parser not available for language '..ft)
|
||||||
end
|
end
|
||||||
|
|
@ -137,10 +110,10 @@ local function install(ft)
|
||||||
return api.nvim_err_writeln('Git is required on your system to run this command')
|
return api.nvim_err_writeln('Git is required on your system to run this command')
|
||||||
end
|
end
|
||||||
|
|
||||||
local package_path, err = get_package_path()
|
local package_path, err = utils.get_package_path()
|
||||||
if err then return api.nvim_err_writeln(err) end
|
if err then return api.nvim_err_writeln(err) end
|
||||||
|
|
||||||
local cache_folder, err = get_cache_dir()
|
local cache_folder, err = utils.get_cache_dir()
|
||||||
if err then return api.nvim_err_writeln(err) end
|
if err then return api.nvim_err_writeln(err) end
|
||||||
|
|
||||||
run_install(cache_folder, package_path, ft, install_info)
|
run_install(cache_folder, package_path, ft, install_info)
|
||||||
|
|
@ -157,7 +130,7 @@ M.ensure_installed = function(languages)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, ft in ipairs(languages) do
|
for _, ft in ipairs(languages) do
|
||||||
if not has_parser(ft) then
|
if not utils.has_parser(ft) then
|
||||||
install(ft)
|
install(ft)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@
|
||||||
-- its the way nvim-treesitter uses to "understand" the code
|
-- its the way nvim-treesitter uses to "understand" the code
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
local ts = vim.treesitter
|
local ts = vim.treesitter
|
||||||
|
|
||||||
local queries = require'nvim-treesitter.query'
|
local queries = require'nvim-treesitter.query'
|
||||||
local parsers = require'nvim-treesitter.parsers'
|
local utils = require'nvim-treesitter.utils'
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
locals={}
|
locals = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function M.collect_locals(bufnr)
|
function M.collect_locals(bufnr)
|
||||||
|
|
@ -17,7 +18,7 @@ function M.collect_locals(bufnr)
|
||||||
local query = queries.get_query(ft, 'locals')
|
local query = queries.get_query(ft, 'locals')
|
||||||
if not query then return end
|
if not query then return end
|
||||||
|
|
||||||
local parser = parsers.get_parser(bufnr, ft)
|
local parser = utils.get_parser(bufnr, ft)
|
||||||
if not parser then return end
|
if not parser then return end
|
||||||
|
|
||||||
local root = parser:parse():root()
|
local root = parser:parse():root()
|
||||||
|
|
|
||||||
|
|
@ -1,88 +1,69 @@
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
local parsers = require'nvim-treesitter.parsers'
|
|
||||||
local utils = require'nvim-treesitter.utils'
|
local configs = require'nvim-treesitter.configs'
|
||||||
|
local state = require'nvim-treesitter.state'
|
||||||
|
local ts_utils = require'nvim-treesitter.ts_utils'
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
local NodeMovementKind = {
|
||||||
M.NodeMovementKind = {
|
parent_scope = 'parent',
|
||||||
up = 'up',
|
child_scope = 'child',
|
||||||
down = 'down',
|
next_scope = 'next',
|
||||||
left = 'left',
|
previous_scope = 'previous',
|
||||||
right = 'right',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.current_node = {}
|
local get_node_fn = {
|
||||||
|
[NodeMovementKind.parent_scope] = function(node, curpos)
|
||||||
local function node_start_to_vim(node)
|
return ts_utils.parent_scope(node, curpos)
|
||||||
if not node then return end
|
end,
|
||||||
|
[NodeMovementKind.child_scope] = function(node, curpos)
|
||||||
local row, col = node:start()
|
return ts_utils.nested_scope(node, curpos)
|
||||||
local exec_command = string.format('call cursor(%d, %d)', row+1, col+1)
|
end,
|
||||||
api.nvim_exec(exec_command, false)
|
[NodeMovementKind.next_scope] = function(node)
|
||||||
end
|
return ts_utils.next_scope(node)
|
||||||
|
end,
|
||||||
|
[NodeMovementKind.previous_scope] = function(node)
|
||||||
|
return ts_utils.previous_scope(node)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
M.do_node_movement = function(kind)
|
M.do_node_movement = function(kind)
|
||||||
local buf, line, col = unpack(vim.fn.getpos("."))
|
local buf = api.nvim_get_current_buf()
|
||||||
|
|
||||||
local current_node = M.current_node[buf]
|
local buf_state = state.get_buf_state(buf)
|
||||||
|
if not buf_state then return end
|
||||||
|
|
||||||
if current_node then
|
local current_node = buf_state.current_node
|
||||||
local node_line, node_col = current_node:start()
|
if not current_node then return end
|
||||||
if line-1 ~= node_line or col-1 ~= node_col then
|
|
||||||
current_node = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local destination_node
|
|
||||||
|
|
||||||
if parsers.has_parser() then
|
local destination_node = get_node_fn[kind](current_node, buf_state.cursor_pos)
|
||||||
local root = parsers.get_parser():parse():root()
|
|
||||||
if not current_node then
|
|
||||||
current_node = root:named_descendant_for_range(line-1, col-1, line-1, col)
|
|
||||||
end
|
|
||||||
|
|
||||||
if kind == M.NodeMovementKind.up then
|
|
||||||
destination_node = current_node:parent()
|
|
||||||
elseif kind == M.NodeMovementKind.down then
|
|
||||||
if current_node:named_child_count() > 0 then
|
|
||||||
destination_node = current_node:named_child(0)
|
|
||||||
else
|
|
||||||
local next_node = utils.get_next_node(current_node)
|
|
||||||
if next_node and next_node:named_child_count() > 0 then
|
|
||||||
destination_node = next_node:named_child(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif kind == M.NodeMovementKind.left then
|
|
||||||
destination_node = utils.get_previous_node(current_node, true, true)
|
|
||||||
elseif kind == M.NodeMovementKind.right then
|
|
||||||
destination_node = utils.get_next_node(current_node, true, true)
|
|
||||||
end
|
|
||||||
M.current_node[buf] = destination_node or current_node
|
|
||||||
end
|
|
||||||
|
|
||||||
if destination_node then
|
if destination_node then
|
||||||
node_start_to_vim(destination_node)
|
local row, col = destination_node:start()
|
||||||
|
vim.fn.setpos(".", { buf, row+1, col+1, 0 })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.move_up = function() M.do_node_movement(M.NodeMovementKind.up) end
|
function M.parent_scope() M.do_node_movement(NodeMovementKind.parent_scope) end
|
||||||
M.move_down = function() M.do_node_movement(M.NodeMovementKind.down) end
|
function M.child_scope() M.do_node_movement(NodeMovementKind.child_scope) end
|
||||||
M.move_left = function() M.do_node_movement(M.NodeMovementKind.left) end
|
function M.next_scope() M.do_node_movement(NodeMovementKind.next_scope) end
|
||||||
M.move_right = function() M.do_node_movement(M.NodeMovementKind.right) end
|
function M.previous_scope() M.do_node_movement(NodeMovementKind.previous_scope) end
|
||||||
|
|
||||||
function M.attach(bufnr)
|
function M.attach(bufnr)
|
||||||
local buf = bufnr or api.nvim_get_current_buf()
|
local bufnr = bufnr or api.nvim_get_current_buf()
|
||||||
|
|
||||||
local config = require'nvim-treesitter.configs'.get_module('node_movement')
|
local config = configs.get_module('node_movement')
|
||||||
for funcname, mapping in pairs(config.keymaps) do
|
for funcname, mapping in pairs(config.keymaps) do
|
||||||
api.nvim_buf_set_keymap(buf, 'n', mapping,
|
local cmd = string.format(":lua require'nvim-treesitter.node_movement'.%s()<CR>", funcname)
|
||||||
string.format(":lua require'nvim-treesitter.node_movement'.%s()<CR>", funcname), { silent = true })
|
api.nvim_buf_set_keymap(bufnr, 'n', mapping, cmd, { silent = true })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.detach(bufnr)
|
function M.detach(bufnr)
|
||||||
local buf = bufnr or api.nvim_get_current_buf()
|
local buf = bufnr or api.nvim_get_current_buf()
|
||||||
|
|
||||||
local config = require'nvim-treesitter.configs'.get_module('node_movement')
|
local config = configs.get_module('node_movement')
|
||||||
for _, mapping in pairs(config.keymaps) do
|
for _, mapping in pairs(config.keymaps) do
|
||||||
api.nvim_buf_del_keymap(buf, 'n', mapping)
|
api.nvim_buf_del_keymap(buf, 'n', mapping)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
-- Treesitter utils
|
|
||||||
|
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
local ts = vim.treesitter
|
local ts = vim.treesitter
|
||||||
|
|
||||||
|
|
|
||||||
114
lua/nvim-treesitter/state.lua
Normal file
114
lua/nvim-treesitter/state.lua
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
local api = vim.api
|
||||||
|
|
||||||
|
local utils = require'nvim-treesitter.utils'
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local buffers = {}
|
||||||
|
|
||||||
|
local g_mode = api.nvim_get_mode().mode
|
||||||
|
|
||||||
|
local function get_selection_range()
|
||||||
|
local _, vstart_row, vstart_col, _ = unpack(vim.fn.getpos("v"))
|
||||||
|
local _, cursor_row, cursor_col, _ = unpack(vim.fn.getpos("."))
|
||||||
|
if vstart_row < cursor_row then
|
||||||
|
return vstart_row, vstart_col, cursor_row, cursor_col
|
||||||
|
else
|
||||||
|
return cursor_row, cursor_col, vstart_row, vstart_col
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.update()
|
||||||
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
local buf_config = buffers[bufnr]
|
||||||
|
if not buf_config then return end
|
||||||
|
|
||||||
|
local mode = api.nvim_get_mode().mode
|
||||||
|
local cursor = api.nvim_win_get_cursor(0)
|
||||||
|
local row = cursor[1]
|
||||||
|
local col = cursor[2]
|
||||||
|
if row == buf_config.cursor_pos.row
|
||||||
|
and col == buf_config.cursor_pos.col
|
||||||
|
and mode == g_mode
|
||||||
|
then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local root = buf_config.parser.tree:root()
|
||||||
|
if not root then return end
|
||||||
|
|
||||||
|
local new_node = root:named_descendant_for_range(row - 1, col, row - 1, col)
|
||||||
|
|
||||||
|
if new_node ~= buf_config.current_node then
|
||||||
|
buf_config.current_node = new_node
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We only want to update the range when the incremental selection has not started yet
|
||||||
|
if mode == "v" and #buf_config.selection.nodes == 0 then
|
||||||
|
local row_start, col_start, row_end, col_end = get_selection_range()
|
||||||
|
buf_config.selection.range = { row_start, col_start, row_end, col_end }
|
||||||
|
elseif mode ~= "v" then
|
||||||
|
buf_config.selection.nodes = {}
|
||||||
|
buf_config.selection.range = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
g_mode = mode
|
||||||
|
buf_config.cursor_pos.row = row
|
||||||
|
buf_config.cursor_pos.col = col
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.insert_selection_node(bufnr, range)
|
||||||
|
local buf_config = buffers[bufnr]
|
||||||
|
if not buf_config then return end
|
||||||
|
|
||||||
|
table.insert(buffers[bufnr].selection.nodes, range)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.pop_selection_node(bufnr)
|
||||||
|
local buf_config = buffers[bufnr]
|
||||||
|
if not buf_config then return end
|
||||||
|
|
||||||
|
table.remove(
|
||||||
|
buffers[bufnr].selection.nodes,
|
||||||
|
#buffers[bufnr].selection.nodes
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.run_update()
|
||||||
|
local cmd = "lua require'nvim-treesitter.state'.update()"
|
||||||
|
api.nvim_command('autocmd NvimTreesitter CursorMoved * '..cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.attach_to_buffer(ft)
|
||||||
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
local ft = ft or api.nvim_buf_get_option(bufnr, 'ft')
|
||||||
|
|
||||||
|
if buffers[bufnr] then return end
|
||||||
|
|
||||||
|
local parser = utils.get_parser(bufnr, ft)
|
||||||
|
if not parser then return end
|
||||||
|
|
||||||
|
buffers[bufnr] = {
|
||||||
|
cursor_pos = {},
|
||||||
|
current_node = nil,
|
||||||
|
selection = {
|
||||||
|
range = nil,
|
||||||
|
nodes = {}
|
||||||
|
},
|
||||||
|
parser = parser,
|
||||||
|
}
|
||||||
|
|
||||||
|
M.update()
|
||||||
|
api.nvim_buf_attach(bufnr, false, {
|
||||||
|
-- TODO(kyazdani): on lines should only parse the changed content
|
||||||
|
-- TODO(kyazdani): add a timer to avoid too frequent updates
|
||||||
|
on_lines = function(_, buf) buffers[buf].parser:parse() end,
|
||||||
|
on_detach = function(bufnr) buffers[bufnr] = nil end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.get_buf_state(bufnr)
|
||||||
|
return buffers[bufnr]
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
208
lua/nvim-treesitter/ts_utils.lua
Normal file
208
lua/nvim-treesitter/ts_utils.lua
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
local api = vim.api
|
||||||
|
|
||||||
|
local locals = require'nvim-treesitter.locals'
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Gets the actual text content of a node
|
||||||
|
-- @param node the node to get the text from
|
||||||
|
-- @param bufnr the buffer containing the node
|
||||||
|
-- @return list of lines of text of the node
|
||||||
|
function M.get_node_text(node, bufnr)
|
||||||
|
local bufnr = bufnr or api.nvim_get_current_buf()
|
||||||
|
if not node then return {} end
|
||||||
|
|
||||||
|
-- We have to remember that end_col is end-exclusive
|
||||||
|
local start_row, start_col, end_row, end_col = node:range()
|
||||||
|
if start_row ~= end_row then
|
||||||
|
local lines = api.nvim_buf_get_lines(bufnr, start_row, end_row+1, false)
|
||||||
|
lines[1] = string.sub(lines[1], start_col+1)
|
||||||
|
lines[#lines] = string.sub(lines[#lines], 1, end_col)
|
||||||
|
return lines
|
||||||
|
else
|
||||||
|
local line = api.nvim_buf_get_lines(bufnr, start_row, start_row+1, true)[1]
|
||||||
|
return { string.sub(line, start_col+1, end_col) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Determines wether a node is the parent of another
|
||||||
|
-- @param dest the possible parent
|
||||||
|
-- @param source the possible child node
|
||||||
|
function M.is_parent(dest, source)
|
||||||
|
if not (dest and source) then return false end
|
||||||
|
|
||||||
|
local current = source
|
||||||
|
while current ~= nil do
|
||||||
|
if current == dest then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
current = current:parent()
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get next node with same parent
|
||||||
|
-- @param node node
|
||||||
|
-- @param allow_switch_parents allow switching parents if last node
|
||||||
|
-- @param allow_next_parent allow next parent if last node and next parent without children
|
||||||
|
function M.get_next_node(node, allow_switch_parents, allow_next_parent)
|
||||||
|
local destination_node
|
||||||
|
local parent = node:parent()
|
||||||
|
|
||||||
|
if not parent then return end
|
||||||
|
local found_pos = 0
|
||||||
|
for i = 0,parent:named_child_count()-1,1 do
|
||||||
|
if parent:named_child(i) == node then
|
||||||
|
found_pos = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if parent:named_child_count() > found_pos + 1 then
|
||||||
|
destination_node = parent:named_child(found_pos + 1)
|
||||||
|
elseif allow_switch_parents then
|
||||||
|
local next_node = M.get_next_node(node:parent())
|
||||||
|
if next_node and next_node:named_child_count() > 0 then
|
||||||
|
destination_node = next_node:named_child(0)
|
||||||
|
elseif next_node and allow_next_parent then
|
||||||
|
destination_node = next_node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return destination_node
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get previous node with same parent
|
||||||
|
-- @param node node
|
||||||
|
-- @param allow_switch_parents allow switching parents if first node
|
||||||
|
-- @param allow_previous_parent allow previous parent if first node and previous parent without children
|
||||||
|
function M.get_previous_node(node, allow_switch_parents, allow_previous_parent)
|
||||||
|
local destination_node
|
||||||
|
local parent = node:parent()
|
||||||
|
if not parent then return end
|
||||||
|
|
||||||
|
local found_pos = 0
|
||||||
|
for i = 0,parent:named_child_count()-1,1 do
|
||||||
|
if parent:named_child(i) == node then
|
||||||
|
found_pos = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if 0 < found_pos then
|
||||||
|
destination_node = parent:named_child(found_pos - 1)
|
||||||
|
elseif allow_switch_parents then
|
||||||
|
local previous_node = M.get_previous_node(node:parent())
|
||||||
|
if previous_node and previous_node:named_child_count() > 0 then
|
||||||
|
destination_node = previous_node:named_child(previous_node:named_child_count() - 1)
|
||||||
|
elseif previous_node and allow_previous_parent then
|
||||||
|
destination_node = previous_node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return destination_node
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.parent_scope(node, cursor_pos)
|
||||||
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
local scopes = locals.get_scopes(bufnr)
|
||||||
|
if not node or not scopes then return end
|
||||||
|
|
||||||
|
local row = cursor_pos.row
|
||||||
|
local col = cursor_pos.col
|
||||||
|
local iter_node = node
|
||||||
|
|
||||||
|
while iter_node ~= nil do
|
||||||
|
local row_, col_ = iter_node:start()
|
||||||
|
if vim.tbl_contains(scopes, iter_node) and (row_+1 ~= row or col_ ~= col) then
|
||||||
|
return iter_node
|
||||||
|
end
|
||||||
|
iter_node = iter_node:parent()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.containing_scope(node)
|
||||||
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
local scopes = locals.get_scopes(bufnr)
|
||||||
|
if not node or not scopes then return end
|
||||||
|
|
||||||
|
local iter_node = node
|
||||||
|
|
||||||
|
while iter_node ~= nil and not vim.tbl_contains(scopes, iter_node) do
|
||||||
|
iter_node = iter_node:parent()
|
||||||
|
end
|
||||||
|
|
||||||
|
return iter_node or node
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.get_named_children(node)
|
||||||
|
local nodes = {}
|
||||||
|
for i=0,node:named_child_count() - 1,1 do
|
||||||
|
nodes[i+1] = node:named_child(i)
|
||||||
|
end
|
||||||
|
return nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.nested_scope(node, cursor_pos)
|
||||||
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
local scopes = locals.get_scopes(bufnr)
|
||||||
|
if not node or not scopes then return end
|
||||||
|
|
||||||
|
local row = cursor_pos.row
|
||||||
|
local col = cursor_pos.col
|
||||||
|
local scope = M.containing_scope(node)
|
||||||
|
|
||||||
|
for _, child in ipairs(M.get_named_children(scope)) do
|
||||||
|
local row_, col_ = child:start()
|
||||||
|
if vim.tbl_contains(scopes, child) and ((row_+1 == row and col_ > col) or row_+1 > row) then
|
||||||
|
return child
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.next_scope(node)
|
||||||
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
local scopes = locals.get_scopes(bufnr)
|
||||||
|
if not node or not scopes then return end
|
||||||
|
|
||||||
|
local scope = M.containing_scope(node)
|
||||||
|
|
||||||
|
local parent = scope:parent()
|
||||||
|
if not parent then return end
|
||||||
|
|
||||||
|
local is_prev = true
|
||||||
|
for _, child in ipairs(M.get_named_children(parent)) do
|
||||||
|
if child == scope then
|
||||||
|
is_prev = false
|
||||||
|
elseif not is_prev and vim.tbl_contains(scopes, child) then
|
||||||
|
return child
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.previous_scope(node)
|
||||||
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
local scopes = locals.get_scopes(bufnr)
|
||||||
|
if not node or not scopes then return end
|
||||||
|
|
||||||
|
local scope = M.containing_scope(node)
|
||||||
|
|
||||||
|
local parent = scope:parent()
|
||||||
|
if not parent then return end
|
||||||
|
|
||||||
|
local is_prev = true
|
||||||
|
local children = M.get_named_children(parent)
|
||||||
|
for i=#children,1,-1 do
|
||||||
|
if children[i] == scope then
|
||||||
|
is_prev = false
|
||||||
|
elseif not is_prev and vim.tbl_contains(scopes, children[i]) then
|
||||||
|
return children[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
@ -1,69 +1,10 @@
|
||||||
-- Utils collection for nvim-treesitter
|
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
local parsers = require'nvim-treesitter.parsers'
|
local fn = vim.fn
|
||||||
local locals = require'nvim-treesitter.locals'
|
local luv = vim.loop
|
||||||
|
local ts = vim.treesitter
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
--- Gets the smallest expression containing the current cursor position
|
|
||||||
function M.expression_at_point(bufnr, lang)
|
|
||||||
local bufnr = bufnr or api.nvim_get_current_buf()
|
|
||||||
local lang = lang or api.nvim_buf_get_option(bufnr, 'ft')
|
|
||||||
|
|
||||||
local parser = parsers.get_parser(bufnr, lang)
|
|
||||||
if not parser then return end
|
|
||||||
|
|
||||||
local tsroot = parser:parse():root()
|
|
||||||
if not tsroot then return end
|
|
||||||
|
|
||||||
local curwin = api.nvim_get_current_win()
|
|
||||||
|
|
||||||
if api.nvim_win_get_buf(curwin) == bufnr then
|
|
||||||
local cursor = vim.api.nvim_win_get_cursor(curwin)
|
|
||||||
local current_node = tsroot:named_descendant_for_range(cursor[1] - 1, cursor[2], cursor[1] - 1, cursor[2])
|
|
||||||
return current_node
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the actual text content of a node
|
|
||||||
-- @param node the node to get the text from
|
|
||||||
-- @param bufnr the buffer containing the node
|
|
||||||
-- @return list of lines of text of the node
|
|
||||||
function M.get_node_text(node, bufnr)
|
|
||||||
local bufnr = bufnr or api.nvim_get_current_buf()
|
|
||||||
if not node then return {} end
|
|
||||||
|
|
||||||
-- We have to remember that end_col is end-exclusive
|
|
||||||
local start_row, start_col, end_row, end_col = node:range()
|
|
||||||
if start_row ~= end_row then
|
|
||||||
local lines = api.nvim_buf_get_lines(bufnr, start_row, end_row+1, false)
|
|
||||||
lines[1] = string.sub(lines[1], start_col+1)
|
|
||||||
lines[#lines] = string.sub(lines[#lines], 1, end_col)
|
|
||||||
return lines
|
|
||||||
else
|
|
||||||
local line = api.nvim_buf_get_lines(bufnr, start_row, start_row+1, true)[1]
|
|
||||||
return { string.sub(line, start_col+1, end_col) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Determines wether a node is the parent of another
|
|
||||||
-- @param dest the possible parent
|
|
||||||
-- @param source the possible child node
|
|
||||||
function M.is_parent(dest, source)
|
|
||||||
if not (dest and source) then return false end
|
|
||||||
|
|
||||||
local current = source
|
|
||||||
while current ~= nil do
|
|
||||||
if current == dest then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
current = current:parent()
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.setup_commands(mod, commands)
|
function M.setup_commands(mod, commands)
|
||||||
for command_name, def in pairs(commands) do
|
for command_name, def in pairs(commands) do
|
||||||
local call_fn = string.format("lua require'nvim-treesitter.%s'.commands.%s.run(<f-args>)", mod, command_name)
|
local call_fn = string.format("lua require'nvim-treesitter.%s'.commands.%s.run(<f-args>)", mod, command_name)
|
||||||
|
|
@ -77,79 +18,50 @@ function M.setup_commands(mod, commands)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Gets the smallest scope which contains @param node
|
function M.get_package_path()
|
||||||
function M.smallest_containing_scope(node, bufnr)
|
for _, path in pairs(api.nvim_list_runtime_paths()) do
|
||||||
local bufnr = bufnr or api.nvim_get_current_buf()
|
if string.match(path, '.*/nvim%-treesitter') then
|
||||||
|
return path
|
||||||
local root = parsers.get_parser(bufnr):parse():root()
|
end
|
||||||
if not node then return root end
|
|
||||||
|
|
||||||
local scopes = locals.get_scopes(bufnr)
|
|
||||||
local current = node
|
|
||||||
while current ~= nil and not vim.tbl_contains(scopes, current) do
|
|
||||||
current = current:parent()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return current or root
|
return nil, 'Plugin runtime path not found.'
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get next node with same parent
|
function M.get_cache_dir()
|
||||||
-- @param node node
|
local home = fn.get(fn.environ(), 'HOME')
|
||||||
-- @param allow_switch_parents allow switching parents if last node
|
local xdg_cache = fn.get(fn.environ(), 'XDG_CACHE_HOME')
|
||||||
-- @param allow_next_parent allow next parent if last node and next parent without children
|
|
||||||
function M.get_next_node(node, allow_switch_parents, allow_next_parent)
|
|
||||||
local destination_node
|
|
||||||
local parent = node:parent()
|
|
||||||
|
|
||||||
if parent then
|
if xdg_cache == 0 then
|
||||||
local found_pos = 0
|
xdg_cache = home .. '/.cache'
|
||||||
for i = 0,parent:named_child_count()-1,1 do
|
|
||||||
if parent:named_child(i) == node then
|
|
||||||
found_pos = i
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if parent:named_child_count() > found_pos + 1 then
|
|
||||||
destination_node = parent:named_child(found_pos + 1)
|
|
||||||
elseif allow_switch_parents then
|
|
||||||
local next_node = M.get_next_node(node:parent())
|
|
||||||
if next_node and next_node:named_child_count() > 0 then
|
|
||||||
destination_node = next_node:named_child(0)
|
|
||||||
elseif next_node and allow_next_parent then
|
|
||||||
destination_node = next_node
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return destination_node
|
|
||||||
|
if luv.fs_access(xdg_cache, 'RW') then
|
||||||
|
return xdg_cache
|
||||||
|
elseif luv.fs_access('/tmp', 'RW') then
|
||||||
|
return '/tmp'
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, 'Invalid cache rights, $XDG_CACHE_HOME or /tmp should be read/write'
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get previous node with same parent
|
function M.has_parser(lang)
|
||||||
-- @param node node
|
local lang = lang or api.nvim_buf_get_option(0, 'filetype')
|
||||||
-- @param allow_switch_parents allow switching parents if first node
|
return #api.nvim_get_runtime_file('parser/' .. lang .. '.so', false) > 0
|
||||||
-- @param allow_previous_parent allow previous parent if first node and previous parent without children
|
end
|
||||||
function M.get_previous_node(node, allow_switch_parents, allow_previous_parent)
|
|
||||||
local destination_node
|
function M.get_parser(bufnr, lang)
|
||||||
local parent = node:parent()
|
if M.has_parser() then
|
||||||
if parent then
|
local buf = bufnr or api.nvim_get_current_buf()
|
||||||
local found_pos = 0
|
local lang = lang or api.nvim_buf_get_option(buf, 'ft')
|
||||||
for i = 0,parent:named_child_count()-1,1 do
|
if not M[buf] then
|
||||||
if parent:named_child(i) == node then
|
M[buf] = {}
|
||||||
found_pos = i
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if 0 < found_pos then
|
if not M[buf][lang] then
|
||||||
destination_node = parent:named_child(found_pos - 1)
|
M[buf][lang] = ts.get_parser(buf, lang)
|
||||||
elseif allow_switch_parents then
|
end
|
||||||
local previous_node = M.get_previous_node(node:parent())
|
return M[buf][lang]
|
||||||
if previous_node and previous_node:named_child_count() > 0 then
|
|
||||||
destination_node = previous_node:named_child(previous_node:named_child_count() - 1)
|
|
||||||
elseif previous_node and allow_previous_parent then
|
|
||||||
destination_node = previous_node
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return destination_node
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue