mirror of
https://github.com/nvim-treesitter/nvim-treesitter.git
synced 2026-07-03 12:06:55 -04:00
feat!: refactor locals.lua into standalone
This commit is contained in:
parent
2c8f2f2fad
commit
fdafc019bb
6 changed files with 294 additions and 447 deletions
|
|
@ -2,7 +2,6 @@
|
|||
-- Locals are a generalization of definition and scopes
|
||||
-- it's the way nvim-treesitter uses to "understand" the code
|
||||
|
||||
local query = require('nvim-treesitter.query')
|
||||
local api = vim.api
|
||||
local ts = vim.treesitter
|
||||
|
||||
|
|
@ -30,19 +29,6 @@ local function get_root_for_node(node)
|
|||
return result
|
||||
end
|
||||
|
||||
-- Iterates matches from a locals query file.
|
||||
-- @param bufnr the buffer
|
||||
-- @param root the root node
|
||||
function M.iter_locals(bufnr, root)
|
||||
return query.iter_group_results(bufnr, 'locals', root)
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
---@return any
|
||||
function M.collect_locals(bufnr)
|
||||
return query.collect_group_results(bufnr, 'locals')
|
||||
end
|
||||
|
||||
-- Creates unique id for a node based on text and range
|
||||
---@param scope TSNode: the scope node of the definition
|
||||
---@param node_text string: the node text to use
|
||||
|
|
@ -52,48 +38,6 @@ function M.get_definition_id(scope, node_text)
|
|||
return table.concat({ 'k', node_text or '', scope:range() }, '_')
|
||||
end
|
||||
|
||||
function M.get_definitions(bufnr)
|
||||
local locals = M.collect_locals(bufnr)
|
||||
|
||||
local defs = {}
|
||||
|
||||
for _, loc in ipairs(locals) do
|
||||
if loc.definition then
|
||||
table.insert(defs, loc.definition)
|
||||
end
|
||||
end
|
||||
|
||||
return defs
|
||||
end
|
||||
|
||||
function M.get_scopes(bufnr)
|
||||
local locals = M.collect_locals(bufnr)
|
||||
|
||||
local scopes = {}
|
||||
|
||||
for _, loc in ipairs(locals) do
|
||||
if loc.scope and loc.scope.node then
|
||||
table.insert(scopes, loc.scope.node)
|
||||
end
|
||||
end
|
||||
|
||||
return scopes
|
||||
end
|
||||
|
||||
function M.get_references(bufnr)
|
||||
local locals = M.collect_locals(bufnr)
|
||||
|
||||
local refs = {}
|
||||
|
||||
for _, loc in ipairs(locals) do
|
||||
if loc.reference and loc.reference.node then
|
||||
table.insert(refs, loc.reference.node)
|
||||
end
|
||||
end
|
||||
|
||||
return refs
|
||||
end
|
||||
|
||||
-- Gets a table with all the scopes containing a node
|
||||
-- The order is from most specific to least (bottom up)
|
||||
---@param node TSNode
|
||||
|
|
@ -190,6 +134,59 @@ local function memoize(fn, hash_fn)
|
|||
end
|
||||
end
|
||||
|
||||
local function get_query(bufnr)
|
||||
local parser = assert(ts.get_parser(bufnr))
|
||||
if not parser then
|
||||
return
|
||||
end
|
||||
|
||||
local ft = vim.bo[bufnr].filetype
|
||||
local lang = ts.language.get_lang(ft) or ft
|
||||
|
||||
local query = (ts.query.get(lang, 'locals'))
|
||||
|
||||
parser:parse()
|
||||
local root = parser:trees():root()
|
||||
|
||||
return query, root
|
||||
end
|
||||
|
||||
-- Return all locals for the buffer
|
||||
--
|
||||
-- memoized by buffer tick
|
||||
--
|
||||
---@param bufnr integer buffer
|
||||
---@return table? definitions
|
||||
---@return table? references
|
||||
---@return table? scopes
|
||||
M.get = memoize(function(bufnr)
|
||||
local query, root = get_query(bufnr)
|
||||
if not query then
|
||||
return
|
||||
end
|
||||
|
||||
local definitions = {}
|
||||
local scopes = {}
|
||||
local references = {}
|
||||
for _, loc in query:iter_captures(root, bufnr) do
|
||||
if loc.definition then
|
||||
table.insert(definitions, loc.definition)
|
||||
end
|
||||
|
||||
if loc.scope and loc.scope.node then
|
||||
table.insert(scopes, loc.scope.node)
|
||||
end
|
||||
|
||||
if loc.reference and loc.reference.node then
|
||||
table.insert(references, loc.reference.node)
|
||||
end
|
||||
end
|
||||
|
||||
return definitions, references, scopes
|
||||
end, function(bufnr)
|
||||
return tostring(bufnr)
|
||||
end)
|
||||
|
||||
-- Get a single dimension table to look definition nodes.
|
||||
-- Keys are generated by using the range of the containing scope and the text of the definition node.
|
||||
-- This makes looking up a definition for a given scope a simple key lookup.
|
||||
|
|
@ -204,9 +201,12 @@ end
|
|||
---@param bufnr integer: the buffer
|
||||
---@return table result: a table for looking up definitions
|
||||
M.get_definitions_lookup_table = memoize(function(bufnr)
|
||||
local definitions = M.get_definitions(bufnr)
|
||||
local result = {}
|
||||
local definitions, _, _ = M.get(bufnr)
|
||||
if not definitions then
|
||||
return {}
|
||||
end
|
||||
|
||||
local result = {}
|
||||
for _, definition in ipairs(definitions) do
|
||||
for _, node_entry in ipairs(M.get_local_nodes(definition)) do
|
||||
local scopes = M.get_definition_scopes(node_entry.node, bufnr, node_entry.scope)
|
||||
|
|
@ -297,7 +297,12 @@ function M.find_usages(node, scope_node, bufnr)
|
|||
scope_node = scope_node or get_root_for_node(node)
|
||||
local usages = {}
|
||||
|
||||
for match in M.iter_locals(bufnr, scope_node) do
|
||||
local query, _ = get_query(bufnr)
|
||||
if not query then
|
||||
return {}
|
||||
end
|
||||
|
||||
for match in query:iter_matches(scope_node, bufnr) do
|
||||
if
|
||||
match.reference
|
||||
and match.reference.node
|
||||
|
|
@ -322,7 +327,7 @@ function M.containing_scope(node, bufnr, allow_scope)
|
|||
bufnr = bufnr or api.nvim_get_current_buf()
|
||||
allow_scope = allow_scope == nil or allow_scope == true
|
||||
|
||||
local scopes = M.get_scopes(bufnr)
|
||||
local _, _, scopes = M.get(bufnr)
|
||||
if not node or not scopes then
|
||||
return
|
||||
end
|
||||
|
|
@ -339,7 +344,7 @@ end
|
|||
function M.nested_scope(node, cursor_pos)
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
|
||||
local scopes = M.get_scopes(bufnr)
|
||||
local _, _, scopes = M.get(bufnr)
|
||||
if not node or not scopes then
|
||||
return
|
||||
end
|
||||
|
|
@ -359,7 +364,7 @@ end
|
|||
function M.next_scope(node)
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
|
||||
local scopes = M.get_scopes(bufnr)
|
||||
local _, _, scopes = M.get(bufnr)
|
||||
if not node or not scopes then
|
||||
return
|
||||
end
|
||||
|
|
@ -389,7 +394,7 @@ end
|
|||
function M.previous_scope(node)
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
|
||||
local scopes = M.get_scopes(bufnr)
|
||||
local _, _, scopes = M.get(bufnr)
|
||||
if not node or not scopes then
|
||||
return
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,165 +0,0 @@
|
|||
local M = {}
|
||||
|
||||
local EMPTY_ITER = function() end
|
||||
|
||||
---@class QueryInfo
|
||||
---@field root TSNode
|
||||
---@field source integer
|
||||
---@field start integer
|
||||
---@field stop integer
|
||||
|
||||
---@param bufnr integer
|
||||
---@param query_name string
|
||||
---@param root TSNode
|
||||
---@param root_lang string|nil
|
||||
---@return Query|nil, QueryInfo|nil
|
||||
local function prepare_query(bufnr, query_name, root, root_lang)
|
||||
local ft = vim.bo[bufnr].filetype
|
||||
local buf_lang = vim.treesitter.language.get_lang(ft) or ft
|
||||
if not buf_lang then
|
||||
return
|
||||
end
|
||||
|
||||
local parser = vim.treesitter.get_parser(bufnr, buf_lang)
|
||||
if not parser then
|
||||
return
|
||||
end
|
||||
|
||||
if not root then
|
||||
local first_tree = parser:trees()[1]
|
||||
|
||||
if first_tree then
|
||||
root = first_tree:root()
|
||||
end
|
||||
end
|
||||
|
||||
if not root then
|
||||
return
|
||||
end
|
||||
|
||||
local range = { root:range() }
|
||||
|
||||
if not root_lang then
|
||||
local lang_tree = parser:language_for_range(range)
|
||||
|
||||
if lang_tree then
|
||||
root_lang = lang_tree:lang()
|
||||
end
|
||||
end
|
||||
|
||||
if not root_lang then
|
||||
return
|
||||
end
|
||||
|
||||
local query = vim.treesitter.query.get(root_lang, query_name)
|
||||
if not query then
|
||||
return
|
||||
end
|
||||
|
||||
return query,
|
||||
{
|
||||
root = root,
|
||||
source = bufnr,
|
||||
start = range[1],
|
||||
-- The end row is exclusive so we need to add 1 to it.
|
||||
stop = range[3] + 1,
|
||||
}
|
||||
end
|
||||
|
||||
-- Given a path (i.e. a List(String)) this functions inserts value at path
|
||||
---@param object any
|
||||
---@param path string[]
|
||||
---@param value any
|
||||
function M.insert_to_path(object, path, value)
|
||||
local curr_obj = object
|
||||
|
||||
for index = 1, (#path - 1) do
|
||||
if curr_obj[path[index]] == nil then
|
||||
curr_obj[path[index]] = {}
|
||||
end
|
||||
|
||||
curr_obj = curr_obj[path[index]]
|
||||
end
|
||||
|
||||
curr_obj[path[#path]] = value
|
||||
end
|
||||
|
||||
---@param query Query
|
||||
---@param bufnr integer
|
||||
---@param start_row integer
|
||||
---@param end_row integer
|
||||
function M.iter_prepared_matches(query, qnode, bufnr, start_row, end_row)
|
||||
-- A function that splits a string on '.'
|
||||
---@param to_split string
|
||||
---@return string[]
|
||||
local function split(to_split)
|
||||
local t = {}
|
||||
for str in string.gmatch(to_split, '([^.]+)') do
|
||||
table.insert(t, str)
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local matches = query:iter_matches(qnode, bufnr, start_row, end_row)
|
||||
|
||||
local function iterator()
|
||||
local pattern, match, metadata = matches()
|
||||
if pattern ~= nil then
|
||||
local prepared_match = {}
|
||||
|
||||
-- Extract capture names from each match
|
||||
for id, node in pairs(match) do
|
||||
local name = query.captures[id] -- name of the capture in the query
|
||||
if name ~= nil then
|
||||
local path = split(name .. '.node')
|
||||
M.insert_to_path(prepared_match, path, node)
|
||||
local metadata_path = split(name .. '.metadata')
|
||||
M.insert_to_path(prepared_match, metadata_path, metadata[id])
|
||||
end
|
||||
end
|
||||
|
||||
-- Add some predicates for testing
|
||||
---@type string[][] ( TODO: make pred type so this can be pred[])
|
||||
local preds = query.info.patterns[pattern]
|
||||
if preds then
|
||||
for _, pred in pairs(preds) do
|
||||
-- functions
|
||||
if pred[1] == 'set!' and type(pred[2]) == 'string' then
|
||||
M.insert_to_path(prepared_match, split(pred[2]), pred[3])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return prepared_match
|
||||
end
|
||||
end
|
||||
return iterator
|
||||
end
|
||||
|
||||
---Iterates matches from a query file.
|
||||
---@param bufnr integer the buffer
|
||||
---@param query_group string the query file to use
|
||||
---@param root TSNode the root node
|
||||
---@param root_lang? string the root node lang, if known
|
||||
function M.iter_group_results(bufnr, query_group, root, root_lang)
|
||||
local query, params = prepare_query(bufnr, query_group, root, root_lang)
|
||||
if not query then
|
||||
return EMPTY_ITER
|
||||
end
|
||||
assert(params)
|
||||
|
||||
return M.iter_prepared_matches(query, params.root, params.source, params.start, params.stop)
|
||||
end
|
||||
|
||||
function M.collect_group_results(bufnr, query_group, root, lang)
|
||||
local matches = {}
|
||||
|
||||
for prepared_match in M.iter_group_results(bufnr, query_group, root, lang) do
|
||||
table.insert(matches, prepared_match)
|
||||
end
|
||||
|
||||
return matches
|
||||
end
|
||||
|
||||
return M
|
||||
Loading…
Add table
Add a link
Reference in a new issue