diff options
Diffstat (limited to 'runtime/lua/vim/treesitter.lua')
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 272 |
1 files changed, 100 insertions, 172 deletions
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 927456708c..0de3388356 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -1,4 +1,6 @@ local a = vim.api +local query = require'vim.treesitter.query' +local language = require'vim.treesitter.language' -- TODO(bfredl): currently we retain parsers for the lifetime of the buffer. -- Consider use weak references to release parser if all plugins are done with @@ -8,12 +10,20 @@ local parsers = {} local Parser = {} Parser.__index = Parser +--- Parses the buffer if needed and returns a tree. +-- +-- Calling this will call the on_changedtree callbacks if the tree has changed. +-- +-- @returns An up to date tree +-- @returns If the tree changed with this call, the changed ranges function Parser:parse() if self.valid then return self.tree end local changes - self.tree, changes = self._parser:parse_buf(self.bufnr) + + self.tree, changes = self._parser:parse(self:input_source()) + self.valid = true if not vim.tbl_isempty(changes) then @@ -25,61 +35,86 @@ function Parser:parse() return self.tree, changes end -function Parser:_on_lines(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size) - local start_byte = a.nvim_buf_get_offset(bufnr,start_row) - local stop_byte = a.nvim_buf_get_offset(bufnr,stop_row) - local old_stop_byte = start_byte + old_byte_size - self._parser:edit(start_byte,old_stop_byte,stop_byte, - start_row,0,old_stop_row,0,stop_row,0) +function Parser:input_source() + return self.bufnr or self.str +end + +function Parser:_on_bytes(bufnr, changed_tick, + start_row, start_col, start_byte, + old_row, old_col, old_byte, + new_row, new_col, new_byte) + local old_end_col = old_col + ((old_row == 0) and start_col or 0) + local new_end_col = new_col + ((new_row == 0) and start_col or 0) + self._parser:edit(start_byte,start_byte+old_byte,start_byte+new_byte, + start_row, start_col, + start_row+old_row, old_end_col, + start_row+new_row, new_end_col) self.valid = false - for _, cb in ipairs(self.lines_cbs) do - cb(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size) + for _, cb in ipairs(self.bytes_cbs) do + cb(bufnr, changed_tick, + start_row, start_col, start_byte, + old_row, old_col, old_byte, + new_row, new_col, new_byte) 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. function Parser:set_included_ranges(ranges) self._parser:set_included_ranges(ranges) -- The buffer will need to be parsed again later self.valid = false end -local M = { - parse_query = vim._ts_parse_query, -} +--- 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, { __index = function (t, k) if k == "TSHighlighter" then - t[k] = require'vim.tshighlighter' + a.nvim_err_writeln("vim.TSHighlighter is deprecated, please use vim.treesitter.highlighter") + t[k] = require'vim.treesitter.highlighter' + return t[k] + elseif k == "highlighter" then + t[k] = require'vim.treesitter.highlighter' return t[k] end end }) -function M.require_language(lang, path) - if vim._ts_has_language(lang) then - return true - end - if path == nil then - local fname = 'parser/' .. lang .. '.*' - local paths = a.nvim_get_runtime_file(fname, false) - if #paths == 0 then - -- TODO(bfredl): help tag? - error("no parser for '"..lang.."' language") - end - path = paths[1] - end - vim._ts_add_language(path, lang) -end - -function M.inspect_language(lang) - M.require_language(lang) - return vim._ts_inspect_language(lang) -end - -function M.create_parser(bufnr, lang, id) - M.require_language(lang) +--- Creates a new parser. +-- +-- It is not recommended to use this, use vim.treesitter.get_parser() instead. +-- +-- @param bufnr The buffer the parser will be tied to +-- @param lang The language of the parser. +-- @param id The id the parser will have +function M._create_parser(bufnr, lang, id) + language.require_language(lang) if bufnr == 0 then bufnr = a.nvim_get_current_buf() end @@ -89,12 +124,12 @@ function M.create_parser(bufnr, lang, id) local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser) self._parser = vim._create_ts_parser(lang) self.changedtree_cbs = {} - self.lines_cbs = {} + self.bytes_cbs = {} self:parse() -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is -- using it. - local function lines_cb(_, ...) - return self:_on_lines(...) + local function bytes_cb(_, ...) + return self:_on_bytes(...) end local detach_cb = nil if id ~= nil then @@ -104,157 +139,50 @@ function M.create_parser(bufnr, lang, id) end end end - a.nvim_buf_attach(self.bufnr, false, {on_lines=lines_cb, on_detach=detach_cb}) + a.nvim_buf_attach(self.bufnr, false, {on_bytes=bytes_cb, on_detach=detach_cb}) return self end -function M.get_parser(bufnr, ft, buf_attach_cbs) +--- Gets the parser for this bufnr / ft combination. +-- +-- If needed this will create the parser. +-- Unconditionnally attach the provided callback +-- +-- @param bufnr The buffer the parser should be tied to +-- @param ft The filetype of this parser +-- @param buf_attach_cbs See Parser:register_cbs +-- +-- @returns The parser +function M.get_parser(bufnr, lang, buf_attach_cbs) if bufnr == nil or bufnr == 0 then bufnr = a.nvim_get_current_buf() end - if ft == nil then - ft = a.nvim_buf_get_option(bufnr, "filetype") + if lang == nil then + lang = a.nvim_buf_get_option(bufnr, "filetype") end - local id = tostring(bufnr)..'_'..ft + local id = tostring(bufnr)..'_'..lang if parsers[id] == nil then - parsers[id] = M.create_parser(bufnr, ft, id) - end - - if buf_attach_cbs and buf_attach_cbs.on_changedtree then - table.insert(parsers[id].changedtree_cbs, buf_attach_cbs.on_changedtree) + parsers[id] = M._create_parser(bufnr, lang, id) end - if buf_attach_cbs and buf_attach_cbs.on_lines then - table.insert(parsers[id].lines_cbs, buf_attach_cbs.on_lines) - end + parsers[id]:register_cbs(buf_attach_cbs) return parsers[id] end --- query: pattern matching on trees --- predicate matching is implemented in lua -local Query = {} -Query.__index = Query +function M.get_string_parser(str, lang) + vim.validate { + str = { str, 'string' }, + lang = { lang, 'string' } + } + language.require_language(lang) -local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true} -local function check_magic(str) - if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then - return str - end - return '\\v'..str -end + local self = setmetatable({str=str, lang=lang, valid=false}, Parser) + self._parser = vim._create_ts_parser(lang) + self:parse() -function M.parse_query(lang, query) - M.require_language(lang) - local self = setmetatable({}, Query) - self.query = vim._ts_parse_query(lang, vim.fn.escape(query,'\\')) - self.info = self.query:inspect() - self.captures = self.info.captures - self.regexes = {} - for id,preds in pairs(self.info.patterns) do - local regexes = {} - for i, pred in ipairs(preds) do - if (pred[1] == "match?" and type(pred[2]) == "number" - and type(pred[3]) == "string") then - regexes[i] = vim.regex(check_magic(pred[3])) - end - end - if next(regexes) then - self.regexes[id] = regexes - end - end return self end -local function get_node_text(node, bufnr) - local start_row, start_col, end_row, end_col = node:range() - if start_row ~= end_row then - return nil - end - local line = a.nvim_buf_get_lines(bufnr, start_row, start_row+1, true)[1] - return string.sub(line, start_col+1, end_col) -end - -function Query:match_preds(match, pattern, bufnr) - local preds = self.info.patterns[pattern] - if not preds then - return true - end - local regexes = self.regexes[pattern] - for i, pred in pairs(preds) do - -- Here we only want to return if a predicate DOES NOT match, and - -- continue on the other case. This way unknown predicates will not be considered, - -- which allows some testing and easier user extensibility (#12173). - -- Also, tree-sitter strips the leading # from predicates for us. - if pred[1] == "eq?" then - local node = match[pred[2]] - local node_text = get_node_text(node, bufnr) - - local str - if type(pred[3]) == "string" then - -- (#eq? @aa "foo") - str = pred[3] - else - -- (#eq? @aa @bb) - str = get_node_text(match[pred[3]], bufnr) - end - - if node_text ~= str or str == nil then - return false - end - elseif pred[1] == "match?" then - if not regexes or not regexes[i] then - return false - end - local node = match[pred[2]] - local start_row, start_col, end_row, end_col = node:range() - if start_row ~= end_row then - return false - end - if not regexes[i]:match_line(bufnr, start_row, start_col, end_col) then - return false - end - end - end - return true -end - -function Query:iter_captures(node, bufnr, start, stop) - if bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - local raw_iter = node:_rawquery(self.query,true,start,stop) - local function iter() - local capture, captured_node, match = raw_iter() - if match ~= nil then - local active = self:match_preds(match, match.pattern, bufnr) - match.active = active - if not active then - return iter() -- tail call: try next match - end - end - return capture, captured_node - end - return iter -end - -function Query:iter_matches(node, bufnr, start, stop) - if bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - local raw_iter = node:_rawquery(self.query,false,start,stop) - local function iter() - local pattern, match = raw_iter() - if match ~= nil then - local active = self:match_preds(match, pattern, bufnr) - if not active then - return iter() -- tail call: try next match - end - end - return pattern, match - end - return iter -end - return M |