From a478bf936b2a648312704c02898eb7e5bfbf5692 Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Thu, 1 Feb 2024 15:34:35 -0500 Subject: [PATCH 1/2] feat(treesitter): use 0-based indexing to show ranges in `:InspectTree` Problem: - `:InspectTree` was showing node ranges in 1-based indexing, i.e., in vim cursor position (lnum, col). However, treesitter API adopts 0-based indexing to represent ranges (Range4). This can often be confusing for developers and plugin authors when debugging code written with treesiter APIs. Solution: - Change to 0-based indexing from 1-based indexing to show node ranges in `:InspectTree`. - Note: To make things not complicated, we do not provide an option or keymap to configure which indexing mode to use. --- runtime/doc/news.txt | 2 ++ runtime/lua/vim/treesitter/dev.lua | 29 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index e83fc25f88..c1fc04d9cf 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -224,6 +224,8 @@ The following new APIs and features were added. • |vim.treesitter.query.edit()| allows live editing of treesitter queries. • Improved error messages for query parsing. + • `:InspectTree` (|vim.treesitter.inspect_tree()|) shows node ranges in + 0-based indexing instead of 1-based indexing. • |vim.ui.open()| opens URIs using the system default handler (macOS `open`, Windows `explorer`, Linux `xdg-open`, etc.) diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 1385642acd..e1f93a654b 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -3,16 +3,20 @@ local api = vim.api ---@class TSDevModule local M = {} +---@private ---@class TSTreeView ---@field ns integer API namespace ----@field opts table Options table with the following keys: ---- - anon (boolean): If true, display anonymous nodes ---- - lang (boolean): If true, display the language alongside each node ---- - indent (number): Number of spaces to indent nested lines. Default is 2. +---@field opts TSTreeViewOpts ---@field nodes TSP.Node[] ---@field named TSP.Node[] local TSTreeView = {} +---@private +---@class TSTreeViewOpts +---@field anon boolean If true, display anonymous nodes. +---@field lang boolean If true, display the language alongside each node. +---@field indent number Number of spaces to indent nested lines. + ---@class TSP.Node ---@field node TSNode Treesitter node ---@field field string? Node field @@ -115,6 +119,7 @@ function TSTreeView:new(bufnr, lang) ns = api.nvim_create_namespace('treesitter/dev-inspect'), nodes = nodes, named = named, + ---@type TSTreeViewOpts opts = { anon = false, lang = false, @@ -129,16 +134,12 @@ end local decor_ns = api.nvim_create_namespace('ts.dev') ----@param lnum integer ----@param col integer ----@param end_lnum integer ----@param end_col integer +---@param range Range4 ---@return string -local function get_range_str(lnum, col, end_lnum, end_col) - if lnum == end_lnum then - return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col) - end - return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col) +local function range_to_string(range) + ---@type integer, integer, integer, integer + local row, col, end_row, end_col = unpack(range) + return string.format('[%d, %d] - [%d, %d]', row, col, end_row, end_col) end ---@param w integer @@ -212,7 +213,7 @@ function TSTreeView:draw(bufnr) local lang_hl_marks = {} ---@type table[] for i, item in self:iter() do - local range_str = get_range_str(item.node:range()) + local range_str = range_to_string({ item.node:range() }) local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' local text ---@type string From f4a3c326314b079e660ebd0f3c837f69d8c425fc Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Thu, 1 Feb 2024 15:25:40 -0500 Subject: [PATCH 2/2] test(treesitter): add test cases for inspect_tree Co-authored-by: altermo <107814000+altermo@users.noreply.github.com> --- .../treesitter/inspect_tree_spec.lua | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 test/functional/treesitter/inspect_tree_spec.lua diff --git a/test/functional/treesitter/inspect_tree_spec.lua b/test/functional/treesitter/inspect_tree_spec.lua new file mode 100644 index 0000000000..0102838b82 --- /dev/null +++ b/test/functional/treesitter/inspect_tree_spec.lua @@ -0,0 +1,109 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local insert = helpers.insert +local dedent = helpers.dedent +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local feed = helpers.feed + +describe('vim.treesitter.inspect_tree', function() + before_each(clear) + + local expect_tree = function(x) + local expected = vim.split(vim.trim(dedent(x)), '\n') + local actual = helpers.buf_lines(0) ---@type string[] + eq(expected, actual) + end + + it('working', function() + insert([[ + print() + ]]) + + exec_lua([[ + vim.treesitter.start(0, 'lua') + vim.treesitter.inspect_tree() + ]]) + + expect_tree [[ + (function_call ; [0, 0] - [0, 7] + name: (identifier) ; [0, 0] - [0, 5] + arguments: (arguments)) ; [0, 5] - [0, 7] + ]] + end) + + it('can toggle to show anonymous nodes', function() + insert([[ + print() + ]]) + + exec_lua([[ + vim.treesitter.start(0, 'lua') + vim.treesitter.inspect_tree() + ]]) + feed('a') + + expect_tree [[ + (function_call ; [0, 0] - [0, 7] + name: (identifier) ; [0, 0] - [0, 5] + arguments: (arguments ; [0, 5] - [0, 7] + "(" ; [0, 5] - [0, 6] + ")")) ; [0, 6] - [0, 7] + ]] + end) + + it('works for injected trees', function() + insert([[ + ```lua + return + ``` + ]]) + + exec_lua([[ + vim.treesitter.start(0, 'markdown') + vim.treesitter.get_parser():parse() + vim.treesitter.inspect_tree() + ]]) + + expect_tree [[ + (section ; [0, 0] - [4, 0] + (fenced_code_block ; [0, 0] - [3, 0] + (fenced_code_block_delimiter) ; [0, 0] - [0, 3] + (info_string ; [0, 3] - [0, 6] + (language)) ; [0, 3] - [0, 6] + (block_continuation) ; [1, 0] - [1, 0] + (code_fence_content ; [1, 0] - [2, 0] + (return_statement) ; [1, 0] - [1, 6] + (block_continuation)) ; [2, 0] - [2, 0] + (fenced_code_block_delimiter))) ; [2, 0] - [2, 3] + ]] + end) + + it('can toggle to show languages', function() + insert([[ + ```lua + return + ``` + ]]) + + exec_lua([[ + vim.treesitter.start(0, 'markdown') + vim.treesitter.get_parser():parse() + vim.treesitter.inspect_tree() + ]]) + feed('I') + + expect_tree [[ + (section ; [0, 0] - [4, 0] markdown + (fenced_code_block ; [0, 0] - [3, 0] markdown + (fenced_code_block_delimiter) ; [0, 0] - [0, 3] markdown + (info_string ; [0, 3] - [0, 6] markdown + (language)) ; [0, 3] - [0, 6] markdown + (block_continuation) ; [1, 0] - [1, 0] markdown + (code_fence_content ; [1, 0] - [2, 0] markdown + (return_statement) ; [1, 0] - [1, 6] lua + (block_continuation)) ; [2, 0] - [2, 0] markdown + (fenced_code_block_delimiter))) ; [2, 0] - [2, 3] markdown + ]] + end) +end)