mirror of
https://github.com/neovim/neovim.git
synced 2026-04-25 08:44:06 +00:00
treesitter: refactor and use lua regexes
This commit is contained in:
@@ -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
|
||||
@@ -44,42 +46,30 @@ function Parser:set_included_ranges(ranges)
|
||||
self.valid = false
|
||||
end
|
||||
|
||||
local M = {
|
||||
parse_query = vim._ts_parse_query,
|
||||
}
|
||||
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
|
||||
@@ -91,8 +81,8 @@ function M.create_parser(bufnr, lang, id)
|
||||
self.changedtree_cbs = {}
|
||||
self.lines_cbs = {}
|
||||
self:parse()
|
||||
-- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
|
||||
-- using it.
|
||||
-- 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(...)
|
||||
end
|
||||
@@ -108,17 +98,31 @@ function M.create_parser(bufnr, lang, id)
|
||||
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 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.
|
||||
--
|
||||
-- @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)
|
||||
parsers[id] = M._create_parser(bufnr, lang, id)
|
||||
end
|
||||
|
||||
if buf_attach_cbs and buf_attach_cbs.on_changedtree then
|
||||
@@ -132,129 +136,4 @@ function M.get_parser(bufnr, ft, buf_attach_cbs)
|
||||
return parsers[id]
|
||||
end
|
||||
|
||||
-- query: pattern matching on trees
|
||||
-- predicate matching is implemented in lua
|
||||
local Query = {}
|
||||
Query.__index = Query
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user