diff --git a/lua/avante/llm_tools.lua b/lua/avante/llm_tools.lua index 7757b4f..5d41d09 100644 --- a/lua/avante/llm_tools.lua +++ b/lua/avante/llm_tools.lua @@ -1631,6 +1631,57 @@ M._tools = { }, }, }, + { + name = "read_definitions", + description = "Retrieves the complete source code definitions of any symbol (function, type, constant, etc.) from your codebase.", + param = { + type = "table", + fields = { + { + name = "symbol_name", + description = "The name of the symbol to retrieve the definition for", + type = "string", + }, + { + name = "show_line_numbers", + description = "Whether to show line numbers in the definitions", + type = "boolean", + default = false, + }, + }, + }, + returns = { + { + name = "definitions", + description = "The source code definitions of the symbol", + type = "string[]", + }, + { + name = "error", + description = "Error message if the definition retrieval failed", + type = "string", + optional = true, + }, + }, + func = function(input_json, on_log, on_complete) + local symbol_name = input_json.symbol_name + local show_line_numbers = input_json.show_line_numbers + if on_log then on_log("symbol_name: " .. vim.inspect(symbol_name)) end + if on_log then on_log("show_line_numbers: " .. vim.inspect(show_line_numbers)) end + if not symbol_name then return nil, "No symbol name provided" end + local sidebar = require("avante").get() + if not sidebar then return nil, "No sidebar" end + local bufnr = sidebar.code.bufnr + if not bufnr then return nil, "No bufnr" end + if not vim.api.nvim_buf_is_valid(bufnr) then return nil, "Invalid bufnr" end + if on_log then on_log("bufnr: " .. vim.inspect(bufnr)) end + Utils.lsp.read_definitions(bufnr, symbol_name, show_line_numbers, function(definitions, error) + local encoded_defs = vim.json.encode(definitions) + on_complete(encoded_defs, error) + end) + return nil, nil + end, + }, } ---@param tools AvanteLLMTool[] diff --git a/lua/avante/types.lua b/lua/avante/types.lua index 686f88d..d7de584 100644 --- a/lua/avante/types.lua +++ b/lua/avante/types.lua @@ -401,3 +401,7 @@ vim.g.avante_login = vim.g.avante_login ---@field handler_opts AvanteHandlerOptions ---@field on_response_headers? fun(headers: table): nil --- +---@class avante.lsp.Definition +---@field content string +---@field uri string +--- diff --git a/lua/avante/utils/init.lua b/lua/avante/utils/init.lua index da7efbd..4f0ded6 100644 --- a/lua/avante/utils/init.lua +++ b/lua/avante/utils/init.lua @@ -8,6 +8,7 @@ local lsp = vim.lsp ---@field file avante.utils.file ---@field history avante.utils.history ---@field environment avante.utils.environment +---@field lsp avante.utils.lsp local M = {} setmetatable(M, { @@ -313,18 +314,6 @@ function M.get_hl(name) return api.nvim_get_hl(0, { name = name }) end -M.lsp = {} - ----@alias vim.lsp.Client.filter {id?: number, bufnr?: number, name?: string, method?: string, filter?:fun(client: vim.lsp.Client):boolean} - ----@param opts? vim.lsp.Client.filter ----@return vim.lsp.Client[] -function M.lsp.get_clients(opts) - ---@type vim.lsp.Client[] - local ret = vim.lsp.get_clients(opts) - return (opts and opts.filter) and vim.tbl_filter(opts.filter, ret) or ret -end - --- vendor from lazy.nvim for early access and override ---@param path string diff --git a/lua/avante/utils/lsp.lua b/lua/avante/utils/lsp.lua new file mode 100644 index 0000000..7703c76 --- /dev/null +++ b/lua/avante/utils/lsp.lua @@ -0,0 +1,112 @@ +---@class avante.utils.lsp +local M = {} + +---@alias vim.lsp.Client.filter {id?: number, bufnr?: number, name?: string, method?: string, filter?:fun(client: vim.lsp.Client):boolean} + +---@param opts? vim.lsp.Client.filter +---@return vim.lsp.Client[] +function M.get_clients(opts) + ---@type vim.lsp.Client[] + local ret = vim.lsp.get_clients(opts) + return (opts and opts.filter) and vim.tbl_filter(opts.filter, ret) or ret +end + +--- return function or variable or class +local function get_ts_node_parent(node) + if not node then return nil end + local type = node:type() + if + type:match("function") + or type:match("method") + or type:match("variable") + or type:match("class") + or type:match("type") + or type:match("parameter") + or type:match("field") + or type:match("property") + or type:match("enum") + or type:match("assignment") + or type:match("struct") + or type:match("declaration") + then + return node + end + return get_ts_node_parent(node:parent()) +end + +local function get_full_definition(location) + local uri = location.uri + local filepath = uri:gsub("^file://", "") + local full_lines = vim.fn.readfile(filepath) + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, full_lines) + local filetype = vim.filetype.match({ filename = filepath, buf = buf }) or "" + + --- use tree-sitter to get the full definition + local parser = require("nvim-treesitter.parsers").get_parser(buf, filetype) + local tree = parser:parse()[1] + local root = tree:root() + local node = root:named_descendant_for_range( + location.range.start.line, + location.range.start.character, + location.range.start.line, + location.range.start.character + ) + if not node then + vim.api.nvim_buf_delete(buf, { force = true }) + return {} + end + local parent = get_ts_node_parent(node) + if not parent then parent = node end + local text = vim.treesitter.get_node_text(parent, buf) + vim.api.nvim_buf_delete(buf, { force = true }) + return vim.split(text, "\n") +end + +---@param bufnr number +---@param symbol_name string +---@param show_line_numbers boolean +---@param on_complete fun(definitions: avante.lsp.Definition[] | nil, error: string | nil) +function M.read_definitions(bufnr, symbol_name, show_line_numbers, on_complete) + local clients = vim.lsp.get_clients({ bufnr = bufnr }) + if #clients == 0 then + on_complete(nil, "No LSP client found") + return + end + local params = { query = symbol_name } + vim.lsp.buf_request_all(bufnr, "workspace/symbol", params, function(results) + if not results or #results == 0 then + on_complete(nil, "No results") + return + end + for _, result in ipairs(results) do + if result.error then + on_complete(nil, result.error.message) + return + end + local definitions = vim.tbl_filter(function(d) return d.name == symbol_name end, result.result) + if #definitions == 0 then + on_complete(nil, "No definition found") + return + end + ---@type avante.lsp.Definition[] + local res = {} + for _, definition in ipairs(definitions) do + local lines = get_full_definition(definition.location) + if show_line_numbers then + local start_line = definition.location.range.start.line + local new_lines = {} + for i, line_ in ipairs(lines) do + table.insert(new_lines, tostring(start_line + i) .. ": " .. line_) + end + lines = new_lines + end + local uri = definition.location.uri + table.insert(res, { content = table.concat(lines, "\n"), uri = uri }) + end + on_complete(res, nil) + end + end) +end + +return M