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.lua210
1 files changed, 201 insertions, 9 deletions
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 6cd00516bf..582922ecb6 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -240,7 +240,7 @@ function M.get_captures_at_pos(bufnr, row, col)
if M.is_in_node_range(node, row, col) then
local c = q._query.captures[capture] -- name of the capture in the query
if c ~= nil then
- table.insert(matches, { capture = c, metadata = metadata })
+ table.insert(matches, { capture = c, metadata = metadata, lang = tree:lang() })
end
end
end
@@ -275,16 +275,17 @@ end
---@param row number Position row
---@param col number Position column
---@param opts table Optional keyword arguments:
+--- - lang string|nil Parser language
--- - ignore_injections boolean Ignore injected languages (default true)
---
----@return userdata |tsnode| under the cursor
+---@return userdata|nil |tsnode| under the cursor
function M.get_node_at_pos(bufnr, row, col, opts)
if bufnr == 0 then
bufnr = a.nvim_get_current_buf()
end
local ts_range = { row, col, row, col }
- local root_lang_tree = M.get_parser(bufnr)
+ local root_lang_tree = M.get_parser(bufnr, opts.lang)
if not root_lang_tree then
return
end
@@ -313,7 +314,7 @@ end
--- In this case, add ``vim.bo.syntax = 'on'`` after the call to `start`.
---
--- Example:
---- <pre>
+--- <pre>lua
--- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex',
--- callback = function(args)
--- vim.treesitter.start(args.buf, 'latex')
@@ -326,12 +327,8 @@ end
---@param lang (string|nil) Language of the parser (default: buffer filetype)
function M.start(bufnr, lang)
bufnr = bufnr or a.nvim_get_current_buf()
-
local parser = M.get_parser(bufnr, lang)
-
M.highlighter.new(parser)
-
- vim.b[bufnr].ts_highlight = true
end
--- Stops treesitter highlighting for a buffer
@@ -343,8 +340,203 @@ function M.stop(bufnr)
if M.highlighter.active[bufnr] then
M.highlighter.active[bufnr]:destroy()
end
+end
+
+--- Open a window that displays a textual representation of the nodes in the language tree.
+---
+--- While in the window, press "a" to toggle display of anonymous nodes, "I" to toggle the
+--- display of the source language of each node, and press <Enter> to jump to the node under the
+--- cursor in the source buffer.
+---
+---@param opts table|nil Optional options table with the following possible keys:
+--- - lang (string|nil): The language of the source buffer. If omitted, the
+--- filetype of the source buffer is used.
+--- - bufnr (number|nil): Buffer to draw the tree into. If omitted, a new
+--- buffer is created.
+--- - winid (number|nil): Window id to display the tree buffer in. If omitted,
+--- a new window is created with {command}.
+--- - command (string|nil): Vimscript command to create the window. Default
+--- value is "topleft 60vnew". Only used when {winid} is nil.
+--- - title (string|fun(bufnr:number):string|nil): Title of the window. If a
+--- function, it accepts the buffer number of the source buffer as its only
+--- argument and should return a string.
+function M.show_tree(opts)
+ vim.validate({
+ opts = { opts, 't', true },
+ })
+
+ opts = opts or {}
+
+ local Playground = require('vim.treesitter.playground')
+ local buf = a.nvim_get_current_buf()
+ local win = a.nvim_get_current_win()
+ local pg = assert(Playground:new(buf, opts.lang))
+
+ -- Close any existing playground window
+ if vim.b[buf].playground then
+ local w = vim.b[buf].playground
+ if a.nvim_win_is_valid(w) then
+ a.nvim_win_close(w, true)
+ end
+ end
+
+ local w = opts.winid
+ if not w then
+ vim.cmd(opts.command or 'topleft 60vnew')
+ w = a.nvim_get_current_win()
+ end
+
+ local b = opts.bufnr
+ if b then
+ a.nvim_win_set_buf(w, b)
+ else
+ b = a.nvim_win_get_buf(w)
+ end
+
+ vim.b[buf].playground = w
+
+ vim.wo[w].scrolloff = 5
+ vim.wo[w].wrap = false
+ vim.bo[b].buflisted = false
+ vim.bo[b].buftype = 'nofile'
+ vim.bo[b].bufhidden = 'wipe'
+
+ local title = opts.title
+ if not title then
+ local bufname = a.nvim_buf_get_name(buf)
+ title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.'))
+ elseif type(title) == 'function' then
+ title = title(buf)
+ end
+
+ assert(type(title) == 'string', 'Window title must be a string')
+ a.nvim_buf_set_name(b, title)
+
+ pg:draw(b)
+
+ vim.fn.matchadd('Comment', '\\[[0-9:-]\\+\\]')
+ vim.fn.matchadd('String', '".*"')
- vim.bo[bufnr].syntax = 'on'
+ a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1)
+ a.nvim_buf_set_keymap(b, 'n', '<CR>', '', {
+ desc = 'Jump to the node under the cursor in the source buffer',
+ callback = function()
+ local row = a.nvim_win_get_cursor(w)[1]
+ local pos = pg:get(row)
+ a.nvim_set_current_win(win)
+ a.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col })
+ end,
+ })
+ a.nvim_buf_set_keymap(b, 'n', 'a', '', {
+ desc = 'Toggle anonymous nodes',
+ callback = function()
+ pg.opts.anon = not pg.opts.anon
+ pg:draw(b)
+ end,
+ })
+ a.nvim_buf_set_keymap(b, 'n', 'I', '', {
+ desc = 'Toggle language display',
+ callback = function()
+ pg.opts.lang = not pg.opts.lang
+ pg:draw(b)
+ end,
+ })
+
+ local group = a.nvim_create_augroup('treesitter/playground', {})
+
+ a.nvim_create_autocmd('CursorMoved', {
+ group = group,
+ buffer = b,
+ callback = function()
+ a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1)
+ local row = a.nvim_win_get_cursor(w)[1]
+ local pos = pg:get(row)
+ a.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, {
+ end_row = pos.end_lnum,
+ end_col = math.max(0, pos.end_col),
+ hl_group = 'Visual',
+ })
+ end,
+ })
+
+ a.nvim_create_autocmd('CursorMoved', {
+ group = group,
+ buffer = buf,
+ callback = function()
+ if not a.nvim_buf_is_loaded(b) then
+ return true
+ end
+
+ a.nvim_buf_clear_namespace(b, pg.ns, 0, -1)
+
+ local cursor = a.nvim_win_get_cursor(win)
+ local cursor_node = M.get_node_at_pos(buf, cursor[1] - 1, cursor[2], {
+ lang = opts.lang,
+ ignore_injections = false,
+ })
+ if not cursor_node then
+ return
+ end
+
+ local cursor_node_id = cursor_node:id()
+ for i, v in pg:iter() do
+ if v.id == cursor_node_id then
+ local start = v.depth
+ local end_col = start + #v.text
+ a.nvim_buf_set_extmark(b, pg.ns, i - 1, start, {
+ end_col = end_col,
+ hl_group = 'Visual',
+ })
+ a.nvim_win_set_cursor(w, { i, 0 })
+ break
+ end
+ end
+ end,
+ })
+
+ a.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, {
+ group = group,
+ buffer = buf,
+ callback = function()
+ if not a.nvim_buf_is_loaded(b) then
+ return true
+ end
+
+ pg = assert(Playground:new(buf, opts.lang))
+ pg:draw(b)
+ end,
+ })
+
+ a.nvim_create_autocmd('BufLeave', {
+ group = group,
+ buffer = b,
+ callback = function()
+ a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1)
+ end,
+ })
+
+ a.nvim_create_autocmd('BufLeave', {
+ group = group,
+ buffer = buf,
+ callback = function()
+ if not a.nvim_buf_is_loaded(b) then
+ return true
+ end
+
+ a.nvim_buf_clear_namespace(b, pg.ns, 0, -1)
+ end,
+ })
+
+ a.nvim_create_autocmd('BufHidden', {
+ group = group,
+ buffer = buf,
+ once = true,
+ callback = function()
+ if a.nvim_win_is_valid(w) then
+ a.nvim_win_close(w, true)
+ end
+ end,
+ })
end
return M