dev.lua (22838B)
1 local api = vim.api 2 3 local Range = require('vim.treesitter._range') 4 5 local M = {} 6 7 ---@class (private) vim.treesitter.dev.TSTreeView 8 ---@field ns integer API namespace 9 ---@field opts vim.treesitter.dev.TSTreeViewOpts 10 ---@field nodes vim.treesitter.dev.Node[] 11 ---@field named vim.treesitter.dev.Node[] 12 local TSTreeView = {} 13 14 ---@private 15 ---@class (private) vim.treesitter.dev.TSTreeViewOpts 16 ---@field anon boolean If true, display anonymous nodes. 17 ---@field lang boolean If true, display the language alongside each node. 18 ---@field indent integer Number of spaces to indent nested lines. 19 20 ---@class (private) vim.treesitter.dev.Node 21 ---@field node TSNode Treesitter node 22 ---@field field string? Node field 23 ---@field depth integer Depth of this node in the tree 24 ---@field text string? Text displayed in the inspector for this node. Not computed until the 25 --- inspector is drawn. 26 ---@field lang string Source language of this node 27 28 ---@class (private) vim.treesitter.dev.Injection 29 ---@field lang string Source language of this injection 30 ---@field root TSNode Root node of the injection 31 32 --- Traverse all child nodes starting at {node}. 33 --- 34 --- This is a recursive function. The {depth} parameter indicates the current recursion level. 35 --- {lang} is a string indicating the language of the tree currently being traversed. Each traversed 36 --- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order 37 --- they were visited. 38 --- 39 --- {injections} is a table mapping node ids from the primary tree to language tree injections. Each 40 --- injected language has a series of trees nested within the primary language's tree, and the root 41 --- node of each of these trees is contained within a node in the primary tree. The {injections} 42 --- table maps nodes in the primary tree to root nodes of injected trees. 43 --- 44 ---@param node TSNode Starting node to begin traversal |tsnode| 45 ---@param depth integer Current recursion depth 46 ---@param field string|nil The field of the current node 47 ---@param lang string Language of the tree currently being traversed 48 ---@param injections table<string, vim.treesitter.dev.Injection[]> Mapping of node ids to root nodes 49 --- of injected language trees (see explanation above) 50 ---@param tree vim.treesitter.dev.Node[] Output table containing a list of tables each representing a node in the tree 51 local function traverse(node, depth, field, lang, injections, tree) 52 table.insert(tree, { 53 node = node, 54 depth = depth, 55 lang = lang, 56 field = field, 57 }) 58 59 for _, injection in ipairs(injections[node:id()] or {}) do 60 traverse(injection.root, depth + 1, nil, injection.lang, injections, tree) 61 end 62 63 for child, child_field in node:iter_children() do 64 traverse(child, depth + 1, child_field, lang, injections, tree) 65 end 66 67 return tree 68 end 69 70 --- Create a new treesitter view. 71 --- 72 ---@param bufnr integer Source buffer number 73 ---@param lang string|nil Language of source buffer 74 --- 75 ---@return vim.treesitter.dev.TSTreeView|nil 76 ---@return string|nil Error message, if any 77 --- 78 ---@package 79 function TSTreeView:new(bufnr, lang) 80 bufnr = bufnr or 0 81 lang = lang or vim.treesitter.language.get_lang(vim.bo[bufnr].filetype) 82 local parser = vim.treesitter.get_parser(bufnr, lang, { error = false }) 83 if not parser then 84 return nil, 85 string.format( 86 'Failed to create TSTreeView for buffer %s: no parser for lang "%s"', 87 bufnr, 88 lang 89 ) 90 end 91 92 -- For each child tree (injected language), find the root of the tree and locate the node within 93 -- the primary tree that contains that root. Add a mapping from the node in the primary tree to 94 -- the root in the child tree to the {injections} table. 95 local root = parser:parse(true)[1]:root() 96 local injections = {} ---@type table<string, table<string, TSNode>> 97 98 parser:for_each_tree(function(parent_tree, parent_ltree) 99 local parent = parent_tree:root() 100 local parent_range = { parent:range() } 101 for _, child in pairs(parent_ltree:children()) do 102 for _, tree in pairs(child:trees()) do 103 local r = tree:root() 104 local r_range = { r:range() } 105 if Range.contains(parent_range, r_range) then 106 local node = assert(parent:named_descendant_for_range(r:range())) 107 local id = node:id() 108 local ilang = child:lang() 109 injections[id] = injections[id] or {} 110 local injection = injections[id][ilang] 111 if not injection or r:byte_length() > injection:byte_length() then 112 injections[id][ilang] = r 113 end 114 end 115 end 116 end 117 end) 118 119 local sorted_injections = {} ---@type table<string, vim.treesitter.dev.Injection[]> 120 for id, lang_injections in pairs(injections) do 121 local langs = vim.tbl_keys(lang_injections) 122 ---@param a string 123 ---@param b string 124 table.sort(langs, function(a, b) 125 return lang_injections[a]:byte_length() > lang_injections[b]:byte_length() 126 end) 127 ---@param ilang string 128 sorted_injections[id] = vim.tbl_map(function(ilang) 129 return { lang = ilang, root = lang_injections[ilang] } 130 end, langs) 131 end 132 133 local nodes = traverse(root, 0, nil, parser:lang(), sorted_injections, {}) 134 135 local named = {} ---@type vim.treesitter.dev.Node[] 136 for _, v in ipairs(nodes) do 137 if v.node:named() then 138 named[#named + 1] = v 139 end 140 end 141 142 local t = { 143 ns = api.nvim_create_namespace('nvim.treesitter.dev_inspect'), 144 nodes = nodes, 145 named = named, 146 ---@type vim.treesitter.dev.TSTreeViewOpts 147 opts = { 148 anon = false, 149 lang = false, 150 indent = 2, 151 }, 152 } 153 154 setmetatable(t, self) 155 self.__index = self 156 return t 157 end 158 159 local decor_ns = api.nvim_create_namespace('nvim.treesitter.dev') 160 161 ---@param w integer 162 ---@return boolean closed Whether the window was closed. 163 local function close_win(w) 164 if api.nvim_win_is_valid(w) then 165 api.nvim_win_close(w, true) 166 return true 167 end 168 169 return false 170 end 171 172 ---@param w integer 173 ---@param b integer 174 ---@param opts nil|{ indent?: integer } 175 local function set_dev_options(w, b, opts) 176 vim.wo[w][0].scrolloff = 5 177 vim.wo[w][0].wrap = false 178 vim.wo[w][0].foldmethod = 'expr' 179 vim.wo[w][0].foldexpr = 'v:lua.vim.treesitter.foldexpr()' -- explicitly set foldexpr 180 vim.wo[w][0].foldenable = false -- Don't fold on first open InspectTree 181 vim.wo[w][0].foldlevel = 99 182 vim.bo[b].buflisted = false 183 vim.bo[b].buftype = 'nofile' 184 vim.bo[b].bufhidden = 'wipe' 185 vim.bo[b].filetype = 'query' 186 vim.bo[b].swapfile = false 187 188 opts = opts or {} 189 if opts.indent then 190 vim.bo[b].shiftwidth = opts.indent 191 end 192 end 193 194 --- Updates the cursor position in the inspector to match the node under the cursor. 195 --- 196 --- @param treeview vim.treesitter.dev.TSTreeView 197 --- @param lang string 198 --- @param source_buf integer 199 --- @param inspect_buf integer 200 --- @param inspect_win integer 201 --- @param pos? [integer, integer] 202 local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, inspect_win, pos) 203 api.nvim_buf_clear_namespace(inspect_buf, treeview.ns, 0, -1) 204 205 local cursor_node = vim.treesitter.get_node({ 206 bufnr = source_buf, 207 lang = lang, 208 pos = pos, 209 ignore_injections = false, 210 include_anonymous = treeview.opts.anon, 211 }) 212 if not cursor_node then 213 return 214 end 215 216 local cursor_node_id = cursor_node:id() 217 for i, v in treeview:iter() do 218 if v.node:id() == cursor_node_id then 219 local start = v.depth * treeview.opts.indent ---@type integer 220 local end_col = start + #v.text 221 api.nvim_buf_set_extmark(inspect_buf, treeview.ns, i - 1, start, { 222 end_col = end_col, 223 hl_group = 'Visual', 224 }) 225 api.nvim_win_set_cursor(inspect_win, { i, 0 }) 226 break 227 end 228 end 229 end 230 231 --- Write the contents of this View into {bufnr}. 232 --- 233 --- Calling this function computes the text that is displayed for each node. 234 --- 235 ---@param bufnr integer Buffer number to write into. 236 ---@package 237 function TSTreeView:draw(bufnr) 238 vim.bo[bufnr].modifiable = true 239 local lines = {} ---@type string[] 240 local lang_hl_marks = {} ---@type table[] 241 242 for i, item in self:iter() do 243 local range_str = ('[%d, %d] - [%d, %d]'):format(item.node:range()) 244 local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' 245 246 local text ---@type string 247 if item.node:named() then 248 text = string.format('(%s%s', item.node:missing() and 'MISSING ' or '', item.node:type()) 249 else 250 text = string.format('%q', item.node:type()):gsub('\n', 'n') 251 if item.node:missing() then 252 text = string.format('(MISSING %s)', text) 253 end 254 end 255 if item.field then 256 text = string.format('%s: %s', item.field, text) 257 end 258 259 local next = self:get(i + 1) 260 if not next or next.depth <= item.depth then 261 local parens = item.depth - (next and next.depth or 0) + (item.node:named() and 1 or 0) 262 if parens > 0 then 263 text = string.format('%s%s', text, string.rep(')', parens)) 264 end 265 end 266 267 item.text = text 268 269 local line = string.format( 270 '%s%s ; %s%s', 271 string.rep(' ', item.depth * self.opts.indent), 272 text, 273 range_str, 274 lang_str 275 ) 276 277 if self.opts.lang then 278 lang_hl_marks[#lang_hl_marks + 1] = { 279 col = #line - #lang_str, 280 end_col = #line, 281 } 282 end 283 284 lines[i] = line 285 end 286 287 api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) 288 289 api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1) 290 291 for i, m in ipairs(lang_hl_marks) do 292 api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, m.col, { 293 hl_group = 'Title', 294 end_col = m.end_col, 295 }) 296 end 297 298 vim.bo[bufnr].modifiable = false 299 end 300 301 --- Get node {i} from this View. 302 --- 303 --- The node number is dependent on whether or not anonymous nodes are displayed. 304 --- 305 ---@param i integer Node number to get 306 ---@return vim.treesitter.dev.Node? 307 ---@package 308 function TSTreeView:get(i) 309 local t = self.opts.anon and self.nodes or self.named 310 return t[i] 311 end 312 313 --- Iterate over all of the nodes in this View. 314 --- 315 ---@return (fun(): integer, vim.treesitter.dev.Node) Iterator over all nodes in this View 316 ---@return table 317 ---@return integer 318 ---@package 319 function TSTreeView:iter() 320 return ipairs(self.opts.anon and self.nodes or self.named) 321 end 322 323 --- @class vim.treesitter.dev.inspect_tree.Opts 324 --- @inlinedoc 325 --- 326 --- The language of the source buffer. If omitted, the filetype of the source 327 --- buffer is used. 328 --- @field lang string? 329 --- 330 --- Buffer to draw the tree into. If omitted, a new buffer is created. 331 --- @field bufnr integer? 332 --- 333 --- Window id to display the tree buffer in. If omitted, a new window is 334 --- created with {command}. 335 --- @field winid integer? 336 --- 337 --- Vimscript command to create the window. Default value is "60vnew". 338 --- Only used when {winid} is nil. 339 --- @field command string? 340 --- 341 --- Title of the window. If a function, it accepts the buffer number of the 342 --- source buffer as its only argument and should return a string. 343 --- @field title (string|fun(bufnr:integer):string|nil) 344 345 --- @nodoc 346 --- @param opts vim.treesitter.dev.inspect_tree.Opts? 347 function M.inspect_tree(opts) 348 vim.validate('opts', opts, 'table', true) 349 350 opts = opts or {} 351 352 -- source buffer 353 local buf = api.nvim_get_current_buf() 354 355 -- window id for source buffer 356 local win = api.nvim_get_current_win() 357 local treeview, err = TSTreeView:new(buf, opts.lang) 358 if err and err:match('no parser for lang') then 359 api.nvim_echo({ { err, 'WarningMsg' } }, true, {}) 360 return 361 elseif not treeview then 362 error(err) 363 end 364 365 -- Close any existing inspector window 366 if vim.b[buf].dev_inspect then 367 close_win(vim.b[buf].dev_inspect) 368 end 369 370 -- window id for tree buffer 371 local w = opts.winid 372 if not w then 373 vim.cmd(opts.command or '60vnew') 374 w = api.nvim_get_current_win() 375 end 376 377 -- tree buffer 378 local b = opts.bufnr 379 if b then 380 api.nvim_win_set_buf(w, b) 381 else 382 b = api.nvim_win_get_buf(w) 383 end 384 385 vim.b[buf].dev_inspect = w 386 vim.b[b].dev_base = win -- base window handle 387 vim.b[b].disable_query_linter = true 388 set_dev_options(w, b, { indent = treeview.opts.indent }) 389 390 local title --- @type string? 391 local opts_title = opts.title 392 if not opts_title then 393 local bufname = api.nvim_buf_get_name(buf) 394 title = ('Syntax tree for %s'):format(vim.fs.relpath('.', bufname) or bufname) 395 elseif type(opts_title) == 'function' then 396 title = opts_title(buf) 397 end 398 399 assert(type(title) == 'string', 'Window title must be a string') 400 api.nvim_buf_set_name(b, title) 401 402 treeview:draw(b) 403 404 local cursor = api.nvim_win_get_cursor(win) 405 set_inspector_cursor(treeview, opts.lang, buf, b, w, { cursor[1] - 1, cursor[2] }) 406 407 api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) 408 api.nvim_buf_set_keymap(b, 'n', '<CR>', '', { 409 desc = 'Jump to the node under the cursor in the source buffer', 410 nowait = true, 411 callback = function() 412 local row = api.nvim_win_get_cursor(w)[1] 413 local lnum, col = treeview:get(row).node:start() 414 415 -- update source window if original was closed 416 if not api.nvim_win_is_valid(win) then 417 win = assert(vim.fn.win_findbuf(buf)[1]) 418 end 419 420 api.nvim_set_current_win(win) 421 api.nvim_win_set_cursor(win, { lnum + 1, col }) 422 end, 423 }) 424 api.nvim_buf_set_keymap(b, 'n', 'a', '', { 425 desc = 'Toggle anonymous nodes', 426 nowait = true, 427 callback = function() 428 local row, col = unpack(api.nvim_win_get_cursor(w)) ---@type integer, integer 429 local curnode = treeview:get(row) 430 while curnode and not curnode.node:named() do 431 row = row - 1 432 curnode = treeview:get(row) 433 end 434 435 treeview.opts.anon = not treeview.opts.anon 436 treeview:draw(b) 437 438 if not curnode then 439 return 440 end 441 442 local id = curnode.node:id() 443 for i, node in treeview:iter() do 444 if node.node:id() == id then 445 api.nvim_win_set_cursor(w, { i, col }) 446 break 447 end 448 end 449 end, 450 }) 451 api.nvim_buf_set_keymap(b, 'n', 'I', '', { 452 desc = 'Toggle language display', 453 nowait = true, 454 callback = function() 455 treeview.opts.lang = not treeview.opts.lang 456 treeview:draw(b) 457 end, 458 }) 459 api.nvim_buf_set_keymap(b, 'n', 'o', '', { 460 desc = 'Toggle query editor', 461 nowait = true, 462 callback = function() 463 local edit_w = vim.b[buf].dev_edit 464 if not edit_w or not close_win(edit_w) then 465 M.edit_query() 466 end 467 end, 468 }) 469 api.nvim_buf_set_keymap(b, 'n', 'q', '<Cmd>wincmd c<CR>', { 470 desc = 'Close language tree window', 471 nowait = true, 472 }) 473 474 local group = api.nvim_create_augroup('nvim.treesitter.dev', {}) 475 476 api.nvim_create_autocmd('CursorMoved', { 477 group = group, 478 buffer = b, 479 callback = function() 480 if not api.nvim_buf_is_loaded(buf) then 481 return true 482 end 483 484 w = api.nvim_get_current_win() 485 api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) 486 local row = api.nvim_win_get_cursor(w)[1] 487 local lnum, col, end_lnum, end_col = treeview:get(row).node:range() 488 api.nvim_buf_set_extmark(buf, treeview.ns, lnum, col, { 489 end_row = end_lnum, 490 end_col = math.max(0, end_col), 491 hl_group = 'Visual', 492 }) 493 494 -- update source window if original was closed 495 if not api.nvim_win_is_valid(win) then 496 win = assert(vim.fn.win_findbuf(buf)[1]) 497 end 498 499 local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) 500 501 -- Move the cursor if highlighted range is completely out of view 502 if lnum < topline and end_lnum < topline then 503 api.nvim_win_set_cursor(win, { end_lnum + 1, 0 }) 504 elseif lnum > botline and end_lnum > botline then 505 api.nvim_win_set_cursor(win, { lnum + 1, 0 }) 506 end 507 end, 508 }) 509 510 api.nvim_create_autocmd('CursorMoved', { 511 group = group, 512 buffer = buf, 513 callback = function() 514 if not api.nvim_buf_is_loaded(b) then 515 return true 516 end 517 518 set_inspector_cursor(treeview, opts.lang, buf, b, w) 519 end, 520 }) 521 522 api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { 523 group = group, 524 buffer = buf, 525 callback = function() 526 if not api.nvim_buf_is_loaded(b) then 527 return true 528 end 529 530 local treeview_opts = treeview.opts 531 treeview = assert(TSTreeView:new(buf, opts.lang)) 532 treeview.opts = treeview_opts 533 treeview:draw(b) 534 end, 535 }) 536 537 api.nvim_create_autocmd('BufLeave', { 538 group = group, 539 buffer = b, 540 callback = function() 541 if not api.nvim_buf_is_loaded(buf) then 542 return true 543 end 544 api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) 545 end, 546 }) 547 548 api.nvim_create_autocmd('BufLeave', { 549 group = group, 550 buffer = buf, 551 callback = function() 552 if not api.nvim_buf_is_loaded(b) then 553 return true 554 end 555 api.nvim_buf_clear_namespace(b, treeview.ns, 0, -1) 556 end, 557 }) 558 559 api.nvim_create_autocmd({ 'BufHidden', 'BufUnload', 'QuitPre' }, { 560 group = group, 561 buffer = buf, 562 callback = function() 563 -- don't close inpector window if source buffer 564 -- has more than one open window 565 if #vim.fn.win_findbuf(buf) > 1 then 566 return 567 end 568 569 -- close all tree windows 570 for _, window in pairs(vim.fn.win_findbuf(b)) do 571 close_win(window) 572 end 573 574 return true 575 end, 576 }) 577 end 578 579 local edit_ns = api.nvim_create_namespace('nvim.treesitter.dev_edit') 580 581 ---@param query_win integer 582 ---@param base_win integer 583 ---@param lang string 584 local function update_editor_highlights(query_win, base_win, lang) 585 local base_buf = api.nvim_win_get_buf(base_win) 586 local query_buf = api.nvim_win_get_buf(query_win) 587 local root_lang = vim.treesitter.language.get_lang(vim.bo[base_buf].filetype) 588 local parser = assert(vim.treesitter.get_parser(base_buf, root_lang, { error = false })) 589 api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1) 590 local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n') 591 592 local ok_query, query = pcall(vim.treesitter.query.parse, lang, query_content) 593 if not ok_query then 594 return 595 end 596 597 local cursor_word = vim.fn.expand('<cword>') --[[@as string]] 598 -- Only highlight captures if the cursor is on a capture name 599 if cursor_word:find('^@') == nil then 600 return 601 end 602 -- Remove the '@' from the cursor word 603 cursor_word = cursor_word:sub(2) 604 -- Parse buffer including injected languages. 605 parser:parse(true) 606 -- Query on the trees of the language requested to highlight captures. 607 parser:for_each_tree(function(tree, ltree) 608 if ltree:lang() ~= lang then 609 return 610 end 611 local root = tree:root() 612 local topline, botline = vim.fn.line('w0', base_win), vim.fn.line('w$', base_win) 613 for id, node, metadata in query:iter_captures(root, base_buf, topline - 1, botline) do 614 local capture_name = query.captures[id] 615 if capture_name == cursor_word then 616 local lnum, col, end_lnum, end_col = 617 Range.unpack4(vim.treesitter.get_range(node, base_buf, metadata[id])) 618 619 api.nvim_buf_set_extmark(base_buf, edit_ns, lnum, col, { 620 end_row = end_lnum, 621 end_col = end_col, 622 hl_group = 'Visual', 623 virt_text = { 624 { capture_name, 'DiagnosticVirtualTextHint' }, 625 }, 626 }) 627 end 628 end 629 end) 630 end 631 632 --- @nodoc 633 --- @param lang? string language to open the query editor for. 634 --- @return boolean? `true` on success, `nil` on failure 635 --- @return string? error message, if applicable 636 function M.edit_query(lang) 637 local buf = api.nvim_get_current_buf() 638 local win = api.nvim_get_current_win() 639 640 -- Close any existing editor window 641 if vim.b[buf].dev_edit then 642 close_win(vim.b[buf].dev_edit) 643 end 644 645 local cmd = '60vnew' 646 -- If the inspector is open, place the editor above it. 647 local base_win = vim.b[buf].dev_base ---@type integer? 648 local base_buf = base_win and api.nvim_win_get_buf(base_win) 649 local inspect_win = base_buf and vim.b[base_buf].dev_inspect 650 if base_win and base_buf and api.nvim_win_is_valid(inspect_win) then 651 api.nvim_set_current_win(inspect_win) 652 buf = base_buf 653 win = base_win 654 cmd = 'new' 655 end 656 vim.cmd(cmd) 657 658 local parser = vim.treesitter.get_parser(buf, lang, { error = false }) 659 if not parser then 660 return nil, 661 string.format('Failed to show query editor for buffer %s: no parser for lang "%s"', buf, lang) 662 end 663 lang = parser:lang() 664 665 local query_win = api.nvim_get_current_win() 666 local query_buf = api.nvim_win_get_buf(query_win) 667 668 vim.b[buf].dev_edit = query_win 669 vim.bo[query_buf].omnifunc = 'v:lua.vim.treesitter.query.omnifunc' 670 set_dev_options(query_win, query_buf) 671 672 -- Note that omnifunc guesses the language based on the containing folder, 673 -- so we add the parser's language to the buffer's name so that omnifunc 674 -- can infer the language later. 675 api.nvim_buf_set_name(query_buf, string.format('%s/query_editor.scm', lang)) 676 677 local group = api.nvim_create_augroup('nvim.treesitter.dev_edit', {}) 678 api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { 679 group = group, 680 buffer = query_buf, 681 desc = 'Update query editor diagnostics when the query changes', 682 callback = function() 683 vim.treesitter.query.lint(query_buf, { langs = lang, clear = false }) 684 end, 685 }) 686 api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave', 'CursorMoved', 'BufEnter' }, { 687 group = group, 688 buffer = query_buf, 689 desc = 'Update query editor highlights when the cursor moves', 690 callback = function() 691 if api.nvim_win_is_valid(win) then 692 update_editor_highlights(query_win, win, lang) 693 end 694 end, 695 }) 696 api.nvim_create_autocmd('BufLeave', { 697 group = group, 698 buffer = query_buf, 699 desc = 'Clear highlights when leaving the query editor', 700 callback = function() 701 api.nvim_buf_clear_namespace(buf, edit_ns, 0, -1) 702 end, 703 }) 704 api.nvim_create_autocmd('BufLeave', { 705 group = group, 706 buffer = buf, 707 desc = 'Clear the query editor highlights when leaving the source buffer', 708 callback = function() 709 if not api.nvim_buf_is_loaded(query_buf) then 710 return true 711 end 712 713 api.nvim_buf_clear_namespace(query_buf, edit_ns, 0, -1) 714 end, 715 }) 716 api.nvim_create_autocmd({ 'BufHidden', 'BufUnload' }, { 717 group = group, 718 buffer = buf, 719 desc = 'Close the editor window when the source buffer is hidden or unloaded', 720 once = true, 721 callback = function() 722 close_win(query_win) 723 end, 724 }) 725 726 api.nvim_buf_set_lines(query_buf, 0, -1, false, { 727 ';; Write queries here (see $VIMRUNTIME/queries/ for examples).', 728 ';; Move cursor to a capture ("@foo") to highlight matches in the source buffer.', 729 ';; Completion for grammar nodes is available (:help compl-omni)', 730 '', 731 '', 732 }) 733 vim.cmd('normal! G') 734 vim.cmd.startinsert() 735 736 return true 737 end 738 739 return M