diff options
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/nvim-treesitter.lua | 58 | ||||
| -rw-r--r-- | lua/nvim-treesitter/configs.lua | 415 | ||||
| -rw-r--r-- | lua/nvim-treesitter/health.lua | 31 | ||||
| -rw-r--r-- | lua/nvim-treesitter/highlight.lua | 56 | ||||
| -rw-r--r-- | lua/nvim-treesitter/incremental_selection.lua | 78 | ||||
| -rw-r--r-- | lua/nvim-treesitter/info.lua | 96 | ||||
| -rw-r--r-- | lua/nvim-treesitter/install.lua | 72 | ||||
| -rw-r--r-- | lua/nvim-treesitter/locals.lua | 26 | ||||
| -rw-r--r-- | lua/nvim-treesitter/node_movement.lua | 91 | ||||
| -rw-r--r-- | lua/nvim-treesitter/parsers.lua | 3 | ||||
| -rw-r--r-- | lua/nvim-treesitter/utils.lua | 89 |
11 files changed, 888 insertions, 127 deletions
diff --git a/lua/nvim-treesitter.lua b/lua/nvim-treesitter.lua index b053f39b0..90b5468a3 100644 --- a/lua/nvim-treesitter.lua +++ b/lua/nvim-treesitter.lua @@ -1,30 +1,58 @@ local api = vim.api -local parsers = require'nvim-treesitter.parsers' -local configs = require 'nvim-treesitter.configs' local install = require'nvim-treesitter.install' local locals = require'nvim-treesitter.locals' -local highlight = require'nvim-treesitter.highlight' +local utils = require'nvim-treesitter.utils' +local info = require'nvim-treesitter.info' +local configs = require'nvim-treesitter.configs' local M = {} -function M.available_parsers() - return vim.tbl_keys(configs.repositories) -end - -- This function sets up everythin needed for a given language -- this is the main interface through the plugin function M.setup(lang) - if parsers.has_parser(lang) then - local autocmd = "autocmd NvimTreesitter FileType %s lua require'nvim-treesitter.highlight'.setup()" - api.nvim_command(string.format(autocmd, lang)) + utils.setup_commands('install', install.commands) + utils.setup_commands('info', info.commands) + utils.setup_commands('configs', configs.commands) + + for _, ft in pairs(configs.available_parsers()) do + 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)) + end + end end end --- This function initialize the plugin --- it is run at startup -M._root = {} -function M._root.setup() - install.setup() +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 + + + 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() + end + + if expr then + return "..." .. indicator + else + return indicator + end end return M diff --git a/lua/nvim-treesitter/configs.lua b/lua/nvim-treesitter/configs.lua index fd3b41d29..9695da5af 100644 --- a/lua/nvim-treesitter/configs.lua +++ b/lua/nvim-treesitter/configs.lua @@ -1,88 +1,421 @@ -local M = {} +local api = vim.api +local queries = require'nvim-treesitter.query' +local parser_utils = require'nvim-treesitter.parsers' +local parsers = {} -M.repositories = { - javascript = { +parsers.javascript = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-javascript", files = { "src/parser.c", "src/scanner.c" }, - }, - c = { + } +} + +parsers.c = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-c", files = { "src/parser.c" } - }, - cpp = { + } +} + +parsers.cpp = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-cpp", files = { "src/parser.c", "src/scanner.cc" } - }, - rust = { + } +} + +parsers.rust = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-rust", files = { "src/parser.c", "src/scanner.c" }, - }, - lua = { + } +} + +parsers.lua = { + install_info = { url = "https://github.com/nvim-treesitter/tree-sitter-lua", files = { "src/parser.c", "src/scanner.cc" } - }, - python = { + } +} + +parsers.python = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-python", files = { "src/parser.c", "src/scanner.cc" }, - }, - go = { + } +} + +parsers.go = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-go", files = { "src/parser.c" }, - }, - ruby = { + } +} + +parsers.ruby = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-ruby", files = { "src/parser.c", "src/scanner.cc" }, - }, - bash = { + } +} + +parsers.bash = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-bash", files = { "src/parser.c", "src/scanner.cc" }, - }, - php = { + } +} + +parsers.php = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-php", files = { "src/parser.c", "src/scanner.cc" }, - }, - java = { + } +} + +parsers.java = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-java", files = { "src/parser.c" }, - }, - html = { + } +} + +parsers.html = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-html", files = { "src/parser.c", "src/scanner.cc" }, - }, - julia = { + } +} + +parsers.julia = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-julia", files = { "src/parser.c", "src/scanner.c" }, - }, - json = { + } +} + +parsers.json = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-json", files = { "src/parser.c" }, - }, - css = { + } +} + +parsers.css = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-css", files = { "src/parser.c", "src/scanner.c" }, - }, - ocaml = { + } +} + +parsers.ocaml = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-ocaml", files = { "src/parser.c", "src/scanner.cc" }, - }, - swift = { + } +} + +parsers.swift = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-swift", files = { "src/parser.c" }, - }, - csharp = { + } +} + +parsers.csharp = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-c-sharp", files = { "src/parser.c", "src/scanner.c" }, - }, - typescript = { + } +} + +parsers.typescript = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-typescript", files = { "src/parser.c", "src/scanner.c" }, location = "tree-sitter-typescript/typescript" - }, - tsx = { + } +} + +parsers.tsx = { + install_info = { url = "https://github.com/tree-sitter/tree-sitter-typescript", files = { "src/parser.c", "src/scanner.c" }, location = "tree-sitter-tsx/tsx" } } +parsers.scala = { + install_info = { + url = "https://github.com/tree-sitter/tree-sitter-scala", + files = { "src/parser.c", "src/scanner.c" }, + } +} + +parsers.haskell = { + install_info = { + url = "https://github.com/tree-sitter/tree-sitter-haskell", + files = { "src/parser.c", "src/scanner.cc" }, + } +} + +parsers.markdown = { + install_info = { + url = "https://github.com/ikatyang/tree-sitter-markdown", + files = { "src/parser.c", "src/scanner.cc" }, + } +} + +parsers.toml = { + install_info = { + url = "https://github.com/ikatyang/tree-sitter-toml", + files = { "src/parser.c", "src/scanner.c" }, + } +} + +parsers.vue = { + install_info = { + url = "https://github.com/ikatyang/tree-sitter-vue", + files = { "src/parser.c", "src/scanner.cc" }, + } +} + +parsers.elm = { + install_info = { + url = "https://github.com//razzeee/tree-sitter-elm", + files = { "src/parser.c", "src/scanner.cc" }, + } +} + +parsers.yaml = { + install_info = { + url = "https://github.com/ikatyang/tree-sitter-yaml", + files = { "src/parser.c", "src/scanner.cc" }, + } +} + +parsers.nix = { + install_info = { + url = "https://github.com/cstrahan/tree-sitter-nix", + files = { "src/parser.c", "src/scanner.cc" }, + } +} + +-- @enable can be true or false +-- @disable is a list of languages, only relevant if enable is true +-- @keymaps list of user mappings for a given module if relevant +-- @is_supported function which, given a ft, will return true if the ft works on the module +local config = { + modules = { + highlight = { + enable = false, + disable = {}, + is_supported = function(ft) + return queries.get_query(ft, 'highlights') ~= nil + end + }, + incremental_selection = { + enable = false, + disable = {}, + keymaps = { + node_incremental="grn", + scope_incremental="grc" + }, + is_supported = function() return true end + }, + node_movement = { + enable = false, + disable = {}, + is_supported = function() return true end, + keymaps = { + move_up = "<a-k>", + move_down = "<a-j>", + move_left = "<a-h>", + move_right = "<a-l>", + }, + }, + -- folding = { + -- enable = false, + -- disable = {}, + -- keymaps = {}, + -- is_supported = function() return false end + -- } + }, + ensure_installed = nil +} + +local M = {} + +local function enable_module(mod, bufnr, ft) + local bufnr = bufnr or api.nvim_get_current_buf() + local ft = ft or api.nvim_buf_get_option(bufnr, 'ft') + if not parsers[ft] or not config.modules[mod] then + return + end + + local loaded_mod = require(string.format("nvim-treesitter.%s", mod)) + loaded_mod.attach(bufnr, ft) +end + +local function enable_mod_conf_autocmd(mod, ft) + if not config.modules[mod] or M.is_enabled(mod, ft) then return end + + local cmd = string.format("lua require'nvim-treesitter.%s'.attach()", mod) + api.nvim_command(string.format("autocmd FileType %s %s", ft, cmd)) + for i, parser in pairs(config.modules[mod].disable) do + if parser == ft then + table.remove(config.modules[mod].disable, i) + break + end + end +end + +local function enable_all(mod, ft) + if not config.modules[mod] then return end + + for _, bufnr in pairs(api.nvim_list_bufs()) do + if not ft or api.nvim_buf_get_option(bufnr, 'ft') == ft then + enable_module(mod, bufnr, ft) + end + end + if ft then + if parser_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 + enable_mod_conf_autocmd(mod, ft) + end + end + end + config.modules[mod].enable = true +end + +local function disable_module(mod, bufnr, ft) + local bufnr = bufnr or api.nvim_get_current_buf() + local ft = ft or api.nvim_buf_get_option(bufnr, 'ft') + if not parsers[ft] or not config.modules[mod] then + return + end + + local loaded_mod = require(string.format("nvim-treesitter.%s", mod)) + loaded_mod.detach(bufnr, ft) +end + +local function disable_mod_conf_autocmd(mod, ft) + if not config.modules[mod] or not M.is_enabled(mod, ft) then return end + + api.nvim_command(string.format("autocmd! FileType %s", ft)) + table.insert(config.modules[mod].disable, ft) +end + +local function disable_all(mod, ft) + for _, bufnr in pairs(api.nvim_list_bufs()) do + if not ft or api.nvim_buf_get_option(bufnr, 'ft') == ft then + disable_module(mod, bufnr, ft) + end + end + if ft then + disable_mod_conf_autocmd(mod, ft) + else + for _, ft in pairs(M.available_parsers()) do + disable_mod_conf_autocmd(mod, ft) + end + config.modules[mod].enable = false + end +end + +M.commands = { + TSBufEnable = { + run = enable_module, + args = { + "-nargs=1", + "-complete=custom,v:lua.ts_available_modules" + }, + description = '`:TSBufEnable module_name` enable a specified module on the current buffer' + }, + TSBufDisable = { + run = disable_module, + args = { + "-nargs=1", + "-complete=custom,v:lua.ts_available_modules" + }, + description = '`:TSBufDisable module_name` disable a specified module on the current buffer' + }, + TSEnableAll = { + run = enable_all, + args = { + "-nargs=+", + "-complete=custom,v:lua.ts_available_modules" + }, + description = '`:TSEnableAll module_name (filetype)` enables a specified module on all buffers. If filetype is specified, enable only for specified filetype' + }, + TSDisableAll = { + run = disable_all, + args = { + "-nargs=+", + "-complete=custom,v:lua.ts_available_modules" + }, + description = '`:TSDisableAll module_name (filetype)` disables a specified module on all buffers. If filetype is specified, disable only for specified filetype' + }, +} + +-- @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 + return false + end + + local module_config = config.modules[mod] + if not module_config then return false end + + if not module_config.enable or not module_config.is_supported(ft) then + return false + end + + for _, parser in pairs(module_config.disable) do + if ft == parser then return false end + end + return true +end + +function M.setup(user_data) + if not user_data then return end + + for mod, data in pairs(user_data) do + if config.modules[mod] then + if type(data.enable) == 'boolean' then + config.modules[mod].enable = data.enable + end + if type(data.disable) == 'table' then + config.modules[mod].disable = data.disable + end + if config.modules[mod].keymaps and type(data.keymaps) == 'table' then + config.modules[mod].keymaps = data.keymaps + end + elseif mod == 'ensure_installed' then + config.ensure_installed = data + require'nvim-treesitter/install'.ensure_installed(data) + end + end +end + +function M.get_parser_configs() + return parsers +end + +function M.available_parsers() + return vim.tbl_keys(parsers) +end + +function M.available_modules() + return vim.tbl_keys(config.modules) +end + +function M.get_module(mod) + return config.modules[mod] +end + return M diff --git a/lua/nvim-treesitter/health.lua b/lua/nvim-treesitter/health.lua index 25da7fe35..7ba1cae65 100644 --- a/lua/nvim-treesitter/health.lua +++ b/lua/nvim-treesitter/health.lua @@ -3,6 +3,7 @@ local fn = vim.fn local queries = require'nvim-treesitter.query' local locals = require'nvim-treesitter.locals' +local highlight = require'nvim-treesitter.highlight' local configs = require'nvim-treesitter.configs' local health_start = vim.fn["health#report_start"] @@ -13,7 +14,7 @@ local health_error = vim.fn['health#report_error'] local M = {} -local function configs_health() +local function install_health() if fn.executable('git') == 0 then health_error('`git` executable not found.', { 'Install it with your package manager.', @@ -34,15 +35,36 @@ local function configs_health() end end +local function highlight_health(lang) + if not queries.get_query(lang, "highlights") then + health_warn("No `highlights.scm` query found for " .. lang, { + "Open an issue at https://github.com/nvim-treesitter/nvim-treesitter" + }) + else + health_ok("`highlights.scm` found.") + end +end + +function locals_health(lang) + if not queries.get_query(lang, "locals") then + health_warn("No `locals.scm` query found for " .. lang, { + "Open an issue at https://github.com/nvim-treesitter/nvim-treesitter" + }) + else + health_ok("`locals.scm` found.") + 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') - configs_health() + install_health() local missing_parsers = {} -- Parser installation checks - for parser_name in pairs(configs.repositories) do + for _, parser_name in pairs(configs.available_parsers()) do local installed = #api.nvim_get_runtime_file('parser/'..parser_name..'.so', false) -- Only print informations about installed parsers @@ -50,7 +72,8 @@ function M.checkhealth() health_start(parser_name .. " parser healthcheck") health_ok(parser_name .. " parser found.") - locals.checkhealth(parser_name) + locals_health(parser_name) + highlight_health(parser_name) elseif installed > 1 then health_warn(string.format("Multiple parsers found for %s, only %s will be used.", parser_name, installed[1])) else diff --git a/lua/nvim-treesitter/highlight.lua b/lua/nvim-treesitter/highlight.lua index 110954a6a..9cf259639 100644 --- a/lua/nvim-treesitter/highlight.lua +++ b/lua/nvim-treesitter/highlight.lua @@ -1,12 +1,53 @@ local api = vim.api -local queries = require'nvim-treesitter.query' local ts = vim.treesitter +local queries = require'nvim-treesitter.query' local M = { - highlighters={} + highlighters = {} } -function M.setup(bufnr, ft) +local hlmap = vim.treesitter.TSHighlighter.hl_map + +-- Misc +hlmap.error = "Error" +hlmap["punctuation.delimiter"] = "Delimiter" +hlmap["punctuation.bracket"] = "Delimiter" + +-- Constants +hlmap["constant"] = "Constant" +hlmap["constant.builtin"] = "Special" +hlmap["constant.macro"] = "Define" +hlmap["string"] = "String" +hlmap["string.regex"] = "String" +hlmap["string.escape"] = "SpecialChar" +hlmap["character"] = "Character" +hlmap["number"] = "Number" +hlmap["boolean"] = "Boolean" +hlmap["float"] = "Float" + +-- Functions +hlmap["function"] = "Function" +hlmap["function.builtin"] = "Special" +hlmap["function.macro"] = "Macro" +hlmap["parameter"] = "Identifier" +hlmap["method"] = "Function" +hlmap["field"] = "Identifier" +hlmap["property"] = "Identifier" +hlmap["constructor"] = "Special" + +-- Keywords +hlmap["conditional"] = "Conditional" +hlmap["repeat"] = "Repeat" +hlmap["label"] = "Label" +hlmap["operator"] = "Operator" +hlmap["keyword"] = "Keyword" +hlmap["exception"] = "Exception" + +hlmap["type"] = "Type" +hlmap["type.builtin"] = "Type" +hlmap["structure"] = "Structure" + +function M.attach(bufnr, ft) local buf = bufnr or api.nvim_get_current_buf() local ft = ft or api.nvim_buf_get_option(buf, 'ft') @@ -16,4 +57,13 @@ function M.setup(bufnr, ft) M.highlighters[buf] = ts.TSHighlighter.new(query, buf, ft) end +function M.detach(bufnr) + local buf = bufnr or api.nvim_get_current_buf() + if M.highlighters[buf] then + M.highlighters[buf]:set_query("") + M.highlighters[buf] = nil + end + api.nvim_buf_set_option(buf, 'syntax', 'on') +end + return M diff --git a/lua/nvim-treesitter/incremental_selection.lua b/lua/nvim-treesitter/incremental_selection.lua new file mode 100644 index 000000000..70eefe37c --- /dev/null +++ b/lua/nvim-treesitter/incremental_selection.lua @@ -0,0 +1,78 @@ +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 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) + + api.nvim_exec(exec_command, false) +end + +local function select_incremental(increment_func) + 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() + + 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) + end + end + + return node_range_to_vim(node) + end +end + +M.node_incremental = select_incremental(function(node) + if node then + return node:parent() or node + end +end) + +M.scope_incremental = select_incremental(function(node) + if node then + return utils.smallest_containing_scope(node:parent() or node) + end +end) + +function M.attach(bufnr) + local buf = bufnr or api.nvim_get_current_buf() + + local config = require'nvim-treesitter.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 }) + 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) + end +end + +return M diff --git a/lua/nvim-treesitter/info.lua b/lua/nvim-treesitter/info.lua new file mode 100644 index 000000000..18b1b611f --- /dev/null +++ b/lua/nvim-treesitter/info.lua @@ -0,0 +1,96 @@ +local api = vim.api +local configs = require'nvim-treesitter.configs' + +local M = {} + +local function install_info() + local max_len = 0 + for _, ft in pairs(configs.available_parsers()) do + if #ft > max_len then max_len = #ft end + end + + for _, ft in pairs(configs.available_parsers()) do + local is_installed = #api.nvim_get_runtime_file('parser/'..ft..'.so', false) > 0 + api.nvim_out_write(ft..string.rep(' ', max_len - #ft + 1)) + if is_installed then + api.nvim_out_write("[✓] installed\n") + else + api.nvim_out_write("[✗] not installed\n") + end + end +end + +local function print_info_module(sorted_filetypes, mod) + local max_str_len = #sorted_filetypes[1] + local header = string.format('%s%s', string.rep(' ', max_str_len + 2), mod) + api.nvim_out_write(header..'\n') + for _, ft in pairs(sorted_filetypes) do + local padding = string.rep(' ', max_str_len - #ft + #mod / 2 + 1) + api.nvim_out_write(ft..":"..padding) + if configs.is_enabled(mod, ft) then + api.nvim_out_write('✓') + else + api.nvim_out_write('✗') + end + api.nvim_out_write('\n') + end +end + +local function print_info_modules(sorted_filetypes) + local max_str_len = #sorted_filetypes[1] + local header = string.rep(' ', max_str_len + 2) + for _, mod in pairs(configs.available_modules()) do + header = string.format('%s%s ', header, mod) + end + api.nvim_out_write(header..'\n') + + for _, ft in pairs(sorted_filetypes) do + local padding = string.rep(' ', max_str_len - #ft) + api.nvim_out_write(ft..":"..padding) + + for _, mod in pairs(configs.available_modules()) do + local pad_len = #mod / 2 + 1 + api.nvim_out_write(string.rep(' ', pad_len)) + + if configs.is_enabled(mod, ft) then + api.nvim_out_write('✓') + else + api.nvim_out_write('✗') + end + api.nvim_out_write(string.rep(' ', pad_len - 1)) + end + api.nvim_out_write('\n') + end +end + +local function module_info(mod) + if mod and not configs.get_config()[mod] then return end + + local ft_by_len = configs.available_parsers() + table.sort(ft_by_len, function(a, b) return #a > #b end) + if mod then + print_info_module(ft_by_len, mod) + else + print_info_modules(ft_by_len) + end +end + +M.commands = { + TSInstallInfo = { + run = install_info, + args = { + "-nargs=0", + }, + description = '`:TSInstallInfo` print installation state for every filetype' + }, + TSModuleInfo = { + run = module_info, + args = { + "-nargs=?", + "-complete=custom,v:lua.ts_available_modules" + }, + description = '`:TSModuleInfo` print module state for every filetype, if module is specified, only for current module' + } +} + +return M diff --git a/lua/nvim-treesitter/install.lua b/lua/nvim-treesitter/install.lua index b00d3d773..77e0fccc4 100644 --- a/lua/nvim-treesitter/install.lua +++ b/lua/nvim-treesitter/install.lua @@ -1,7 +1,9 @@ local api = vim.api local fn = vim.fn local luv = vim.loop -local repositories = require'nvim-treesitter/configs'.repositories +local configs = require'nvim-treesitter/configs' +local parsers = configs.get_parser_configs() +local has_parser = require'nvim-treesitter/parsers'.has_parser local M = {} @@ -33,14 +35,16 @@ local function get_cache_dir() end local function iter_cmd(cmd_list, i, ft) - if i == #cmd_list 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 - local attr = cmd_list[i + 1] + local attr = cmd_list[i] if attr.info then print(attr.info) end + local handle + handle = luv.spawn(attr.cmd, attr.opts, vim.schedule_wrap(function(code) handle:close() - if code ~= 0 then return api.nvim_err_writeln(attr.err) end + if code ~= 0 then return api.nvim_err_writeln(attr.err) end iter_cmd(cmd_list, i + 1, ft) end)) end @@ -75,12 +79,12 @@ local function run_install(cache_folder, package_path, ft, repo) args = vim.tbl_flatten({ '-o', 'parser.so', + '-I./src', + repo.files, '-shared', + '-Os', '-lstdc++', '-fPIC', - '-Os', - '-I./src', - repo.files }), cwd = compile_location } @@ -99,7 +103,7 @@ local function run_install(cache_folder, package_path, ft, repo) } } - iter_cmd(command_list, 0, ft) + iter_cmd(command_list, 1, ft) end -- TODO(kyazdani): this should work on windows too @@ -118,14 +122,15 @@ local function install(ft) if not string.match(yesno, '^y.*') then return end end - local repository = repositories[ft] - if not repository then + local parser_config = parsers[ft] + if not parser_config then return api.nvim_err_writeln('Parser not available for language '..ft) end + local install_info = parser_config.install_info vim.validate { - url={ repository.url, 'string' }, - files={ repository.files, 'table' } + url={ install_info.url, 'string' }, + files={ install_info.files, 'table' } } if fn.executable('git') == 0 then @@ -138,26 +143,27 @@ local function install(ft) local cache_folder, err = get_cache_dir() if err then return api.nvim_err_writeln(err) end - run_install(cache_folder, package_path, ft, repository) + run_install(cache_folder, package_path, ft, install_info) end -local function install_info() - local max_len = 0 - for parser_name, _ in pairs(repositories) do - if #parser_name > max_len then max_len = #parser_name end - end - for parser_name, _ in pairs(repositories) do - local is_installed = #api.nvim_get_runtime_file('parser/'..parser_name..'.so', false) > 0 - api.nvim_out_write(parser_name..string.rep(' ', max_len - #parser_name + 1)) - if is_installed then - api.nvim_out_write("[✓] installed\n") +M.ensure_installed = function(languages) + if type(languages) == 'string' then + if languages == 'all' then + languages = configs.available_parsers() else - api.nvim_out_write("[✗] not installed\n") + languages = {languages} + end + end + + for _, ft in ipairs(languages) do + if not has_parser(ft) then + install(ft) end end end + M.commands = { TSInstall = { run = install, @@ -166,25 +172,7 @@ M.commands = { "-complete=custom,v:lua.ts_installable_parsers" }, description = '`:TSInstall {ft}` installs a parser under nvim-treesitter/parser/{name}.so' - }, - TSInstallInfo = { - run = install_info, - args = { "-nargs=0" }, - description = '`:TSInstallInfo` print installation state for every filetype' } } -function M.setup() - for command_name, def in pairs(M.commands) do - local call_fn = string.format("lua require'nvim-treesitter.install'.commands.%s.run(<f-args>)", command_name) - local parts = vim.tbl_flatten({ - "command!", - def.args, - command_name, - call_fn, - }) - api.nvim_command(table.concat(parts, " ")) - end -end - return M diff --git a/lua/nvim-treesitter/locals.lua b/lua/nvim-treesitter/locals.lua index 388c7e489..b16408ac5 100644 --- a/lua/nvim-treesitter/locals.lua +++ b/lua/nvim-treesitter/locals.lua @@ -10,22 +10,6 @@ local M = { locals={} } -function M.checkhealth(lang) - local health_start = vim.fn["health#report_start"] - local health_ok = vim.fn['health#report_ok'] - local health_info = vim.fn['health#report_info'] - local health_warn = vim.fn['health#report_warn'] - local health_error = vim.fn['health#report_error'] - - if not queries.get_query(lang, "locals") then - health_warn("No `locals.scm` query found for " .. lang, { - "Open an issue at https://github.com/nvim-treesitter/nvim-treesitter" - }) - else - health_ok("`locals.scm` found.") - end -end - function M.collect_locals(bufnr) local ft = api.nvim_buf_get_option(bufnr, "ft") if not ft then return end @@ -69,7 +53,7 @@ function M.get_definitions(bufnr) for _, loc in ipairs(locals) do if loc.definition then - table.insert(defs, {definition=loc.definition, kind=loc.kind}) + table.insert(defs, loc.definition) end end @@ -82,8 +66,8 @@ function M.get_scopes(bufnr) local scopes = {} for _, loc in ipairs(locals) do - if loc.scope then - table.insert(scopes, loc.scope) + if loc.scope and loc.scope.node then + table.insert(scopes, loc.scope.node) end end @@ -96,8 +80,8 @@ function M.get_references(bufnr) local refs = {} for _, loc in ipairs(locals) do - if loc.reference then - table.insert(refs, loc.reference) + if loc.reference and loc.reference.node then + table.insert(refs, loc.reference.node) end end diff --git a/lua/nvim-treesitter/node_movement.lua b/lua/nvim-treesitter/node_movement.lua new file mode 100644 index 000000000..5d4813bc4 --- /dev/null +++ b/lua/nvim-treesitter/node_movement.lua @@ -0,0 +1,91 @@ +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', +} + +M.current_node = {} + +local function node_start_to_vim(node) + if not node then return end + + 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 + +M.do_node_movement = function(kind) + local buf, line, col = unpack(vim.fn.getpos(".")) + + local current_node = M.current_node[buf] + + 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 + + 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 + + if destination_node then + node_start_to_vim(destination_node) + 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.attach(bufnr) + local buf = bufnr or api.nvim_get_current_buf() + + local config = require'nvim-treesitter.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 }) + 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') + for _, mapping in pairs(config.keymaps) do + api.nvim_buf_del_keymap(buf, 'n', mapping) + end +end + +return M diff --git a/lua/nvim-treesitter/parsers.lua b/lua/nvim-treesitter/parsers.lua index e046ca45c..07f3e9d34 100644 --- a/lua/nvim-treesitter/parsers.lua +++ b/lua/nvim-treesitter/parsers.lua @@ -5,11 +5,12 @@ local M = {} function M.has_parser(lang) local lang = lang or api.nvim_buf_get_option(0, 'filetype') + if not lang or #lang == 0 then return false end return #api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0 end function M.get_parser(bufnr, lang) - if M.has_parser() then + if M.has_parser(lang) 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 diff --git a/lua/nvim-treesitter/utils.lua b/lua/nvim-treesitter/utils.lua index 8313a46a7..9d591eab6 100644 --- a/lua/nvim-treesitter/utils.lua +++ b/lua/nvim-treesitter/utils.lua @@ -1,6 +1,7 @@ -- Utils collection for nvim-treesitter local api = vim.api local parsers = require'nvim-treesitter.parsers' +local locals = require'nvim-treesitter.locals' local M = {} @@ -63,4 +64,92 @@ function M.is_parent(dest, source) 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) + local parts = vim.tbl_flatten({ + "command!", + def.args, + command_name, + call_fn, + }) + api.nvim_command(table.concat(parts, " ")) + 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() + end + + return current or root +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 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 + 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 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 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 + end + return destination_node +end + return M |
