diff options
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 37 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/highlighter.lua | 102 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 98 |
3 files changed, 181 insertions, 56 deletions
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 77bbfaa3ad..0de3388356 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -59,6 +59,24 @@ function Parser:_on_bytes(bufnr, changed_tick, end end +--- Registers callbacks for the parser +-- @param cbs An `nvim_buf_attach`-like table argument with the following keys : +-- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback. +-- `on_changedtree` : a callback that will be called everytime the tree has syntactical changes. +-- it will only be passed one argument, that is a table of the ranges (as node ranges) that +-- changed. +function Parser:register_cbs(cbs) + if not cbs then return end + + if cbs.on_changedtree then + table.insert(self.changedtree_cbs, cbs.on_changedtree) + end + + if cbs.on_bytes then + table.insert(self.bytes_cbs, cbs.on_bytes) + end +end + --- Sets the included ranges for the current parser -- -- @param ranges A table of nodes that will be used as the ranges the parser should include. @@ -68,6 +86,11 @@ function Parser:set_included_ranges(ranges) self.valid = false end +--- Gets the included ranges for the parsers +function Parser:included_ranges() + return self._parser:included_ranges() +end + local M = vim.tbl_extend("error", query, language) setmetatable(M, { @@ -127,11 +150,7 @@ end -- -- @param bufnr The buffer the parser should be tied to -- @param ft The filetype of this parser --- @param buf_attach_cbs An `nvim_buf_attach`-like table argument with the following keys : --- `on_lines` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback. --- `on_changedtree` : a callback that will be called everytime the tree has syntactical changes. --- it will only be passed one argument, that is a table of the ranges (as node ranges) that --- changed. +-- @param buf_attach_cbs See Parser:register_cbs -- -- @returns The parser function M.get_parser(bufnr, lang, buf_attach_cbs) @@ -147,13 +166,7 @@ function M.get_parser(bufnr, lang, buf_attach_cbs) parsers[id] = M._create_parser(bufnr, lang, id) end - if buf_attach_cbs and buf_attach_cbs.on_changedtree then - table.insert(parsers[id].changedtree_cbs, buf_attach_cbs.on_changedtree) - end - - if buf_attach_cbs and buf_attach_cbs.on_bytes then - table.insert(parsers[id].bytes_cbs, buf_attach_cbs.on_bytes) - end + parsers[id]:register_cbs(buf_attach_cbs) return parsers[id] end diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 5b964a6020..decde08019 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -6,7 +6,9 @@ TSHighlighter.__index = TSHighlighter TSHighlighter.active = TSHighlighter.active or {} --- These are conventions defined by tree-sitter, though it +local ns = a.nvim_create_namespace("treesitter/highlighter") + +-- These are conventions defined by nvim-treesitter, though it -- needs to be user extensible also. TSHighlighter.hl_map = { ["error"] = "Error", @@ -54,21 +56,14 @@ TSHighlighter.hl_map = { ["include"] = "Include", } -function TSHighlighter.new(query, bufnr, ft) - if bufnr == nil or bufnr == 0 then - bufnr = a.nvim_get_current_buf() - end - +function TSHighlighter.new(parser, query) local self = setmetatable({}, TSHighlighter) - self.parser = vim.treesitter.get_parser( - bufnr, - ft, - { - on_changedtree = function(...) self:on_changedtree(...) end, - } - ) - - self.buf = self.parser.bufnr + + self.parser = parser + parser:register_cbs { + on_changedtree = function(...) self:on_changedtree(...) end + } + self:set_query(query) self.edit_count = 0 self.redraw_count = 0 @@ -77,7 +72,11 @@ function TSHighlighter.new(query, bufnr, ft) a.nvim_buf_set_option(self.buf, "syntax", "") -- TODO(bfredl): can has multiple highlighters per buffer???? - TSHighlighter.active[bufnr] = self + if not TSHighlighter.active[parser.bufnr] then + TSHighlighter.active[parser.bufnr] = {} + end + + TSHighlighter.active[parser.bufnr][parser.lang] = self -- Tricky: if syntax hasn't been enabled, we need to reload color scheme -- but use synload.vim rather than syntax.vim to not enable @@ -117,13 +116,6 @@ end function TSHighlighter:set_query(query) if type(query) == "string" then query = vim.treesitter.parse_query(self.parser.lang, query) - elseif query == nil then - query = vim.treesitter.get_query(self.parser.lang, 'highlights') - - if query == nil then - a.nvim_err_writeln("No highlights.scm query found for " .. self.parser.lang) - query = vim.treesitter.parse_query(self.parser.lang, "") - end end self.query = query @@ -137,12 +129,16 @@ function TSHighlighter:set_query(query) end }) - a.nvim__buf_redraw_range(self.buf, 0, a.nvim_buf_line_count(self.buf)) + a.nvim__buf_redraw_range(self.parser.bufnr, 0, a.nvim_buf_line_count(self.parser.bufnr)) end -function TSHighlighter._on_line(_, _win, buf, line) - -- on_line is only called when this is non-nil - local self = TSHighlighter.active[buf] +local function iter_active_tshl(buf, fn) + for _, hl in pairs(TSHighlighter.active[buf] or {}) do + fn(hl) + end +end + +local function on_line_impl(self, buf, line) if self.root == nil then return -- parser bought the farm already end @@ -158,7 +154,11 @@ function TSHighlighter._on_line(_, _win, buf, line) local start_row, start_col, end_row, end_col = node:range() local hl = self.hl_cache[capture] if hl and end_row >= line then - a.nvim__put_attr(start_row, start_col, { end_line = end_row, end_col = end_col, hl_group = hl }) + a.nvim_buf_set_extmark(buf, ns, start_row, start_col, + { end_line = end_row, end_col = end_col, + hl_group = hl, + ephemeral = true + }) end if start_row > line then self.nextrow = start_row @@ -166,31 +166,45 @@ function TSHighlighter._on_line(_, _win, buf, line) end end -function TSHighlighter._on_start(_, buf, _tick) - local self = TSHighlighter.active[buf] - if self then - local tree = self.parser:parse() - self.root = (tree and tree:root()) or nil +function TSHighlighter._on_line(_, _win, buf, line, highlighter) + -- on_line is only called when this is non-nil + if highlighter then + on_line_impl(highlighter, buf, line) + else + iter_active_tshl(buf, function(self) + on_line_impl(self, buf, line) + end) end end +function TSHighlighter._on_buf(_, buf) + iter_active_tshl(buf, function(self) + if self then + local tree = self.parser:parse() + self.root = (tree and tree:root()) or nil + end + end) +end + function TSHighlighter._on_win(_, _win, buf, _topline, botline) - local self = TSHighlighter.active[buf] - if not self then - return false - end + iter_active_tshl(buf, function(self) + if not self then + return false + end - self.iter = nil - self.nextrow = 0 - self.botline = botline - self.redraw_count = self.redraw_count + 1 + self.iter = nil + self.nextrow = 0 + self.botline = botline + self.redraw_count = self.redraw_count + 1 + return true + end) return true end -a.nvim__set_luahl { - on_start = TSHighlighter._on_start; +a.nvim_set_decoration_provider(ns, { + on_buf = TSHighlighter._on_buf; on_win = TSHighlighter._on_win; on_line = TSHighlighter._on_line; -} +}) return TSHighlighter diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 494fb59fa7..2903c5905c 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -8,6 +8,104 @@ Query.__index = Query local M = {} +-- Filter the runtime query files, the spec is like regular runtime files but in the new `queries` +-- directory. They resemble ftplugins, that is that you can override queries by adding things in the +-- `queries` directory, and extend using the `after/queries` directory. +local function filter_files(file_list) + local main = nil + local after = {} + + for _, fname in ipairs(file_list) do + -- Only get the name of the directory containing the queries directory + if vim.fn.fnamemodify(fname, ":p:h:h:h:t") == "after" then + table.insert(after, fname) + -- The first one is the one with most priority + elseif not main then + main = fname + end + end + + return { main, unpack(after) } +end + +local function runtime_query_path(lang, query_name) + return string.format('queries/%s/%s.scm', lang, query_name) +end + +local function filtered_runtime_queries(lang, query_name) + return filter_files(a.nvim_get_runtime_file(runtime_query_path(lang, query_name), true) or {}) +end + +local function get_query_files(lang, query_name, is_included) + local lang_files = filtered_runtime_queries(lang, query_name) + local query_files = lang_files + + if #query_files == 0 then return {} end + + local base_langs = {} + + -- Now get the base languages by looking at the first line of every file + -- The syntax is the folowing : + -- ;+ inherits: ({language},)*{language} + -- + -- {language} ::= {lang} | ({lang}) + local MODELINE_FORMAT = "^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$" + + for _, file in ipairs(query_files) do + local modeline = vim.fn.readfile(file, "", 1) + + if #modeline == 1 then + local langlist = modeline[1]: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)) + end + else + table.insert(base_langs, incllang) + end + end + end + end + end + + for _, base_lang in ipairs(base_langs) do + local base_files = get_query_files(base_lang, query_name, true) + vim.list_extend(query_files, base_files) + end + + return query_files +end + +local function read_query_files(filenames) + local contents = {} + + for _,filename in ipairs(filenames) do + vim.list_extend(contents, vim.fn.readfile(filename)) + end + + return table.concat(contents, '\n') +end + +--- Returns the runtime query {query_name} for {lang}. +-- +-- @param lang The language to use for the query +-- @param query_name The name of the query (i.e. "highlights") +-- +-- @return The corresponding query, parsed. +function M.get_query(lang, query_name) + local query_files = get_query_files(lang, query_name) + local query_string = read_query_files(query_files) + + if #query_string > 0 then + return M.parse_query(lang, query_string) + end +end + --- Parses a query. -- -- @param language The language |