diff options
| author | kiyan42 <yazdani.kiyan@protonmail.com> | 2020-05-08 11:22:59 +0200 |
|---|---|---|
| committer | kiyan42 <yazdani.kiyan@protonmail.com> | 2020-05-12 16:16:48 +0200 |
| commit | 45dcebb15f1a954eba2bcb3ae4c1a03f710a1f2a (patch) | |
| tree | 50cbca8e9615d69a8ff9aca6b33a35abb32f9363 | |
| parent | Merge pull request #47 from theHamsta/fix-typo-contributing.md (diff) | |
| download | nvim-treesitter-45dcebb15f1a954eba2bcb3ae4c1a03f710a1f2a.tar nvim-treesitter-45dcebb15f1a954eba2bcb3ae4c1a03f710a1f2a.tar.gz nvim-treesitter-45dcebb15f1a954eba2bcb3ae4c1a03f710a1f2a.tar.bz2 nvim-treesitter-45dcebb15f1a954eba2bcb3ae4c1a03f710a1f2a.tar.lz nvim-treesitter-45dcebb15f1a954eba2bcb3ae4c1a03f710a1f2a.tar.xz nvim-treesitter-45dcebb15f1a954eba2bcb3ae4c1a03f710a1f2a.tar.zst nvim-treesitter-45dcebb15f1a954eba2bcb3ae4c1a03f710a1f2a.zip | |
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
| -rw-r--r-- | README.md | 26 | ||||
| -rw-r--r-- | doc/nvim-treesitter.txt | 22 | ||||
| -rw-r--r-- | lua/nvim-treesitter.lua | 44 | ||||
| -rw-r--r-- | lua/nvim-treesitter/configs.lua | 47 | ||||
| -rw-r--r-- | lua/nvim-treesitter/health.lua | 2 | ||||
| -rw-r--r-- | lua/nvim-treesitter/highlight.lua | 1 | ||||
| -rw-r--r-- | lua/nvim-treesitter/incremental_selection.lua | 109 | ||||
| -rw-r--r-- | lua/nvim-treesitter/install.lua | 41 | ||||
| -rw-r--r-- | lua/nvim-treesitter/locals.lua | 7 | ||||
| -rw-r--r-- | lua/nvim-treesitter/node_movement.lua | 101 | ||||
| -rw-r--r-- | lua/nvim-treesitter/query.lua | 2 | ||||
| -rw-r--r-- | lua/nvim-treesitter/state.lua | 114 | ||||
| -rw-r--r-- | lua/nvim-treesitter/ts_utils.lua | 208 | ||||
| -rw-r--r-- | lua/nvim-treesitter/utils.lua | 162 |
14 files changed, 554 insertions, 332 deletions
@@ -89,25 +89,27 @@ in your `init.vim`: lua <<EOF require'nvim-treesitter.configs'.setup { highlight = { - enable = true, -- false will disable the whole extension - disable = { 'c', 'rust' }, -- list of language that will be disabled + enable = true, -- false will disable the whole extension + disable = { 'c', 'rust' }, -- list of language that will be disabled }, - incremental_selection = { -- this enables incremental selection + incremental_selection = { enable = true, disable = { 'cpp', 'lua' }, keymaps = { -- mappings for incremental selection (visual mappings) - node_incremental = "<leader>e", -- "grn" by default, - scope_incremental = "<leader>f" -- "grc" by default + init_selection = 'gnn', -- maps in normal mode to init the node/scope selection + 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, disable = { 'cpp', 'rust' }, - keymaps = { -- mappings for node movement (normal mappings) - move_up = "<a-k>", -- default is to move with alt key hold - move_down = "<a-j>", - move_left = "<a-h>", - move_right = "<a-l>", + keymaps = { -- mappings for scope movement (normal mappings) + parent_scope = "<a-k>", -- default is to move with alt key hold + child_scope = "<a-j>", + next_scope = "<a-h>", + previous_scope = "<a-l>", } }, ensure_installed = 'all' -- one of 'all', 'language', or a list of languages @@ -134,7 +136,7 @@ Some of these features are : - [x] Incremental selection - [ ] Syntax based code folding - [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)`) You can find the roadmap [here](https://github.com/nvim-treesitter/nvim-treesitter/projects/1). diff --git a/doc/nvim-treesitter.txt b/doc/nvim-treesitter.txt index 084936bad..9c66b0a57 100644 --- a/doc/nvim-treesitter.txt +++ b/doc/nvim-treesitter.txt @@ -31,25 +31,27 @@ By default, everything is disabled. To enable support for features, in your `ini lua <<EOF require'nvim-treesitter.configs'.setup { highlight = { - enable = true, -- false will disable the whole extension - disable = { 'c', 'rust' }, -- list of language that will be disabled + enable = true, -- false will disable the whole extension + disable = { 'c', 'rust' }, -- list of language that will be disabled }, - incremental_selection = { -- this enables incremental selection + incremental_selection = { enable = true, disable = { 'cpp', 'lua' }, keymaps = { -- mappings for incremental selection (visual mappings) - node_incremental = "<leader>e", -- "grn" by default, - scope_incremental = "<leader>f" -- "grc" by default + init_selection = 'gnn', -- maps in normal mode to init the node/scope selection + 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 enable = true, disable = { 'cpp', 'rust' }, - keymaps = { -- mappings for node movement (normal mappings) - move_up = "<a-k>", -- default is to move with alt key hold - move_down = "<a-j>", - move_left = "<a-h>", - move_right = "<a-l>", + keymaps = { -- mappings for scope movement + parent_scope = "<a-k>", -- default is to move with alt key hold + child_scope = "<a-j>", + next_scope = "<a-h>", + previous_scope = "<a-l>", } }, ensure_installed = 'all' -- one of 'all', 'language', or a list of languages diff --git a/lua/nvim-treesitter.lua b/lua/nvim-treesitter.lua index 90b5468a3..5c0dfac75 100644 --- a/lua/nvim-treesitter.lua +++ b/lua/nvim-treesitter.lua @@ -1,15 +1,14 @@ local api = vim.api + local install = require'nvim-treesitter.install' -local locals = require'nvim-treesitter.locals' local utils = require'nvim-treesitter.utils' local info = require'nvim-treesitter.info' local configs = require'nvim-treesitter.configs' +local state = require'nvim-treesitter.state' local M = {} --- This function sets up everythin needed for a given language --- this is the main interface through the plugin -function M.setup(lang) +function M.setup() utils.setup_commands('install', install.commands) utils.setup_commands('info', info.commands) utils.setup_commands('configs', configs.commands) @@ -18,33 +17,34 @@ function M.setup(lang) for _, mod in pairs(configs.available_modules()) do if configs.is_enabled(mod, ft) then 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 + 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 + + state.run_update() end function M.statusline(indicator_size) - local indicator_size = indicator_size or 1000 - local expr = require"nvim-treesitter.utils".expression_at_point() - local current_node = - require'nvim-treesitter.node_movement'.current_node[api.nvim_get_current_buf()] - - local indicator = "" - while expr and (#indicator + #(expr:type()) + 5) < indicator_size do - - local prefix = "" - if expr:parent() then - prefix = "->" - end + local indicator_size = indicator_size or 100 + local bufnr = api.nvim_get_current_buf() + local buf_state = state.get_buf_state(bufnr) + if not buf_state then return "" end + local current_node = buf_state.current_node + if not current_node then return "" end - if expr == current_node then - indicator = string.format("%s[%s]%s", prefix, expr:type(), indicator) - else - indicator = prefix .. expr:type() .. indicator - 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 + indicator = expr:type() .. prefix .. indicator expr = expr:parent() end diff --git a/lua/nvim-treesitter/configs.lua b/lua/nvim-treesitter/configs.lua index 9695da5af..31f01a72a 100644 --- a/lua/nvim-treesitter/configs.lua +++ b/lua/nvim-treesitter/configs.lua @@ -1,6 +1,8 @@ local api = vim.api + local queries = require'nvim-treesitter.query' -local parser_utils = require'nvim-treesitter.parsers' +local utils = require'nvim-treesitter.utils' + local parsers = {} parsers.javascript = { @@ -218,28 +220,28 @@ local config = { enable = false, disable = {}, keymaps = { + init_selection="gnn", 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 = { enable = false, disable = {}, - is_supported = function() return true end, + is_supported = function(ft) + return queries.get_query(ft, 'locals') + end, keymaps = { - move_up = "<a-k>", - move_down = "<a-j>", - move_left = "<a-h>", - move_right = "<a-l>", + parent_scope = "<a-k>", + child_scope = "<a-j>", + next_scope = "<a-l>", + previous_scope = "<a-h>", }, - }, - -- folding = { - -- enable = false, - -- disable = {}, - -- keymaps = {}, - -- is_supported = function() return false end - -- } + } }, ensure_installed = nil } @@ -279,12 +281,12 @@ local function enable_all(mod, ft) end end if ft then - if parser_utils.has_parser(ft) then + if utils.has_parser(ft) then enable_mod_conf_autocmd(mod, ft) end else 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) end end @@ -364,7 +366,7 @@ M.commands = { -- @param mod: module (string) -- @param ft: filetype (string) 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 end @@ -378,6 +380,7 @@ function M.is_enabled(mod, ft) for _, parser in pairs(module_config.disable) do if ft == parser then return false end end + return true end @@ -393,11 +396,15 @@ function M.setup(user_data) config.modules[mod].disable = data.disable end 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 elseif mod == 'ensure_installed' then config.ensure_installed = data - require'nvim-treesitter/install'.ensure_installed(data) + require'nvim-treesitter.install'.ensure_installed(data) end end end diff --git a/lua/nvim-treesitter/health.lua b/lua/nvim-treesitter/health.lua index 7ba1cae65..a1852162e 100644 --- a/lua/nvim-treesitter/health.lua +++ b/lua/nvim-treesitter/health.lua @@ -55,8 +55,6 @@ function locals_health(lang) end end - --- TODO(vigoux): Maybe we should move each check to be perform in its own module function M.checkhealth() -- Installation dependency checks health_start('Installation') diff --git a/lua/nvim-treesitter/highlight.lua b/lua/nvim-treesitter/highlight.lua index 9cf259639..c1981aff8 100644 --- a/lua/nvim-treesitter/highlight.lua +++ b/lua/nvim-treesitter/highlight.lua @@ -1,5 +1,6 @@ local api = vim.api local ts = vim.treesitter + local queries = require'nvim-treesitter.query' local M = { diff --git a/lua/nvim-treesitter/incremental_selection.lua b/lua/nvim-treesitter/incremental_selection.lua index 70eefe37c..ea7a9f71e 100644 --- a/lua/nvim-treesitter/incremental_selection.lua +++ b/lua/nvim-treesitter/incremental_selection.lua @@ -1,77 +1,102 @@ local api = vim.api -local utils = require'nvim-treesitter.utils' -local parsers = require'nvim-treesitter.parsers' -local M = {} -local function node_range_to_vim(node) - if not node then return end +local state = require'nvim-treesitter.state' +local configs = require'nvim-treesitter.configs' +local ts_utils = require'nvim-treesitter.ts_utils' + +local M = {} +local function update_selection(buf, node) local start_row, start_col, end_row, end_col = node:range() - local select_range = [[ - call cursor(%d, %d) - normal v - call cursor(%d, %d) - ]] - local exec_command = string.format(select_range, - start_row+1, start_col+1, - end_row+1, end_col+1) + if end_row == vim.fn.line('$') then + end_col = #vim.fn.getline('$') + end - 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 -local function select_incremental(increment_func) +local function select_incremental(get_parent) return function() - local buf, sel_start_line, sel_start_col, _ = unpack(vim.fn.getpos("'<")) - local buf, sel_end_line, sel_end_col, _ = unpack(vim.fn.getpos("'>")) - - local node = nil - if parsers.has_parser() then - local root = parsers.get_parser():parse():root() - node = root:named_descendant_for_range(sel_start_line-1, sel_start_col-1, sel_end_line-1, sel_end_col) - local node_start_row, node_start_col, node_end_row, node_end_col = node:range() + local buf = api.nvim_get_current_buf() + local buf_state = state.get_buf_state(buf) - if (sel_start_line-1) == node_start_row and (sel_start_col-1) == node_start_col - and (sel_end_line-1) == node_end_row and sel_end_col == node_end_col then - node = increment_func(node) + local node + -- initialize incremental selection with current range + if #buf_state.selection.nodes == 0 then + local cur_range = buf_state.selection.range + if not cur_range then + local _, cursor_row, cursor_col, _ = unpack(vim.fn.getpos(".")) + cur_range = { cursor_row, cursor_col, cursor_row, cursor_col + 1 } 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 + + if not node then return end + + if node ~= buf_state.selection.nodes[#buf_state.selection.nodes] then + state.insert_selection_node(buf, node) end - return node_range_to_vim(node) + update_selection(buf, node) end end M.node_incremental = select_incremental(function(node) - if node then - return node:parent() or node - end + return node:parent() or node end) M.scope_incremental = select_incremental(function(node) - if node then - return utils.smallest_containing_scope(node:parent() or node) - end + return ts_utils.containing_scope(node:parent() or node) 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) 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 - api.nvim_buf_set_keymap(buf, 'v', mapping, - string.format(":lua require'nvim-treesitter.incremental_selection'.%s()<CR>", funcname), { silent = true }) - api.nvim_buf_set_keymap(buf, 'o', mapping, - string.format(":normal v%s<CR>", mapping), { silent = true }) + + if funcname == "init_selection" then + local cmd = ":lua require'nvim-treesitter.incremental_selection'.node_incremental()<CR>" + 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 function M.detach(bufnr) local buf = bufnr or api.nvim_get_current_buf() - local config = require'nvim-treesitter.configs'.get_module('incremental_selection') - for _, mapping in pairs(config.keymaps) do - api.nvim_buf_del_keymap(buf, 'v', mapping) - api.nvim_buf_del_keymap(buf, 'o', mapping) + 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(buf, 'n', mapping) + else + api.nvim_buf_del_keymap(buf, 'v', mapping) + end end end diff --git a/lua/nvim-treesitter/install.lua b/lua/nvim-treesitter/install.lua index 77e0fccc4..45aec99ca 100644 --- a/lua/nvim-treesitter/install.lua +++ b/lua/nvim-treesitter/install.lua @@ -1,38 +1,11 @@ local api = vim.api local fn = vim.fn local luv = vim.loop -local configs = require'nvim-treesitter/configs' -local parsers = configs.get_parser_configs() -local has_parser = require'nvim-treesitter/parsers'.has_parser -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') +local configs = require'nvim-treesitter.configs' +local utils = require'nvim-treesitter.utils' - 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 M = {} local function iter_cmd(cmd_list, i, ft) 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 end - local parser_config = parsers[ft] + local parser_config = configs.get_parser_configs()[ft] if not parser_config then return api.nvim_err_writeln('Parser not available for language '..ft) end @@ -137,10 +110,10 @@ local function install(ft) return api.nvim_err_writeln('Git is required on your system to run this command') 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 - 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 run_install(cache_folder, package_path, ft, install_info) @@ -157,7 +130,7 @@ M.ensure_installed = function(languages) end for _, ft in ipairs(languages) do - if not has_parser(ft) then + if not utils.has_parser(ft) then install(ft) end end diff --git a/lua/nvim-treesitter/locals.lua b/lua/nvim-treesitter/locals.lua index b16408ac5..313d7655d 100644 --- a/lua/nvim-treesitter/locals.lua +++ b/lua/nvim-treesitter/locals.lua @@ -3,11 +3,12 @@ -- its the way nvim-treesitter uses to "understand" the code local api = vim.api local ts = vim.treesitter + local queries = require'nvim-treesitter.query' -local parsers = require'nvim-treesitter.parsers' +local utils = require'nvim-treesitter.utils' local M = { - locals={} + locals = {} } function M.collect_locals(bufnr) @@ -17,7 +18,7 @@ function M.collect_locals(bufnr) local query = queries.get_query(ft, 'locals') 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 local root = parser:parse():root() diff --git a/lua/nvim-treesitter/node_movement.lua b/lua/nvim-treesitter/node_movement.lua index 5d4813bc4..f6f15fced 100644 --- a/lua/nvim-treesitter/node_movement.lua +++ b/lua/nvim-treesitter/node_movement.lua @@ -1,88 +1,69 @@ local api = vim.api -local parsers = require'nvim-treesitter.parsers' -local utils = require'nvim-treesitter.utils' -local M = {} - -M.NodeMovementKind = { - up = 'up', - down = 'down', - left = 'left', - right = 'right', -} +local configs = require'nvim-treesitter.configs' +local state = require'nvim-treesitter.state' +local ts_utils = require'nvim-treesitter.ts_utils' -M.current_node = {} +local M = {} -local function node_start_to_vim(node) - if not node then return end +local NodeMovementKind = { + parent_scope = 'parent', + child_scope = 'child', + next_scope = 'next', + previous_scope = 'previous', +} - local row, col = node:start() - local exec_command = string.format('call cursor(%d, %d)', row+1, col+1) - api.nvim_exec(exec_command, false) -end +local get_node_fn = { + [NodeMovementKind.parent_scope] = function(node, curpos) + return ts_utils.parent_scope(node, curpos) + end, + [NodeMovementKind.child_scope] = function(node, curpos) + return ts_utils.nested_scope(node, curpos) + end, + [NodeMovementKind.next_scope] = function(node) + 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) - 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 node_line, node_col = current_node:start() - if line-1 ~= node_line or col-1 ~= node_col then - current_node = nil - end - end - local destination_node + local current_node = buf_state.current_node + if not current_node then return end - if parsers.has_parser() then - 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 + local destination_node = get_node_fn[kind](current_node, buf_state.cursor_pos) 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 -M.move_up = function() M.do_node_movement(M.NodeMovementKind.up) end -M.move_down = function() M.do_node_movement(M.NodeMovementKind.down) end -M.move_left = function() M.do_node_movement(M.NodeMovementKind.left) end -M.move_right = function() M.do_node_movement(M.NodeMovementKind.right) end +function M.parent_scope() M.do_node_movement(NodeMovementKind.parent_scope) end +function M.child_scope() M.do_node_movement(NodeMovementKind.child_scope) end +function M.next_scope() M.do_node_movement(NodeMovementKind.next_scope) end +function M.previous_scope() M.do_node_movement(NodeMovementKind.previous_scope) end 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 - api.nvim_buf_set_keymap(buf, 'n', mapping, - string.format(":lua require'nvim-treesitter.node_movement'.%s()<CR>", funcname), { silent = true }) + local cmd = string.format(":lua require'nvim-treesitter.node_movement'.%s()<CR>", funcname) + api.nvim_buf_set_keymap(bufnr, 'n', mapping, cmd, { silent = true }) end end function M.detach(bufnr) 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 api.nvim_buf_del_keymap(buf, 'n', mapping) end diff --git a/lua/nvim-treesitter/query.lua b/lua/nvim-treesitter/query.lua index 277c29697..644c33933 100644 --- a/lua/nvim-treesitter/query.lua +++ b/lua/nvim-treesitter/query.lua @@ -1,5 +1,3 @@ --- Treesitter utils - local api = vim.api local ts = vim.treesitter diff --git a/lua/nvim-treesitter/state.lua b/lua/nvim-treesitter/state.lua new file mode 100644 index 000000000..9da07ae10 --- /dev/null +++ b/lua/nvim-treesitter/state.lua @@ -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 diff --git a/lua/nvim-treesitter/ts_utils.lua b/lua/nvim-treesitter/ts_utils.lua new file mode 100644 index 000000000..1445418e0 --- /dev/null +++ b/lua/nvim-treesitter/ts_utils.lua @@ -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 diff --git a/lua/nvim-treesitter/utils.lua b/lua/nvim-treesitter/utils.lua index 9d591eab6..9dc5d17bd 100644 --- a/lua/nvim-treesitter/utils.lua +++ b/lua/nvim-treesitter/utils.lua @@ -1,69 +1,10 @@ --- Utils collection for nvim-treesitter local api = vim.api -local parsers = require'nvim-treesitter.parsers' -local locals = require'nvim-treesitter.locals' +local fn = vim.fn +local luv = vim.loop +local ts = vim.treesitter 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) 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) @@ -77,79 +18,50 @@ function M.setup_commands(mod, commands) end end ---- Gets the smallest scope which contains @param node -function M.smallest_containing_scope(node, bufnr) - local bufnr = bufnr or api.nvim_get_current_buf() - - local root = parsers.get_parser(bufnr):parse():root() - 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() +function M.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 current or root + return nil, 'Plugin runtime path not found.' 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() +function M.get_cache_dir() + local home = fn.get(fn.environ(), 'HOME') + local xdg_cache = fn.get(fn.environ(), 'XDG_CACHE_HOME') - if parent then - 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 + 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 destination_node + + return nil, 'Invalid cache rights, $XDG_CACHE_HOME or /tmp should be read/write' 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 parent then - 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 +function M.has_parser(lang) + local lang = lang or api.nvim_buf_get_option(0, 'filetype') + return #api.nvim_get_runtime_file('parser/' .. lang .. '.so', false) > 0 +end + +function M.get_parser(bufnr, lang) + if M.has_parser() then + local buf = bufnr or api.nvim_get_current_buf() + local lang = lang or api.nvim_buf_get_option(buf, 'ft') + if not M[buf] then + M[buf] = {} + end + if not M[buf][lang] then + M[buf][lang] = ts.get_parser(buf, lang) 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 M[buf][lang] end - return destination_node end return M |
