diff options
Diffstat (limited to 'runtime/lua/vim/treesitter')
-rw-r--r-- | runtime/lua/vim/treesitter/highlighter.lua | 141 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/language.lua | 14 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 44 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 79 |
4 files changed, 140 insertions, 138 deletions
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index e27a5fa9c3..1e625eddb8 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -12,105 +12,18 @@ TSHighlighterQuery.__index = TSHighlighterQuery local ns = a.nvim_create_namespace('treesitter/highlighter') -local _default_highlights = {} -local _link_default_highlight_once = function(from, to) - if not _default_highlights[from] then - _default_highlights[from] = true - a.nvim_set_hl(0, from, { link = to, default = true }) - end - - return from -end - --- If @definition.special does not exist use @definition instead -local subcapture_fallback = { - __index = function(self, capture) - local rtn - local shortened = capture - while not rtn and shortened do - shortened = shortened:match('(.*)%.') - rtn = shortened and rawget(self, shortened) - end - rawset(self, capture, rtn or '__notfound') - return rtn - end, -} - -TSHighlighter.hl_map = setmetatable({ - ['error'] = 'Error', - ['text.underline'] = 'Underlined', - ['todo'] = 'Todo', - ['debug'] = 'Debug', - - -- Miscs - ['comment'] = 'Comment', - ['punctuation.delimiter'] = 'Delimiter', - ['punctuation.bracket'] = 'Delimiter', - ['punctuation.special'] = 'Delimiter', - - -- Constants - ['constant'] = 'Constant', - ['constant.builtin'] = 'Special', - ['constant.macro'] = 'Define', - ['define'] = 'Define', - ['macro'] = 'Macro', - ['string'] = 'String', - ['string.regex'] = 'String', - ['string.escape'] = 'SpecialChar', - ['character'] = 'Character', - ['character.special'] = 'SpecialChar', - ['number'] = 'Number', - ['boolean'] = 'Boolean', - ['float'] = 'Float', - - -- Functions - ['function'] = 'Function', - ['function.special'] = 'Function', - ['function.builtin'] = 'Special', - ['function.macro'] = 'Macro', - ['parameter'] = 'Identifier', - ['method'] = 'Function', - ['field'] = 'Identifier', - ['property'] = 'Identifier', - ['constructor'] = 'Special', - - -- Keywords - ['conditional'] = 'Conditional', - ['repeat'] = 'Repeat', - ['label'] = 'Label', - ['operator'] = 'Operator', - ['keyword'] = 'Keyword', - ['exception'] = 'Exception', - - ['type'] = 'Type', - ['type.builtin'] = 'Type', - ['type.qualifier'] = 'Type', - ['type.definition'] = 'Typedef', - ['storageclass'] = 'StorageClass', - ['structure'] = 'Structure', - ['include'] = 'Include', - ['preproc'] = 'PreProc', -}, subcapture_fallback) - ----@private -local function is_highlight_name(capture_name) - local firstc = string.sub(capture_name, 1, 1) - return firstc ~= string.lower(firstc) -end - ---@private function TSHighlighterQuery.new(lang, query_string) local self = setmetatable({}, { __index = TSHighlighterQuery }) self.hl_cache = setmetatable({}, { __index = function(table, capture) - local hl, is_vim_highlight = self:_get_hl_from_capture(capture) - if not is_vim_highlight then - hl = _link_default_highlight_once(lang .. hl, hl) + local name = self._query.captures[capture] + local id = 0 + if not vim.startswith(name, '_') then + id = a.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang) end - local id = a.nvim_get_hl_id_by_name(hl) - rawset(table, capture, id) return id end, @@ -130,20 +43,6 @@ function TSHighlighterQuery:query() return self._query end ----@private ---- Get the hl from capture. ---- Returns a tuple { highlight_name: string, is_builtin: bool } -function TSHighlighterQuery:_get_hl_from_capture(capture) - local name = self._query.captures[capture] - - if is_highlight_name(name) then - -- From "Normal.left" only keep "Normal" - return vim.split(name, '.', true)[1], true - else - return TSHighlighter.hl_map[name] or 0, false - end -end - --- Creates a new highlighter using @param tree --- ---@param tree The language tree to use for highlighting @@ -187,7 +86,7 @@ function TSHighlighter.new(tree, opts) end end - a.nvim_buf_set_option(self.bufnr, 'syntax', '') + vim.bo[self.bufnr].syntax = '' TSHighlighter.active[self.bufnr] = self @@ -196,9 +95,13 @@ function TSHighlighter.new(tree, opts) -- syntax FileType autocmds. Later on we should integrate with the -- `:syntax` and `set syntax=...` machinery properly. if vim.g.syntax_on ~= 1 then - vim.api.nvim_command('runtime! syntax/synload.vim') + vim.cmd.runtime({ 'syntax/synload.vim', bang = true }) end + a.nvim_buf_call(self.bufnr, function() + vim.opt_local.spelloptions:append('noplainbuffer') + end) + self.tree:parse() return self @@ -257,7 +160,7 @@ function TSHighlighter:get_query(lang) end ---@private -local function on_line_impl(self, buf, line) +local function on_line_impl(self, buf, line, spell) self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -294,7 +197,9 @@ local function on_line_impl(self, buf, line) local start_row, start_col, end_row, end_col = node:range() local hl = highlighter_query.hl_cache[capture] - if hl and end_row >= line then + local is_spell = highlighter_query:query().captures[capture] == 'spell' + + if hl and end_row >= line and (not spell or is_spell) then a.nvim_buf_set_extmark(buf, ns, start_row, start_col, { end_line = end_row, end_col = end_col, @@ -302,6 +207,7 @@ local function on_line_impl(self, buf, line) ephemeral = true, priority = tonumber(metadata.priority) or 100, -- Low but leaves room below conceal = metadata.conceal, + spell = is_spell, }) end if start_row > line then @@ -318,7 +224,21 @@ function TSHighlighter._on_line(_, _win, buf, line, _) return end - on_line_impl(self, buf, line) + on_line_impl(self, buf, line, false) +end + +---@private +function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) + local self = TSHighlighter.active[buf] + if not self then + return + end + + self:reset_highlight_state() + + for row = srow, erow do + on_line_impl(self, buf, row, true) + end end ---@private @@ -345,6 +265,7 @@ a.nvim_set_decoration_provider(ns, { on_buf = TSHighlighter._on_buf, on_win = TSHighlighter._on_win, on_line = TSHighlighter._on_line, + _on_spell_nav = TSHighlighter._on_spell_nav, }) return TSHighlighter diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index dfb6f5be84..d14b825603 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -6,10 +6,11 @@ local M = {} --- --- Parsers are searched in the `parser` runtime directory. --- ----@param lang The language the parser should parse ----@param path Optional path the parser is located at ----@param silent Don't throw an error if language not found -function M.require_language(lang, path, silent) +---@param lang string The language the parser should parse +---@param path string|nil Optional path the parser is located at +---@param silent boolean|nil Don't throw an error if language not found +---@param symbol_name string|nil Internal symbol name for the language to load +function M.require_language(lang, path, silent, symbol_name) if vim._ts_has_language(lang) then return true end @@ -21,7 +22,6 @@ function M.require_language(lang, path, silent) return false end - -- TODO(bfredl): help tag? error("no parser for '" .. lang .. "' language, see :help treesitter-parsers") end path = paths[1] @@ -29,10 +29,10 @@ function M.require_language(lang, path, silent) if silent then return pcall(function() - vim._ts_add_language(path, lang) + vim._ts_add_language(path, lang, symbol_name) end) else - vim._ts_add_language(path, lang) + vim._ts_add_language(path, lang, symbol_name) end return true diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 4d3b0631a2..70317a9f94 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -299,7 +299,7 @@ function LanguageTree:included_regions() end ---@private -local function get_node_range(node, id, metadata) +local function get_range_from_metadata(node, id, metadata) if metadata[id] and metadata[id].range then return metadata[id].range end @@ -362,7 +362,7 @@ function LanguageTree:_get_injections() elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then - table.insert(ranges, get_node_range(node, id, metadata)) + table.insert(ranges, get_range_from_metadata(node, id, metadata)) -- Ignore any tags that start with "_" -- Allows for other tags to be used in matches elseif string.sub(name, 1, 1) ~= '_' then @@ -371,7 +371,7 @@ function LanguageTree:_get_injections() end if #ranges == 0 then - table.insert(ranges, get_node_range(node, id, metadata)) + table.insert(ranges, get_range_from_metadata(node, id, metadata)) end end end @@ -549,6 +549,44 @@ function LanguageTree:contains(range) return false end +--- Gets the tree that contains {range} +--- +---@param range table A text range +---@param opts table Options table +---@param opts.ignore_injections boolean (default true) Ignore injected languages. +function LanguageTree:tree_for_range(range, opts) + opts = opts or {} + local ignore = vim.F.if_nil(opts.ignore_injections, true) + + if not ignore then + for _, child in pairs(self._children) do + for _, tree in pairs(child:trees()) do + if tree_contains(tree, range) then + return tree + end + end + end + end + + for _, tree in pairs(self._trees) do + if tree_contains(tree, range) then + return tree + end + end + + return nil +end + +--- Gets the smallest named node that contains {range} +--- +---@param range table A text range +---@param opts table Options table +---@param opts.ignore_injections boolean (default true) Ignore injected languages. +function LanguageTree:named_node_for_range(range, opts) + local tree = self:tree_for_range(range, opts) + return tree:root():named_descendant_for_range(unpack(range)) +end + --- Gets the appropriate language that contains {range} --- ---@param range A text range, see |LanguageTree:contains| diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 103e85abfd..90ed2a357c 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -34,6 +34,18 @@ local function safe_read(filename, read_quantifier) return content end +---@private +--- Adds @p ilang to @p base_langs, only if @p ilang is different than @lang +--- +---@return boolean true it lang == ilang +local function add_included_lang(base_langs, lang, ilang) + if lang == ilang then + return true + end + table.insert(base_langs, ilang) + return false +end + --- Gets the list of files used to make up a query --- ---@param lang The language @@ -47,6 +59,9 @@ function M.get_query_files(lang, query_name, is_included) return {} end + local base_query = nil + local extensions = {} + local base_langs = {} -- Now get the base languages by looking at the first line of every file @@ -55,35 +70,61 @@ function M.get_query_files(lang, query_name, is_included) -- -- {language} ::= {lang} | ({lang}) local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$' + local EXTENDS_FORMAT = '^;+%s*extends%s*$' - for _, file in ipairs(lang_files) do - local modeline = safe_read(file, '*l') + for _, filename in ipairs(lang_files) do + local file, err = io.open(filename, 'r') + if not file then + error(err) + end - if modeline then - local langlist = modeline:match(MODELINE_FORMAT) + local extension = false + for modeline in + function() + return file:read('*l') + end + do + if not vim.startswith(modeline, ';') then + break + end + + local langlist = modeline:match(MODELINE_FORMAT) if langlist then for _, incllang in ipairs(vim.split(langlist, ',', true)) do local is_optional = incllang:match('%(.*%)') if is_optional then if not is_included then - table.insert(base_langs, incllang:sub(2, #incllang - 1)) + if add_included_lang(base_langs, lang, incllang:sub(2, #incllang - 1)) then + extension = true + end end else - table.insert(base_langs, incllang) + if add_included_lang(base_langs, lang, incllang) then + extension = true + end end end + elseif modeline:match(EXTENDS_FORMAT) then + extension = true end end + + if extension then + table.insert(extensions, filename) + elseif base_query == nil then + base_query = filename + end + io.close(file) end - local query_files = {} + local query_files = { base_query } for _, base_lang in ipairs(base_langs) do local base_files = M.get_query_files(base_lang, query_name, true) vim.list_extend(query_files, base_files) end - vim.list_extend(query_files, lang_files) + vim.list_extend(query_files, extensions) return query_files end @@ -140,12 +181,9 @@ function M.get_query(lang, query_name) end end -local query_cache = setmetatable({}, { - __index = function(tbl, key) - rawset(tbl, key, {}) - return rawget(tbl, key) - end, -}) +local query_cache = vim.defaulttable(function() + return setmetatable({}, { __mode = 'v' }) +end) --- Parse {query} as a string. (If the query is in a file, the caller --- should read the contents into a string before calling). @@ -181,9 +219,14 @@ end --- Gets the text corresponding to a given node --- ----@param node the node ----@param source The buffer or string from which the node is extracted -function M.get_node_text(node, source) +---@param node table The node +---@param source table The buffer or string from which the node is extracted +---@param opts table Optional parameters. +--- - concat: (boolean default true) Concatenate result in a string +function M.get_node_text(node, source, opts) + opts = opts or {} + local concat = vim.F.if_nil(opts.concat, true) + local start_row, start_col, start_byte = node:start() local end_row, end_col, end_byte = node:end_() @@ -210,7 +253,7 @@ function M.get_node_text(node, source) end end - return table.concat(lines, '\n') + return concat and table.concat(lines, '\n') or lines elseif type(source) == 'string' then return source:sub(start_byte + 1, end_byte) end |