diff --git a/lua/nvim-treesitter.lua b/lua/nvim-treesitter.lua index 731f55e5d..079044151 100644 --- a/lua/nvim-treesitter.lua +++ b/lua/nvim-treesitter.lua @@ -1,28 +1,13 @@ local api = vim.api -local parsers = require'nvim-treesitter.parsers' 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 highlight = require'nvim-treesitter.highlight' 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) - - for _, ft in pairs(configs.available_parsers()) do - for _, mod in pairs(configs.available_modules()) do - if parsers.has_parser(ft) and 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 + utils.setup_commands('highlight', highlight.commands) end return M diff --git a/lua/nvim-treesitter/configs.lua b/lua/nvim-treesitter/configs.lua index c9450ed61..4d9309a8f 100644 --- a/lua/nvim-treesitter/configs.lua +++ b/lua/nvim-treesitter/configs.lua @@ -1,6 +1,4 @@ local api = vim.api -local queries = require'nvim-treesitter.query' -local parser_utils = require'nvim-treesitter.parsers' local parsers = {} parsers.javascript = { @@ -145,190 +143,9 @@ parsers.tsx = { } } --- @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 = { - highlight = { - enable = false, - disable = {}, - is_supported = function(ft) - return queries.get_query(ft, 'highlights') ~= nil - end - }, - -- selection = { - -- enable = false, - -- disable = {}, - -- keymaps = {}, - -- is_supported = function() return false end - -- }, - -- folding = { - -- enable = false, - -- disable = {}, - -- keymaps = {}, - -- is_supported = function() return false end - -- } -} - 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[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[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[mod].disable) do - if parser == ft then - table.remove(config[mod].disable, i) - break - end - end -end - -local function enable_all(mod, ft) - if not config[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 - enable_mod_conf_autocmd(mod, ft) - 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[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[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[mod] or not M.is_enabled(mod, ft) then return end - - api.nvim_command(string.format("autocmd! FileType %s", ft)) - table.insert(config[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 - if parser_utils.has_parser(ft) then - disable_mod_conf_autocmd(mod, ft) - end - end - config[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] then return false end - - local module_config = M.get_config()[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[mod] then - if type(data.enable) == 'boolean' then - config[mod].enable = data.enable - end - if type(data.disable) == 'table' then - config[mod].disable = data.disable - end - if config[mod].keymaps and type(data.keymaps) == 'table' then - config[mod].keymaps = data.keymaps - end - end - end -end - -function M.get_config() - return config -end - -function M.get_parser_configs() +function M.get_configs() return parsers end @@ -336,8 +153,4 @@ function M.available_parsers() return vim.tbl_keys(parsers) end -function M.available_modules() - return vim.tbl_keys(config) -end - return M diff --git a/lua/nvim-treesitter/health.lua b/lua/nvim-treesitter/health.lua index 563e46a90..f9c7d3267 100644 --- a/lua/nvim-treesitter/health.lua +++ b/lua/nvim-treesitter/health.lua @@ -1,9 +1,9 @@ local api = vim.api local fn = vim.fn -local queries = require'nvim-treesitter.query' local locals = require'nvim-treesitter.locals' local configs = require'nvim-treesitter.configs' +local queries = require'nvim-treesitter.query' local health_start = vim.fn["health#report_start"] local health_ok = vim.fn['health#report_ok'] @@ -13,6 +13,16 @@ local health_error = vim.fn['health#report_error'] local M = {} +local function queries_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 + local function install_health() if fn.executable('git') == 0 then health_error('`git` executable not found.', { @@ -42,19 +52,19 @@ function M.checkhealth() local missing_parsers = {} -- Parser installation checks - for _, parser_name in pairs(configs.available_parsers()) do - local installed = #api.nvim_get_runtime_file('parser/'..parser_name..'.so', false) + for _, ft in pairs(configs.available_parsers()) do + local installed = #api.nvim_get_runtime_file('parser/'..ft..'.so', false) -- Only print informations about installed parsers if installed == 1 then - health_start(parser_name .. " parser healthcheck") - health_ok(parser_name .. " parser found.") + health_start(ft .. " parser healthcheck") + health_ok(ft .. " parser found.") - locals.checkhealth(parser_name) + queries_health(ft) elseif installed > 1 then - health_warn(string.format("Multiple parsers found for %s, only %s will be used.", parser_name, installed[1])) + health_warn(string.format("Multiple parsers found for %s, only %s will be used.", ft, installed[1])) else - table.insert(missing_parsers, parser_name) + table.insert(missing_parsers, ft) end end diff --git a/lua/nvim-treesitter/highlight.lua b/lua/nvim-treesitter/highlight.lua index 65ab48dcb..5de19757a 100644 --- a/lua/nvim-treesitter/highlight.lua +++ b/lua/nvim-treesitter/highlight.lua @@ -1,11 +1,35 @@ local api = vim.api local ts = vim.treesitter local queries = require'nvim-treesitter.query' +local utils = require'nvim-treesitter.utils' +local configs = require'nvim-treesitter.configs' local M = { - highlighters = {} + highlighters = {}, + registered_autocmd = {} } +local function detach(bufnr) + local buf = bufnr or api.nvim_get_current_buf() + if not M.highlighters[buf] then return end + + M.highlighters[buf]:set_query("") + M.highlighters[buf] = nil + api.nvim_buf_set_option(buf, 'syntax', 'on') +end + +local function disable_hl() + for _, ft in pairs(configs.available_parsers()) do + if M.registered_autocmd[ft] then + api.nvim_command(string.format("autocmd! NvimTreesitter FileType %s", ft)) + M.registered_autocmd[ft] = nil + end + end + for _, buf in pairs(api.nvim_list_bufs()) do + detach(buf) + end +end + 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,13 +40,37 @@ function M.attach(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 +local function enable_hl() + M.setup() + for _, buf in pairs(api.nvim_list_bufs()) do + M.attach(buf) end - api.nvim_buf_set_option(buf, 'syntax', 'on') end +function M.setup() + for _, ft in pairs(configs.available_parsers()) do + if utils.has_parser(ft) + and queries.get_query(ft, 'highlights') + and not M.registered_autocmd[ft] then + local cmd = "lua require'nvim-treesitter.highlight'.attach()" + local autocmd = string.format('autocmd NvimTreesitter Filetype %s %s', ft, cmd) + M.registered_autocmd[ft] = autocmd + api.nvim_command(autocmd) + end + end +end + +M.commands = { + TSDisableHl = { + run = disable_hl, + args = { "-nargs=0" }, + description = '`:TSDisableHl` disable treesitter highlight for the current session' + }, + TSEnableHl = { + run = enable_hl, + args = { "-nargs=0" }, + description = '`:TSEnableHl` enable treesitter highlight for the current session' + } +} + return M diff --git a/lua/nvim-treesitter/info.lua b/lua/nvim-treesitter/info.lua deleted file mode 100644 index 18b1b611f..000000000 --- a/lua/nvim-treesitter/info.lua +++ /dev/null @@ -1,96 +0,0 @@ -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 be09a3bc1..4d5f6318b 100644 --- a/lua/nvim-treesitter/install.lua +++ b/lua/nvim-treesitter/install.lua @@ -1,8 +1,8 @@ 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 configs = require'nvim-treesitter.configs' +local parsers = configs.get_configs() local M = {} @@ -143,6 +143,22 @@ local function install(ft) run_install(cache_folder, package_path, ft, install_info) end +local function install_info() + local max_len = 0 + for ft in pairs(parsers) do + if #ft > max_len then max_len = #ft end + end + + for ft in pairs(parsers) do + api.nvim_out_write(ft..string.rep(' ', max_len - #ft + 1)) + if utils.has_parser(ft) then + api.nvim_out_write("[✓] installed\n") + else + api.nvim_out_write("[✗] not installed\n") + end + end +end + M.commands = { TSInstall = { run = install, @@ -151,6 +167,13 @@ 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' } } diff --git a/lua/nvim-treesitter/locals.lua b/lua/nvim-treesitter/locals.lua index 388c7e489..9bd9835a4 100644 --- a/lua/nvim-treesitter/locals.lua +++ b/lua/nvim-treesitter/locals.lua @@ -4,28 +4,12 @@ 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={} } -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 @@ -33,7 +17,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/parsers.lua b/lua/nvim-treesitter/parsers.lua deleted file mode 100644 index e046ca45c..000000000 --- a/lua/nvim-treesitter/parsers.lua +++ /dev/null @@ -1,25 +0,0 @@ -local api = vim.api -local ts = vim.treesitter - -local M = {} - -function M.has_parser(lang) - local lang = lang or api.nvim_buf_get_option(0, 'filetype') - return #api.nvim_get_runtime_file('parser/' .. lang .. '.*', 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 - return M[buf][lang] - end -end - -return M 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/tsutils.lua b/lua/nvim-treesitter/tsutils.lua new file mode 100644 index 000000000..57baa1430 --- /dev/null +++ b/lua/nvim-treesitter/tsutils.lua @@ -0,0 +1,65 @@ +local api = vim.api +local utils = require'nvim-treesitter.utils' + +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 + +return M diff --git a/lua/nvim-treesitter/utils.lua b/lua/nvim-treesitter/utils.lua index 59de5dd57..f022d9783 100644 --- a/lua/nvim-treesitter/utils.lua +++ b/lua/nvim-treesitter/utils.lua @@ -1,68 +1,9 @@ -- Utils collection for nvim-treesitter local api = vim.api -local parsers = require'nvim-treesitter.parsers' +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()", mod, command_name) @@ -76,4 +17,23 @@ function M.setup_commands(mod, commands) end end +function M.has_parser(ft) + local ft = ft or api.nvim_buf_get_option(0, 'filetype') + return #api.nvim_get_runtime_file('parser/' .. ft .. '.*', false) > 0 +end + +function M.get_parser(bufnr, ft) + if M.has_parser() then + local buf = bufnr or api.nvim_get_current_buf() + local ft = ft or api.nvim_buf_get_option(buf, 'ft') + if not M[buf] then + M[buf] = {} + end + if not M[buf][ft] then + M[buf][ft] = ts.get_parser(buf, ft) + end + return M[buf][ft] + end +end + return M diff --git a/plugin/nvim-treesitter.vim b/plugin/nvim-treesitter.vim index a94f56a96..6fd1e263d 100644 --- a/plugin/nvim-treesitter.vim +++ b/plugin/nvim-treesitter.vim @@ -5,7 +5,7 @@ if exists('g:loaded_nvim_treesitter') endif augroup NvimTreesitter -augroup END +augroup end let g:loaded_nvim_treesitter = 1 @@ -13,8 +13,5 @@ lua << EOF ts_installable_parsers = function() return table.concat(require'nvim-treesitter.configs'.available_parsers(), '\n') end -ts_available_modules = function() - return table.concat(require'nvim-treesitter.configs'.available_modules(), '\n') -end require'nvim-treesitter'.setup() EOF