aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter.lua
diff options
context:
space:
mode:
authorGregory Anders <8965202+gpanders@users.noreply.github.com>2022-12-08 09:22:57 -0700
committerGitHub <noreply@github.com>2022-12-08 09:22:57 -0700
commitd44699800cd0dbf14fb45476c13b6cc3c993b5c7 (patch)
tree378964d4739148359b3e78133fd8dc97d866cecb /runtime/lua/vim/treesitter.lua
parent35767769036671d5ce562f53cae574f9c66e4bb2 (diff)
downloadrneovim-d44699800cd0dbf14fb45476c13b6cc3c993b5c7.tar.gz
rneovim-d44699800cd0dbf14fb45476c13b6cc3c993b5c7.tar.bz2
rneovim-d44699800cd0dbf14fb45476c13b6cc3c993b5c7.zip
feat(treesitter): add vim.treesitter.show_tree() (#21322)
Add a "show_tree" function to view a textual representation of the nodes in a language tree in a window. Moving the cursor in the window highlights the corresponding text in the source buffer, and moving the cursor in the source buffer highlights the corresponding nodes in the window.
Diffstat (limited to 'runtime/lua/vim/treesitter.lua')
-rw-r--r--runtime/lua/vim/treesitter.lua195
1 files changed, 194 insertions, 1 deletions
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 0603ddb421..5c9210cc2d 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -277,7 +277,7 @@ end
---@param opts table Optional keyword arguments:
--- - 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()
@@ -347,4 +347,197 @@ function M.stop(bufnr)
vim.bo[bufnr].syntax = 'on'
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:
+--- - 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 },
+ })
+
+ 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 = opts or {}
+
+ -- 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', '".*"')
+
+ 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], { 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))
+ 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