aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/treesitter.lua')
-rw-r--r--runtime/lua/vim/treesitter.lua272
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