mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge pull request #12739 from vigoux/ts-refactor-predicates
treesitter: refactor
This commit is contained in:
commit
aa48c1c724
@ -574,6 +574,14 @@ retained for the lifetime of a buffer but this is subject to change. A plugin
|
|||||||
should keep a reference to the parser object as long as it wants incremental
|
should keep a reference to the parser object as long as it wants incremental
|
||||||
updates.
|
updates.
|
||||||
|
|
||||||
|
Parser files *treesitter-parsers*
|
||||||
|
|
||||||
|
Parsers are the heart of tree-sitter. They are libraries that tree-sitter will
|
||||||
|
search for in the `parsers` runtime directory.
|
||||||
|
|
||||||
|
For a parser to be available for a given language, there must be a file named
|
||||||
|
`{lang}.so` within the parser directory.
|
||||||
|
|
||||||
Parser methods *lua-treesitter-parser*
|
Parser methods *lua-treesitter-parser*
|
||||||
|
|
||||||
tsparser:parse() *tsparser:parse()*
|
tsparser:parse() *tsparser:parse()*
|
||||||
@ -593,9 +601,9 @@ shouldn't be done directly in the change callback anyway as they will be very
|
|||||||
frequent. Rather a plugin that does any kind of analysis on a tree should use
|
frequent. Rather a plugin that does any kind of analysis on a tree should use
|
||||||
a timer to throttle too frequent updates.
|
a timer to throttle too frequent updates.
|
||||||
|
|
||||||
tsparser:set_included_ranges(ranges) *tsparser:set_included_ranges()*
|
tsparser:set_included_ranges({ranges}) *tsparser:set_included_ranges()*
|
||||||
Changes the ranges the parser should consider. This is used for
|
Changes the ranges the parser should consider. This is used for
|
||||||
language injection. `ranges` should be of the form (all zero-based): >
|
language injection. {ranges} should be of the form (all zero-based): >
|
||||||
{
|
{
|
||||||
{start_node, end_node},
|
{start_node, end_node},
|
||||||
...
|
...
|
||||||
@ -617,15 +625,15 @@ tsnode:parent() *tsnode:parent()*
|
|||||||
tsnode:child_count() *tsnode:child_count()*
|
tsnode:child_count() *tsnode:child_count()*
|
||||||
Get the node's number of children.
|
Get the node's number of children.
|
||||||
|
|
||||||
tsnode:child(N) *tsnode:child()*
|
tsnode:child({index}) *tsnode:child()*
|
||||||
Get the node's child at the given index, where zero represents the
|
Get the node's child at the given {index}, where zero represents the
|
||||||
first child.
|
first child.
|
||||||
|
|
||||||
tsnode:named_child_count() *tsnode:named_child_count()*
|
tsnode:named_child_count() *tsnode:named_child_count()*
|
||||||
Get the node's number of named children.
|
Get the node's number of named children.
|
||||||
|
|
||||||
tsnode:named_child(N) *tsnode:named_child()*
|
tsnode:named_child({index}) *tsnode:named_child()*
|
||||||
Get the node's named child at the given index, where zero represents
|
Get the node's named child at the given {index}, where zero represents
|
||||||
the first named child.
|
the first named child.
|
||||||
|
|
||||||
tsnode:start() *tsnode:start()*
|
tsnode:start() *tsnode:start()*
|
||||||
@ -661,12 +669,12 @@ tsnode:has_error() *tsnode:has_error()*
|
|||||||
tsnode:sexpr() *tsnode:sexpr()*
|
tsnode:sexpr() *tsnode:sexpr()*
|
||||||
Get an S-expression representing the node as a string.
|
Get an S-expression representing the node as a string.
|
||||||
|
|
||||||
tsnode:descendant_for_range(start_row, start_col, end_row, end_col)
|
tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
|
||||||
*tsnode:descendant_for_range()*
|
*tsnode:descendant_for_range()*
|
||||||
Get the smallest node within this node that spans the given range of
|
Get the smallest node within this node that spans the given range of
|
||||||
(row, column) positions
|
(row, column) positions
|
||||||
|
|
||||||
tsnode:named_descendant_for_range(start_row, start_col, end_row, end_col)
|
tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
|
||||||
*tsnode:named_descendant_for_range()*
|
*tsnode:named_descendant_for_range()*
|
||||||
Get the smallest named node within this node that spans the given
|
Get the smallest named node within this node that spans the given
|
||||||
range of (row, column) positions
|
range of (row, column) positions
|
||||||
@ -677,17 +685,17 @@ Tree-sitter queries are supported, with some limitations. Currently, the only
|
|||||||
supported match predicate is `eq?` (both comparing a capture against a string
|
supported match predicate is `eq?` (both comparing a capture against a string
|
||||||
and two captures against each other).
|
and two captures against each other).
|
||||||
|
|
||||||
vim.treesitter.parse_query(lang, query)
|
vim.treesitter.parse_query({lang}, {query})
|
||||||
*vim.treesitter.parse_query(()*
|
*vim.treesitter.parse_query()*
|
||||||
Parse the query as a string. (If the query is in a file, the caller
|
Parse {query} as a string. (If the query is in a file, the caller
|
||||||
should read the contents into a string before calling).
|
should read the contents into a string before calling).
|
||||||
|
|
||||||
query:iter_captures(node, bufnr, start_row, end_row)
|
query:iter_captures({node}, {bufnr}, {start_row}, {end_row})
|
||||||
*query:iter_captures()*
|
*query:iter_captures()*
|
||||||
Iterate over all captures from all matches inside a `node`.
|
Iterate over all captures from all matches inside {node}.
|
||||||
`bufnr` is needed if the query contains predicates, then the caller
|
{bufnr} is needed if the query contains predicates, then the caller
|
||||||
must ensure to use a freshly parsed tree consistent with the current
|
must ensure to use a freshly parsed tree consistent with the current
|
||||||
text of the buffer. `start_row` and `end_row` can be used to limit
|
text of the buffer. {start_row} and {end_row} can be used to limit
|
||||||
matches inside a row range (this is typically used with root node
|
matches inside a row range (this is typically used with root node
|
||||||
as the node, i e to get syntax highlight matches in the current
|
as the node, i e to get syntax highlight matches in the current
|
||||||
viewport)
|
viewport)
|
||||||
@ -704,7 +712,7 @@ query:iter_captures(node, bufnr, start_row, end_row)
|
|||||||
... use the info here ...
|
... use the info here ...
|
||||||
end
|
end
|
||||||
<
|
<
|
||||||
query:iter_matches(node, bufnr, start_row, end_row)
|
query:iter_matches({node}, {bufnr}, {start_row}, {end_row})
|
||||||
*query:iter_matches()*
|
*query:iter_matches()*
|
||||||
Iterate over all matches within a node. The arguments are the same as
|
Iterate over all matches within a node. The arguments are the same as
|
||||||
for |query:iter_captures()| but the iterated values are different:
|
for |query:iter_captures()| but the iterated values are different:
|
||||||
@ -721,8 +729,52 @@ query:iter_matches(node, bufnr, start_row, end_row)
|
|||||||
... use the info here ...
|
... use the info here ...
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
>
|
|
||||||
Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
|
Treesitter Query Predicates *lua-treesitter-predicates*
|
||||||
|
|
||||||
|
When writing queries for treesitter, one might use `predicates`, that is,
|
||||||
|
special scheme nodes that are evaluted to verify things on a captured node for
|
||||||
|
example, the |eq?| predicate : >
|
||||||
|
((identifier) @foo (#eq? @foo "foo"))
|
||||||
|
|
||||||
|
This will only match identifier corresponding to the `"foo"` text.
|
||||||
|
Here is a list of built-in predicates :
|
||||||
|
|
||||||
|
`eq?` *ts-predicate-eq?*
|
||||||
|
This predicate will check text correspondance between nodes or
|
||||||
|
strings : >
|
||||||
|
((identifier) @foo (#eq? @foo "foo"))
|
||||||
|
((node1) @left (node2) @right (#eq? @left @right))
|
||||||
|
<
|
||||||
|
`match?` *ts-predicate-match?*
|
||||||
|
This will match if the provived lua regex matches the text
|
||||||
|
corresponding to a node : >
|
||||||
|
((idenfitier) @constant (#match? @constant "^[A-Z_]+$"))
|
||||||
|
< Note: the `^` and `$` anchors will respectively match the
|
||||||
|
start and end of the node's text.
|
||||||
|
|
||||||
|
`vim-match?` *ts-predicate-vim-match?*
|
||||||
|
This will match the same way than |match?| but using vim
|
||||||
|
regexes.
|
||||||
|
|
||||||
|
`contains?` *ts-predicate-contains?*
|
||||||
|
Will check if any of the following arguments appears in the
|
||||||
|
text corresponding to the node : >
|
||||||
|
((identifier) @foo (#contains? @foo "foo"))
|
||||||
|
((identifier) @foo-bar (#contains @foo-bar "foo" "bar"))
|
||||||
|
<
|
||||||
|
*lua-treesitter-not-predicate*
|
||||||
|
Each predicate has a `not-` prefixed predicate that is just the negation of
|
||||||
|
the predicate.
|
||||||
|
|
||||||
|
*vim.treesitter.query.add_predicate()*
|
||||||
|
vim.treesitter.query.add_predicate({name}, {handler})
|
||||||
|
|
||||||
|
This adds a predicate with the name {name} to be used in queries.
|
||||||
|
{handler} should be a function whose signature will be : >
|
||||||
|
handler(match, pattern, bufnr, predicate)
|
||||||
|
|
||||||
|
Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
|
||||||
|
|
||||||
NOTE: This is a partially implemented feature, and not usable as a default
|
NOTE: This is a partially implemented feature, and not usable as a default
|
||||||
solution yet. What is documented here is a temporary interface indented
|
solution yet. What is documented here is a temporary interface indented
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
local a = vim.api
|
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.
|
-- 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
|
-- Consider use weak references to release parser if all plugins are done with
|
||||||
@ -8,6 +10,12 @@ local parsers = {}
|
|||||||
local Parser = {}
|
local Parser = {}
|
||||||
Parser.__index = 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()
|
function Parser:parse()
|
||||||
if self.valid then
|
if self.valid then
|
||||||
return self.tree
|
return self.tree
|
||||||
@ -38,48 +46,39 @@ function Parser:_on_lines(bufnr, changed_tick, start_row, old_stop_row, stop_row
|
|||||||
end
|
end
|
||||||
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)
|
function Parser:set_included_ranges(ranges)
|
||||||
self._parser:set_included_ranges(ranges)
|
self._parser:set_included_ranges(ranges)
|
||||||
-- The buffer will need to be parsed again later
|
-- The buffer will need to be parsed again later
|
||||||
self.valid = false
|
self.valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
local M = {
|
local M = vim.tbl_extend("error", query, language)
|
||||||
parse_query = vim._ts_parse_query,
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(M, {
|
setmetatable(M, {
|
||||||
__index = function (t, k)
|
__index = function (t, k)
|
||||||
if k == "TSHighlighter" then
|
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]
|
return t[k]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
function M.require_language(lang, path)
|
--- Creates a new parser.
|
||||||
if vim._ts_has_language(lang) then
|
--
|
||||||
return true
|
-- It is not recommended to use this, use vim.treesitter.get_parser() instead.
|
||||||
end
|
--
|
||||||
if path == nil then
|
-- @param bufnr The buffer the parser will be tied to
|
||||||
local fname = 'parser/' .. lang .. '.*'
|
-- @param lang The language of the parser.
|
||||||
local paths = a.nvim_get_runtime_file(fname, false)
|
-- @param id The id the parser will have
|
||||||
if #paths == 0 then
|
function M._create_parser(bufnr, lang, id)
|
||||||
-- TODO(bfredl): help tag?
|
language.require_language(lang)
|
||||||
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)
|
|
||||||
if bufnr == 0 then
|
if bufnr == 0 then
|
||||||
bufnr = a.nvim_get_current_buf()
|
bufnr = a.nvim_get_current_buf()
|
||||||
end
|
end
|
||||||
@ -91,8 +90,8 @@ function M.create_parser(bufnr, lang, id)
|
|||||||
self.changedtree_cbs = {}
|
self.changedtree_cbs = {}
|
||||||
self.lines_cbs = {}
|
self.lines_cbs = {}
|
||||||
self:parse()
|
self:parse()
|
||||||
-- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
|
-- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
|
||||||
-- using it.
|
-- using it.
|
||||||
local function lines_cb(_, ...)
|
local function lines_cb(_, ...)
|
||||||
return self:_on_lines(...)
|
return self:_on_lines(...)
|
||||||
end
|
end
|
||||||
@ -108,17 +107,31 @@ function M.create_parser(bufnr, lang, id)
|
|||||||
return self
|
return self
|
||||||
end
|
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
|
if bufnr == nil or bufnr == 0 then
|
||||||
bufnr = a.nvim_get_current_buf()
|
bufnr = a.nvim_get_current_buf()
|
||||||
end
|
end
|
||||||
if ft == nil then
|
if lang == nil then
|
||||||
ft = a.nvim_buf_get_option(bufnr, "filetype")
|
lang = a.nvim_buf_get_option(bufnr, "filetype")
|
||||||
end
|
end
|
||||||
local id = tostring(bufnr)..'_'..ft
|
local id = tostring(bufnr)..'_'..lang
|
||||||
|
|
||||||
if parsers[id] == nil then
|
if parsers[id] == nil then
|
||||||
parsers[id] = M.create_parser(bufnr, ft, id)
|
parsers[id] = M._create_parser(bufnr, lang, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
if buf_attach_cbs and buf_attach_cbs.on_changedtree then
|
if buf_attach_cbs and buf_attach_cbs.on_changedtree then
|
||||||
@ -132,129 +145,4 @@ function M.get_parser(bufnr, ft, buf_attach_cbs)
|
|||||||
return parsers[id]
|
return parsers[id]
|
||||||
end
|
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
|
return M
|
||||||
|
@ -7,20 +7,50 @@ local ts_hs_ns = a.nvim_create_namespace("treesitter_hl")
|
|||||||
|
|
||||||
-- These are conventions defined by tree-sitter, though it
|
-- These are conventions defined by tree-sitter, though it
|
||||||
-- needs to be user extensible also.
|
-- needs to be user extensible also.
|
||||||
-- TODO(bfredl): this is very much incomplete, we will need to
|
|
||||||
-- go through a few tree-sitter provided queries and decide
|
|
||||||
-- on translations that makes the most sense.
|
|
||||||
TSHighlighter.hl_map = {
|
TSHighlighter.hl_map = {
|
||||||
keyword="Keyword",
|
["error"] = "Error",
|
||||||
string="String",
|
|
||||||
type="Type",
|
-- Miscs
|
||||||
comment="Comment",
|
["comment"] = "Comment",
|
||||||
constant="Constant",
|
["punctuation.delimiter"] = "Delimiter",
|
||||||
operator="Operator",
|
["punctuation.bracket"] = "Delimiter",
|
||||||
number="Number",
|
["punctuation.special"] = "Delimiter",
|
||||||
label="Label",
|
|
||||||
["function"]="Function",
|
-- Constants
|
||||||
["function.special"]="Function",
|
["constant"] = "Constant",
|
||||||
|
["constant.builtin"] = "Special",
|
||||||
|
["constant.macro"] = "Define",
|
||||||
|
["string"] = "String",
|
||||||
|
["string.regex"] = "String",
|
||||||
|
["string.escape"] = "SpecialChar",
|
||||||
|
["character"] = "Character",
|
||||||
|
["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",
|
||||||
|
["structure"] = "Structure",
|
||||||
|
["include"] = "Include",
|
||||||
}
|
}
|
||||||
|
|
||||||
function TSHighlighter.new(query, bufnr, ft)
|
function TSHighlighter.new(query, bufnr, ft)
|
||||||
@ -75,7 +105,15 @@ end
|
|||||||
function TSHighlighter:set_query(query)
|
function TSHighlighter:set_query(query)
|
||||||
if type(query) == "string" then
|
if type(query) == "string" then
|
||||||
query = vim.treesitter.parse_query(self.parser.lang, query)
|
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
|
end
|
||||||
|
|
||||||
self.query = query
|
self.query = query
|
||||||
|
|
||||||
self.hl_cache = setmetatable({}, {
|
self.hl_cache = setmetatable({}, {
|
37
runtime/lua/vim/treesitter/language.lua
Normal file
37
runtime/lua/vim/treesitter/language.lua
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
local a = vim.api
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Asserts that the provided language is installed, and optionnaly provide a path for the parser
|
||||||
|
--
|
||||||
|
-- Parsers are searched in the `parser` runtime directory.
|
||||||
|
--
|
||||||
|
-- @param lang The language the parser should parse
|
||||||
|
-- @param path Optionnal path the parser is located at
|
||||||
|
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, see :help treesitter-parsers")
|
||||||
|
end
|
||||||
|
path = paths[1]
|
||||||
|
end
|
||||||
|
vim._ts_add_language(path, lang)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Inspects the provided language.
|
||||||
|
--
|
||||||
|
-- Inspecting provides some useful informations on the language like node names, ...
|
||||||
|
--
|
||||||
|
-- @param lang The language.
|
||||||
|
function M.inspect_language(lang)
|
||||||
|
M.require_language(lang)
|
||||||
|
return vim._ts_inspect_language(lang)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
210
runtime/lua/vim/treesitter/query.lua
Normal file
210
runtime/lua/vim/treesitter/query.lua
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
local a = vim.api
|
||||||
|
local language = require'vim.treesitter.language'
|
||||||
|
|
||||||
|
-- query: pattern matching on trees
|
||||||
|
-- predicate matching is implemented in lua
|
||||||
|
local Query = {}
|
||||||
|
Query.__index = Query
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Parses a query.
|
||||||
|
--
|
||||||
|
-- @param language The language
|
||||||
|
-- @param query A string containing the query (s-expr syntax)
|
||||||
|
--
|
||||||
|
-- @returns The query
|
||||||
|
function M.parse_query(lang, query)
|
||||||
|
language.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
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO(vigoux): support multiline nodes too
|
||||||
|
|
||||||
|
--- Gets the text corresponding to a given node
|
||||||
|
-- @param node the node
|
||||||
|
-- @param bufnr the buffer from which the node in extracted.
|
||||||
|
function M.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
|
||||||
|
|
||||||
|
-- Predicate handler receive the following arguments
|
||||||
|
-- (match, pattern, bufnr, predicate)
|
||||||
|
local predicate_handlers = {
|
||||||
|
["eq?"] = function(match, _, bufnr, predicate)
|
||||||
|
local node = match[predicate[2]]
|
||||||
|
local node_text = M.get_node_text(node, bufnr)
|
||||||
|
|
||||||
|
local str
|
||||||
|
if type(predicate[3]) == "string" then
|
||||||
|
-- (#eq? @aa "foo")
|
||||||
|
str = predicate[3]
|
||||||
|
else
|
||||||
|
-- (#eq? @aa @bb)
|
||||||
|
str = M.get_node_text(match[predicate[3]], bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
if node_text ~= str or str == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
|
||||||
|
["match?"] = function(match, _, bufnr, predicate)
|
||||||
|
local node = match[predicate[2]]
|
||||||
|
local regex = predicate[3]
|
||||||
|
local start_row, _, end_row, _ = node:range()
|
||||||
|
if start_row ~= end_row then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return string.find(M.get_node_text(node, bufnr), regex)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["vim-match?"] = (function()
|
||||||
|
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 compiled_vim_regexes = setmetatable({}, {
|
||||||
|
__index = function(t, pattern)
|
||||||
|
local res = vim.regex(check_magic(pattern))
|
||||||
|
rawset(t, pattern, res)
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
return function(match, _, bufnr, pred)
|
||||||
|
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
|
||||||
|
|
||||||
|
local regex = compiled_vim_regexes[pred[3]]
|
||||||
|
return regex:match_line(bufnr, start_row, start_col, end_col)
|
||||||
|
end
|
||||||
|
end)(),
|
||||||
|
|
||||||
|
["contains?"] = function(match, _, bufnr, predicate)
|
||||||
|
local node = match[predicate[2]]
|
||||||
|
local node_text = M.get_node_text(node, bufnr)
|
||||||
|
|
||||||
|
for i=3,#predicate do
|
||||||
|
if string.find(node_text, predicate[i], 1, true) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Adds a new predicates to be used in queries
|
||||||
|
--
|
||||||
|
-- @param name the name of the predicate, without leading #
|
||||||
|
-- @param handler the handler function to be used
|
||||||
|
-- signature will be (match, pattern, bufnr, predicate)
|
||||||
|
function M.add_predicate(name, handler, force)
|
||||||
|
if predicate_handlers[name] and not force then
|
||||||
|
a.nvim_err_writeln(string.format("Overriding %s", name))
|
||||||
|
end
|
||||||
|
|
||||||
|
predicate_handlers[name] = handler
|
||||||
|
end
|
||||||
|
|
||||||
|
function Query:match_preds(match, pattern, bufnr)
|
||||||
|
local preds = self.info.patterns[pattern]
|
||||||
|
if not preds then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
for _, 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 string.sub(pred[1], 1, 4) == "not-" then
|
||||||
|
local pred_name = string.sub(pred[1], 5)
|
||||||
|
if predicate_handlers[pred_name] and
|
||||||
|
predicate_handlers[pred_name](match, pattern, bufnr, pred) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif predicate_handlers[pred[1]] and
|
||||||
|
not predicate_handlers[pred[1]](match, pattern, bufnr, pred) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Iterates of the captures of self on a given range.
|
||||||
|
--
|
||||||
|
-- @param node The node under witch the search will occur
|
||||||
|
-- @param buffer The source buffer to search
|
||||||
|
-- @param start The starting line of the search
|
||||||
|
-- @param stop The stoping line of the search (end-exclusive)
|
||||||
|
--
|
||||||
|
-- @returns The matching capture id
|
||||||
|
-- @returns The captured node
|
||||||
|
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
|
||||||
|
|
||||||
|
--- Iterates of the matches of self on a given range.
|
||||||
|
--
|
||||||
|
-- @param node The node under witch the search will occur
|
||||||
|
-- @param buffer The source buffer to search
|
||||||
|
-- @param start The starting line of the search
|
||||||
|
-- @param stop The stoping line of the search (end-exclusive)
|
||||||
|
--
|
||||||
|
-- @returns The matching pattern id
|
||||||
|
-- @returns The matching match
|
||||||
|
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
|
@ -15,14 +15,14 @@ before_each(clear)
|
|||||||
describe('treesitter API', function()
|
describe('treesitter API', function()
|
||||||
-- error tests not requiring a parser library
|
-- error tests not requiring a parser library
|
||||||
it('handles missing language', function()
|
it('handles missing language', function()
|
||||||
eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language",
|
eq("Error executing lua: .../language.lua: no parser for 'borklang' language, see :help treesitter-parsers",
|
||||||
pcall_err(exec_lua, "parser = vim.treesitter.create_parser(0, 'borklang')"))
|
pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')"))
|
||||||
|
|
||||||
-- actual message depends on platform
|
-- actual message depends on platform
|
||||||
matches("Error executing lua: Failed to load parser: uv_dlopen: .+",
|
matches("Error executing lua: Failed to load parser: uv_dlopen: .+",
|
||||||
pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')"))
|
pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')"))
|
||||||
|
|
||||||
eq("Error executing lua: .../treesitter.lua: no parser for 'borklang' language",
|
eq("Error executing lua: .../language.lua: no parser for 'borklang' language, see :help treesitter-parsers",
|
||||||
pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')"))
|
pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -198,6 +198,41 @@ void ui_refresh(void)
|
|||||||
}, res)
|
}, res)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('allows to add predicates', function()
|
||||||
|
insert([[
|
||||||
|
int main(void) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
local custom_query = "((identifier) @main (#is-main? @main))"
|
||||||
|
|
||||||
|
local res = exec_lua([[
|
||||||
|
local query = require"vim.treesitter.query"
|
||||||
|
|
||||||
|
local function is_main(match, pattern, bufnr, predicate)
|
||||||
|
local node = match[ predicate[2] ]
|
||||||
|
|
||||||
|
return query.get_node_text(node, bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
local parser = vim.treesitter.get_parser(0, "c")
|
||||||
|
|
||||||
|
query.add_predicate("is-main?", is_main)
|
||||||
|
|
||||||
|
local query = query.parse_query("c", ...)
|
||||||
|
|
||||||
|
local nodes = {}
|
||||||
|
for _, node in query:iter_captures(parser:parse():root(), 0, 0, 19) do
|
||||||
|
table.insert(nodes, {node:range()})
|
||||||
|
end
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
]], custom_query)
|
||||||
|
|
||||||
|
eq({{0, 4, 0, 8}}, res)
|
||||||
|
end)
|
||||||
|
|
||||||
it('supports highlighting', function()
|
it('supports highlighting', function()
|
||||||
if not check_parser() then return end
|
if not check_parser() then return end
|
||||||
|
|
||||||
@ -243,10 +278,10 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
(primitive_type) @type
|
(primitive_type) @type
|
||||||
(sized_type_specifier) @type
|
(sized_type_specifier) @type
|
||||||
|
|
||||||
; defaults to very magic syntax, for best compatibility
|
; Use lua regexes
|
||||||
((identifier) @Identifier (#match? @Identifier "^l(u)a_"))
|
((identifier) @Identifier (#contains? @Identifier "lua_"))
|
||||||
; still support \M etc prefixes
|
((identifier) @Constant (#match? @Constant "^[A-Z_]+$"))
|
||||||
((identifier) @Constant (#match? @Constant "\M^\[A-Z_]\+$"))
|
((identifier) @Normal (#vim-match? @Constant "^lstate$"))
|
||||||
|
|
||||||
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right))
|
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right))
|
||||||
|
|
||||||
@ -292,13 +327,13 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
]]}
|
]]}
|
||||||
|
|
||||||
exec_lua([[
|
exec_lua([[
|
||||||
local TSHighlighter = vim.treesitter.TSHighlighter
|
local highlighter = vim.treesitter.highlighter
|
||||||
local query = ...
|
local query = ...
|
||||||
test_hl = TSHighlighter.new(query, 0, "c")
|
test_hl = highlighter.new(query, 0, "c")
|
||||||
]], hl_query)
|
]], hl_query)
|
||||||
screen:expect{grid=[[
|
screen:expect{grid=[[
|
||||||
{2:/// Schedule Lua callback on main loop's event queue} |
|
{2:/// Schedule Lua callback on main loop's event queue} |
|
||||||
{3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
||||||
{ |
|
{ |
|
||||||
{4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
|
{4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
|
||||||
|| {6:lstate} != {6:lstate}) { |
|
|| {6:lstate} != {6:lstate}) { |
|
||||||
@ -306,9 +341,9 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
{4:return} {11:lua_error}(lstate); |
|
{4:return} {11:lua_error}(lstate); |
|
||||||
} |
|
} |
|
||||||
|
|
|
|
||||||
{7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
||||||
|
|
|
|
||||||
multiqueue_put(main_loop.events, nlua_schedule_event, |
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
||||||
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
||||||
{4:return} {5:0}; |
|
{4:return} {5:0}; |
|
||||||
^} |
|
^} |
|
||||||
@ -320,7 +355,7 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
feed('7Go*/<esc>')
|
feed('7Go*/<esc>')
|
||||||
screen:expect{grid=[[
|
screen:expect{grid=[[
|
||||||
{2:/// Schedule Lua callback on main loop's event queue} |
|
{2:/// Schedule Lua callback on main loop's event queue} |
|
||||||
{3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
||||||
{ |
|
{ |
|
||||||
{4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
|
{4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
|
||||||
|| {6:lstate} != {6:lstate}) { |
|
|| {6:lstate} != {6:lstate}) { |
|
||||||
@ -329,9 +364,9 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
{8:*^/} |
|
{8:*^/} |
|
||||||
} |
|
} |
|
||||||
|
|
|
|
||||||
{7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
||||||
|
|
|
|
||||||
multiqueue_put(main_loop.events, nlua_schedule_event, |
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
||||||
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
||||||
{4:return} {5:0}; |
|
{4:return} {5:0}; |
|
||||||
} |
|
} |
|
||||||
@ -342,7 +377,7 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
feed('3Go/*<esc>')
|
feed('3Go/*<esc>')
|
||||||
screen:expect{grid=[[
|
screen:expect{grid=[[
|
||||||
{2:/// Schedule Lua callback on main loop's event queue} |
|
{2:/// Schedule Lua callback on main loop's event queue} |
|
||||||
{3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
||||||
{ |
|
{ |
|
||||||
{2:/^*} |
|
{2:/^*} |
|
||||||
{2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
|
{2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
|
||||||
@ -352,9 +387,9 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
{2:*/} |
|
{2:*/} |
|
||||||
} |
|
} |
|
||||||
|
|
|
|
||||||
{7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
||||||
|
|
|
|
||||||
multiqueue_put(main_loop.events, nlua_schedule_event, |
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
||||||
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
||||||
{4:return} {5:0}; |
|
{4:return} {5:0}; |
|
||||||
{8:}} |
|
{8:}} |
|
||||||
@ -365,7 +400,7 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
feed("~")
|
feed("~")
|
||||||
screen:expect{grid=[[
|
screen:expect{grid=[[
|
||||||
{2:/// Schedule Lua callback on main loop's event queu^E} |
|
{2:/// Schedule Lua callback on main loop's event queu^E} |
|
||||||
{3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
||||||
{ |
|
{ |
|
||||||
{2:/*} |
|
{2:/*} |
|
||||||
{2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
|
{2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
|
||||||
@ -375,9 +410,9 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
{2:*/} |
|
{2:*/} |
|
||||||
} |
|
} |
|
||||||
|
|
|
|
||||||
{7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
||||||
|
|
|
|
||||||
multiqueue_put(main_loop.events, nlua_schedule_event, |
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
||||||
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
||||||
{4:return} {5:0}; |
|
{4:return} {5:0}; |
|
||||||
{8:}} |
|
{8:}} |
|
||||||
@ -388,7 +423,7 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
feed("re")
|
feed("re")
|
||||||
screen:expect{grid=[[
|
screen:expect{grid=[[
|
||||||
{2:/// Schedule Lua callback on main loop's event queu^e} |
|
{2:/// Schedule Lua callback on main loop's event queu^e} |
|
||||||
{3:static} {3:int} nlua_schedule({3:lua_State} *{3:const} lstate) |
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
||||||
{ |
|
{ |
|
||||||
{2:/*} |
|
{2:/*} |
|
||||||
{2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
|
{2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
|
||||||
@ -398,9 +433,9 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
{2:*/} |
|
{2:*/} |
|
||||||
} |
|
} |
|
||||||
|
|
|
|
||||||
{7:LuaRef} cb = nlua_ref(lstate, {5:1}); |
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
||||||
|
|
|
|
||||||
multiqueue_put(main_loop.events, nlua_schedule_event, |
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
||||||
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
||||||
{4:return} {5:0}; |
|
{4:return} {5:0}; |
|
||||||
{8:}} |
|
{8:}} |
|
||||||
|
Loading…
Reference in New Issue
Block a user