feat: experimental support for navigating to symbol names (#279)

Using the treesitter backend, we can use the same "selectionRange" logic
that LSP symbol sources use to provide more detailed information about
where the name of the symbol is. We already use the LSP information to
change how we navigate the cursor to symbols, so once we parse this
information from treesitter it will automatically get used.

I'm putting this behind an experimental config option for now so we can
test it out for a while before making a sudden change to the behavior.
This commit is contained in:
Steven Arcangeli 2023-08-09 21:43:49 -07:00
parent de460a4a29
commit e54cae0df0
8 changed files with 56 additions and 15 deletions

View file

@ -1,6 +1,8 @@
local config = require("aerial.config")
-- This file is used by the markdown backend as well.
-- We pcall(require) so it doesn't error when nvim-treesitter isn't installed.
local _, utils = pcall(require, "nvim-treesitter.utils")
local helpers = require("aerial.backends.treesitter.helpers")
local M = {}
local get_node_text
@ -178,6 +180,10 @@ M.help = {
node = node:prev_sibling()
item.lnum = row + 1
item.col = col
if item.selection_range then
item.selection_range.lnum = row + 1
item.selection_range.col = col
end
end
item.name = table.concat(pieces, " ")
end
@ -278,6 +284,9 @@ local function c_postprocess(bufnr, item, match)
end
end
item.name = get_node_text(root, bufnr) or "<parse error>"
if config.treesitter.experimental_selection_range and not item.selection_range then
item.selection_range = helpers.range_from_nodes(root, root)
end
end
end

View file

@ -0,0 +1,17 @@
local M = {}
---@param start_node TSNode
---@param end_node TSNode
---@return aerial.Range
M.range_from_nodes = function(start_node, end_node)
local row, col = start_node:start()
local end_row, end_col = end_node:end_()
return {
lnum = row + 1,
end_lnum = end_row + 1,
col = col,
end_col = end_col,
}
end
return M

View file

@ -1,5 +1,6 @@
local backends = require("aerial.backends")
local config = require("aerial.config")
local helpers = require("aerial.backends.treesitter.helpers")
local util = require("aerial.backends.util")
---@type aerial.Backend
@ -60,12 +61,14 @@ M.fetch_symbols_sync = function(bufnr)
)
return
end
local use_selection_range = config.treesitter.experimental_selection_range
-- This will track a loose hierarchy of recent node+items.
-- It is used to determine node parents for the tree structure.
local stack = {}
local ext = extensions[lang]
for match in query.iter_group_results(bufnr, "aerial", syntax_tree:root(), lang) do
local name_match = match.name or {}
local selection_match = match.selection or {}
local type_node = (match.type or {}).node
-- The location capture groups are optional. We default to the
-- location of the @type capture
@ -89,11 +92,17 @@ M.fetch_symbols_sync = function(bufnr)
)
break
end
local row, col = start_node:start()
local end_row, end_col = end_node:end_()
local range = helpers.range_from_nodes(start_node, end_node)
local selection_range
if selection_match.node then
selection_range = helpers.range_from_nodes(selection_match.node, selection_match.node)
end
local name
if name_match.node then
name = get_node_text(name_match.node, bufnr, name_match) or "<parse error>"
if not selection_range then
selection_range = helpers.range_from_nodes(name_match.node, name_match.node)
end
else
name = "<Anonymous>"
end
@ -103,11 +112,13 @@ M.fetch_symbols_sync = function(bufnr)
name = name,
level = level,
parent = parent_item,
lnum = row + 1,
end_lnum = end_row + 1,
col = col,
end_col = end_col,
}
if use_selection_range then
item.selection_range = selection_range
end
for k, v in pairs(range) do
item[k] = v
end
if ext.postprocess(bufnr, item, match) == false or not include_kind[item.kind] then
goto continue
end

View file

@ -335,6 +335,10 @@ local default_options = {
treesitter = {
-- How long to wait (in ms) after a buffer change before updating
update_delay = 300,
-- Experimental feature to navigate to symbol names instead of the declaration
-- See https://github.com/stevearc/aerial.nvim/issues/279
-- If no bugs are reported for a time this will become the default
experimental_selection_range = false,
},
markdown = {

View file

@ -32,7 +32,7 @@
(string)? @name
(function_definition) @type)
(#set! "kind" "Function")
) @start
) @start @selection
(function_call
name: (dot_index_expression
@ -43,4 +43,4 @@
(string)? @name
(function_definition) @type)
(#set! "kind" "Function")
) @start
) @start @selection

View file

@ -40,4 +40,4 @@
(call) @name
])?
(#set! "kind" "Method")
) @type
) @type @selection

View file

@ -48,7 +48,7 @@
(string
(string_fragment) @name @string))?
(#set! "kind" "Function")
) @type
) @type @selection
; test.skip("this test")
(call_expression
@ -60,7 +60,7 @@
(string
(string_fragment) @name @string))?
(#set! "kind" "Function")
) @type
) @type @selection
; describe.each([])("Test suite")
(call_expression
@ -74,4 +74,4 @@
(string
(string_fragment) @name @string))?
(#set! "kind" "Function")
) @type
) @type @selection

View file

@ -48,7 +48,7 @@
(string
(string_fragment) @name @string))?
(#set! "kind" "Function")
) @type
) @type @selection
; test.skip("this test")
(call_expression
@ -60,7 +60,7 @@
(string
(string_fragment) @name @string))?
(#set! "kind" "Function")
) @type
) @type @selection
; describe.each([])("Test suite")
(call_expression
@ -74,4 +74,4 @@
(string
(string_fragment) @name @string))?
(#set! "kind" "Function")
) @type
) @type @selection