aboutsummaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/doc/if_lua.txt106
-rw-r--r--runtime/lua/vim/treesitter.lua73
2 files changed, 179 insertions, 0 deletions
diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt
index aa2d0a03c6..0ba35aeae6 100644
--- a/runtime/doc/if_lua.txt
+++ b/runtime/doc/if_lua.txt
@@ -446,6 +446,112 @@ Example: TCP echo-server *tcp-server*
print('TCP echo-server listening on port: '..server:getsockname().port)
------------------------------------------------------------------------------
+VIM.TREESITTER *lua-treesitter*
+
+Nvim integrates the tree-sitter library for incremental parsing of buffers.
+
+Currently Nvim does not provide the tree-sitter parsers, instead these must
+be built separately, for instance using the tree-sitter utility.
+The parser is loaded into nvim using >
+
+ vim.treesitter.add_language("/path/to/c_parser.so", "c")
+
+<Create a parser for a buffer and a given language (if another plugin uses the
+same buffer/language combination, it will be safely reused). Use >
+
+ parser = vim.treesitter.get_parser(bufnr, lang)
+
+<`bufnr=0` can be used for current buffer. `lang` will default to 'filetype' (this
+doesn't work yet for some filetypes like "cpp") Currently, the parser will be
+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
+updates.
+
+Whenever you need to access the current syntax tree, parse the buffer: >
+
+ tstree = parser:parse()
+
+<This will return an immutable tree that represents the current state of the
+buffer. When the plugin wants to access the state after a (possible) edit
+it should call `parse()` again. If the buffer wasn't edited, the same tree will
+be returned again without extra work. If the buffer was parsed before,
+incremental parsing will be done of the changed parts.
+
+NB: to use the parser directly inside a |nvim_buf_attach| lua callback, you must
+call `get_parser()` before you register your callback. But preferably parsing
+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
+a timer to throttle too frequent updates.
+
+Tree methods *lua-treesitter-tree*
+
+tstree:root() *tstree:root()*
+ Return the root node of this tree.
+
+
+Node methods *lua-treesitter-node*
+
+tsnode:parent() *tsnode:parent()*
+ Get the node's immediate parent.
+
+tsnode:child_count() *tsnode:child_count()*
+ Get the node's number of children.
+
+tsnode:child(N) *tsnode:child()*
+ Get the node's child at the given index, where zero represents the
+ first child.
+
+tsnode:named_child_count() *tsnode:named_child_count()*
+ Get the node's number of named children.
+
+tsnode:named_child(N) *tsnode:named_child()*
+ Get the node's named child at the given index, where zero represents
+ the first named child.
+
+tsnode:start() *tsnode:start()*
+ Get the node's start position. Return three values: the row, column
+ and total byte count (all zero-based).
+
+tsnode:end_() *tsnode:end_()*
+ Get the node's end position. Return three values: the row, column
+ and total byte count (all zero-based).
+
+tsnode:range() *tsnode:range()*
+ Get the range of the node. Return four values: the row, column
+ of the start position, then the row, column of the end position.
+
+tsnode:type() *tsnode:type()*
+ Get the node's type as a string.
+
+tsnode:symbol() *tsnode:symbol()*
+ Get the node's type as a numerical id.
+
+tsnode:named() *tsnode:named()*
+ Check if the node is named. Named nodes correspond to named rules in
+ the grammar, whereas anonymous nodes correspond to string literals
+ in the grammar.
+
+tsnode:missing() *tsnode:missing()*
+ Check if the node is missing. Missing nodes are inserted by the
+ parser in order to recover from certain kinds of syntax errors.
+
+tsnode:has_error() *tsnode:has_error()*
+ Check if the node is a syntax error or contains any syntax errors.
+
+tsnode:sexpr() *tsnode:sexpr()*
+ 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()*
+ Get the smallest node within this node that spans the given range of
+ (row, column) positions
+
+tsnode:named_descendant_for_range(start_row, start_col, end_row, end_col)
+ *tsnode:named_descendant_for_range()*
+ Get the smallest named node within this node that spans the given
+ range of (row, column) positions
+
+------------------------------------------------------------------------------
VIM *lua-util*
vim.in_fast_event() *vim.in_fast_event()*
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
new file mode 100644
index 0000000000..e0202927bb
--- /dev/null
+++ b/runtime/lua/vim/treesitter.lua
@@ -0,0 +1,73 @@
+local a = vim.api
+
+-- 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
+-- it.
+local parsers = {}
+
+local Parser = {}
+Parser.__index = Parser
+
+function Parser:parse()
+ if self.valid then
+ return self.tree
+ end
+ self.tree = self._parser:parse_buf(self.bufnr)
+ self.valid = true
+ return self.tree
+end
+
+function Parser:_on_lines(bufnr, _, 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)
+ self.valid = false
+end
+
+local module = {
+ add_language=vim._ts_add_language,
+ inspect_language=vim._ts_inspect_language,
+}
+
+function module.create_parser(bufnr, ft, id)
+ if bufnr == 0 then
+ bufnr = a.nvim_get_current_buf()
+ end
+ local self = setmetatable({bufnr=bufnr, valid=false}, Parser)
+ self._parser = vim._create_ts_parser(ft)
+ 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(...)
+ end
+ local detach_cb = nil
+ if id ~= nil then
+ detach_cb = function()
+ if parsers[id] == self then
+ parsers[id] = nil
+ end
+ end
+ end
+ a.nvim_buf_attach(self.bufnr, false, {on_lines=lines_cb, on_detach=detach_cb})
+ return self
+end
+
+function module.get_parser(bufnr, ft)
+ 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")
+ end
+ local id = tostring(bufnr)..'_'..ft
+
+ if parsers[id] == nil then
+ parsers[id] = module.create_parser(bufnr, ft, id)
+ end
+ return parsers[id]
+end
+
+return module