aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkiyan42 <yazdani.kiyan@protonmail.com>2020-05-08 11:22:59 +0200
committerkiyan42 <yazdani.kiyan@protonmail.com>2020-05-12 16:16:48 +0200
commit45dcebb15f1a954eba2bcb3ae4c1a03f710a1f2a (patch)
tree50cbca8e9615d69a8ff9aca6b33a35abb32f9363
parentMerge pull request #47 from theHamsta/fix-typo-contributing.md (diff)
downloadnvim-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.md26
-rw-r--r--doc/nvim-treesitter.txt22
-rw-r--r--lua/nvim-treesitter.lua44
-rw-r--r--lua/nvim-treesitter/configs.lua47
-rw-r--r--lua/nvim-treesitter/health.lua2
-rw-r--r--lua/nvim-treesitter/highlight.lua1
-rw-r--r--lua/nvim-treesitter/incremental_selection.lua109
-rw-r--r--lua/nvim-treesitter/install.lua41
-rw-r--r--lua/nvim-treesitter/locals.lua7
-rw-r--r--lua/nvim-treesitter/node_movement.lua101
-rw-r--r--lua/nvim-treesitter/query.lua2
-rw-r--r--lua/nvim-treesitter/state.lua114
-rw-r--r--lua/nvim-treesitter/ts_utils.lua208
-rw-r--r--lua/nvim-treesitter/utils.lua162
14 files changed, 554 insertions, 332 deletions
diff --git a/README.md b/README.md
index afedbc400..2c5a3ce71 100644
--- a/README.md
+++ b/README.md
@@ -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