mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-14 11:03:08 -05:00
feat: Add find symbol tool
* improve other tools for reading context
This commit is contained in:
@ -1,7 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
list(APPEND CMAKE_PREFIX_PATH "/Users/palm1r/Qt/Qt Creator.app/Contents/Resources/lib/cmake/QtCreator")
|
|
||||||
|
|
||||||
project(QodeAssist)
|
project(QodeAssist)
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
@ -52,6 +50,7 @@ add_qtc_plugin(QodeAssist
|
|||||||
QtCreator::LanguageClient
|
QtCreator::LanguageClient
|
||||||
QtCreator::TextEditor
|
QtCreator::TextEditor
|
||||||
QtCreator::ProjectExplorer
|
QtCreator::ProjectExplorer
|
||||||
|
QtCreator::CppEditor
|
||||||
DEPENDS
|
DEPENDS
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
@ -60,6 +59,7 @@ add_qtc_plugin(QodeAssist
|
|||||||
Qt::Network
|
Qt::Network
|
||||||
QtCreator::ExtensionSystem
|
QtCreator::ExtensionSystem
|
||||||
QtCreator::Utils
|
QtCreator::Utils
|
||||||
|
QtCreator::CPlusPlus
|
||||||
QodeAssistChatViewplugin
|
QodeAssistChatViewplugin
|
||||||
SOURCES
|
SOURCES
|
||||||
.github/workflows/build_cmake.yml
|
.github/workflows/build_cmake.yml
|
||||||
@ -115,7 +115,7 @@ add_qtc_plugin(QodeAssist
|
|||||||
widgets/QuickRefactorDialog.hpp widgets/QuickRefactorDialog.cpp
|
widgets/QuickRefactorDialog.hpp widgets/QuickRefactorDialog.cpp
|
||||||
QuickRefactorHandler.hpp QuickRefactorHandler.cpp
|
QuickRefactorHandler.hpp QuickRefactorHandler.cpp
|
||||||
tools/ToolsFactory.hpp tools/ToolsFactory.cpp
|
tools/ToolsFactory.hpp tools/ToolsFactory.cpp
|
||||||
tools/ReadProjectFileByNameTool.hpp tools/ReadProjectFileByNameTool.cpp
|
tools/ReadFileByPathTool.hpp tools/ReadFileByPathTool.cpp
|
||||||
tools/ReadVisibleFilesTool.hpp tools/ReadVisibleFilesTool.cpp
|
tools/ReadVisibleFilesTool.hpp tools/ReadVisibleFilesTool.cpp
|
||||||
tools/ToolHandler.hpp tools/ToolHandler.cpp
|
tools/ToolHandler.hpp tools/ToolHandler.cpp
|
||||||
tools/ListProjectFilesTool.hpp tools/ListProjectFilesTool.cpp
|
tools/ListProjectFilesTool.hpp tools/ListProjectFilesTool.cpp
|
||||||
@ -123,12 +123,14 @@ add_qtc_plugin(QodeAssist
|
|||||||
tools/SearchInProjectTool.hpp tools/SearchInProjectTool.cpp
|
tools/SearchInProjectTool.hpp tools/SearchInProjectTool.cpp
|
||||||
tools/GetIssuesListTool.hpp tools/GetIssuesListTool.cpp
|
tools/GetIssuesListTool.hpp tools/GetIssuesListTool.cpp
|
||||||
tools/EditProjectFileTool.hpp tools/EditProjectFileTool.cpp
|
tools/EditProjectFileTool.hpp tools/EditProjectFileTool.cpp
|
||||||
|
tools/FindSymbolTool.hpp tools/FindSymbolTool.cpp
|
||||||
providers/ClaudeMessage.hpp providers/ClaudeMessage.cpp
|
providers/ClaudeMessage.hpp providers/ClaudeMessage.cpp
|
||||||
providers/OpenAIMessage.hpp providers/OpenAIMessage.cpp
|
providers/OpenAIMessage.hpp providers/OpenAIMessage.cpp
|
||||||
providers/OllamaMessage.hpp providers/OllamaMessage.cpp
|
providers/OllamaMessage.hpp providers/OllamaMessage.cpp
|
||||||
providers/GoogleMessage.hpp providers/GoogleMessage.cpp
|
providers/GoogleMessage.hpp providers/GoogleMessage.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
get_target_property(QtCreatorCorePath QtCreator::Core LOCATION)
|
get_target_property(QtCreatorCorePath QtCreator::Core LOCATION)
|
||||||
find_program(QtCreatorExecutable
|
find_program(QtCreatorExecutable
|
||||||
NAMES
|
NAMES
|
||||||
|
|||||||
705
tools/FindSymbolTool.cpp
Normal file
705
tools/FindSymbolTool.cpp
Normal file
@ -0,0 +1,705 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FindSymbolTool.hpp"
|
||||||
|
|
||||||
|
#include <cplusplus/Overview.h>
|
||||||
|
#include <cplusplus/Scope.h>
|
||||||
|
#include <cplusplus/Symbols.h>
|
||||||
|
#include <cppeditor/cppmodelmanager.h>
|
||||||
|
#include <logger/Logger.hpp>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QTextCursor>
|
||||||
|
#include <QTextDocument>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
|
FindSymbolTool::FindSymbolTool(QObject *parent)
|
||||||
|
: BaseTool(parent)
|
||||||
|
, m_ignoreManager(new Context::IgnoreManager(this))
|
||||||
|
{}
|
||||||
|
|
||||||
|
QString FindSymbolTool::name() const
|
||||||
|
{
|
||||||
|
return "find_cpp_symbol";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindSymbolTool::stringName() const
|
||||||
|
{
|
||||||
|
return "Finding C++ symbols in project";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindSymbolTool::description() const
|
||||||
|
{
|
||||||
|
return "Find C++ symbols (classes, functions, enums, variables, typedefs, namespaces) in the "
|
||||||
|
"current project. "
|
||||||
|
"Returns file paths, line numbers, qualified names, and optionally the source code of "
|
||||||
|
"found symbols. "
|
||||||
|
"Supports exact match, wildcard patterns, and regular expressions.\n"
|
||||||
|
"Input parameters:\n"
|
||||||
|
"- 'symbol_name' (required): Name or pattern of the symbol to find\n"
|
||||||
|
"- 'symbol_type' (optional): Type of symbol to search for: 'all', 'class', 'function', "
|
||||||
|
"'enum', 'variable', 'typedef', 'namespace'. Default is 'all'\n"
|
||||||
|
"- 'scope_filter' (optional): Filter results by scope (e.g., 'MyNamespace', "
|
||||||
|
"'MyClass'). Only symbols within this scope will be returned\n"
|
||||||
|
"- 'case_sensitive' (optional): Enable case-sensitive search. Default is true\n"
|
||||||
|
"- 'use_regex' (optional): Treat symbol_name as regular expression. Default is false\n"
|
||||||
|
"- 'use_wildcard' (optional): Treat symbol_name as wildcard pattern (e.g., 'find*', "
|
||||||
|
"'*Symbol', '*find*'). Default is false\n"
|
||||||
|
"- 'include_code' (optional): Whether to include the source code of found symbols. "
|
||||||
|
"Default is false\n"
|
||||||
|
"- 'max_results' (optional): Maximum number of results to return. Default is 10\n"
|
||||||
|
"- 'group_by' (optional): How to group results: 'type' (default), 'file', or 'scope'";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject FindSymbolTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
||||||
|
{
|
||||||
|
QJsonObject properties;
|
||||||
|
|
||||||
|
QJsonObject symbolNameProperty;
|
||||||
|
symbolNameProperty["type"] = "string";
|
||||||
|
symbolNameProperty["description"] = "Name or pattern of the symbol to find (supports exact "
|
||||||
|
"match, wildcard, or regex depending on flags)";
|
||||||
|
properties["symbol_name"] = symbolNameProperty;
|
||||||
|
|
||||||
|
QJsonObject symbolTypeProperty;
|
||||||
|
symbolTypeProperty["type"] = "string";
|
||||||
|
symbolTypeProperty["description"]
|
||||||
|
= "Type of symbol: all, class, function, enum, variable, typedef, namespace";
|
||||||
|
symbolTypeProperty["enum"]
|
||||||
|
= QJsonArray{"all", "class", "function", "enum", "variable", "typedef", "namespace"};
|
||||||
|
properties["symbol_type"] = symbolTypeProperty;
|
||||||
|
|
||||||
|
QJsonObject scopeFilterProperty;
|
||||||
|
scopeFilterProperty["type"] = "string";
|
||||||
|
scopeFilterProperty["description"]
|
||||||
|
= "Filter results by scope (e.g., 'MyNamespace', 'MyClass')";
|
||||||
|
properties["scope_filter"] = scopeFilterProperty;
|
||||||
|
|
||||||
|
QJsonObject caseSensitiveProperty;
|
||||||
|
caseSensitiveProperty["type"] = "boolean";
|
||||||
|
caseSensitiveProperty["description"] = "Enable case-sensitive search (default: true)";
|
||||||
|
properties["case_sensitive"] = caseSensitiveProperty;
|
||||||
|
|
||||||
|
QJsonObject useRegexProperty;
|
||||||
|
useRegexProperty["type"] = "boolean";
|
||||||
|
useRegexProperty["description"]
|
||||||
|
= "Treat symbol_name as regular expression (default: false)";
|
||||||
|
properties["use_regex"] = useRegexProperty;
|
||||||
|
|
||||||
|
QJsonObject useWildcardProperty;
|
||||||
|
useWildcardProperty["type"] = "boolean";
|
||||||
|
useWildcardProperty["description"]
|
||||||
|
= "Treat symbol_name as wildcard pattern like 'find*', '*Symbol' (default: false)";
|
||||||
|
properties["use_wildcard"] = useWildcardProperty;
|
||||||
|
|
||||||
|
QJsonObject includeCodeProperty;
|
||||||
|
includeCodeProperty["type"] = "boolean";
|
||||||
|
includeCodeProperty["description"] = "Include source code of found symbols";
|
||||||
|
properties["include_code"] = includeCodeProperty;
|
||||||
|
|
||||||
|
QJsonObject maxResultsProperty;
|
||||||
|
maxResultsProperty["type"] = "integer";
|
||||||
|
maxResultsProperty["description"] = "Maximum number of results to return";
|
||||||
|
properties["max_results"] = maxResultsProperty;
|
||||||
|
|
||||||
|
QJsonObject groupByProperty;
|
||||||
|
groupByProperty["type"] = "string";
|
||||||
|
groupByProperty["description"] = "How to group results: type, file, or scope";
|
||||||
|
groupByProperty["enum"] = QJsonArray{"type", "file", "scope"};
|
||||||
|
properties["group_by"] = groupByProperty;
|
||||||
|
|
||||||
|
QJsonObject definition;
|
||||||
|
definition["type"] = "object";
|
||||||
|
definition["properties"] = properties;
|
||||||
|
definition["required"] = QJsonArray{"symbol_name"};
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case LLMCore::ToolSchemaFormat::OpenAI:
|
||||||
|
return customizeForOpenAI(definition);
|
||||||
|
case LLMCore::ToolSchemaFormat::Claude:
|
||||||
|
return customizeForClaude(definition);
|
||||||
|
case LLMCore::ToolSchemaFormat::Ollama:
|
||||||
|
return customizeForOllama(definition);
|
||||||
|
case LLMCore::ToolSchemaFormat::Google:
|
||||||
|
return customizeForGoogle(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
LLMCore::ToolPermissions FindSymbolTool::requiredPermissions() const
|
||||||
|
{
|
||||||
|
return LLMCore::ToolPermission::FileSystemRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<QString> FindSymbolTool::executeAsync(const QJsonObject &input)
|
||||||
|
{
|
||||||
|
return QtConcurrent::run([this, input]() -> QString {
|
||||||
|
QString symbolName = input["symbol_name"].toString();
|
||||||
|
QString symbolTypeStr = input["symbol_type"].toString("all");
|
||||||
|
QString scopeFilter = input["scope_filter"].toString();
|
||||||
|
bool caseSensitive = input["case_sensitive"].toBool(true);
|
||||||
|
bool useRegex = input["use_regex"].toBool(false);
|
||||||
|
bool useWildcard = input["use_wildcard"].toBool(false);
|
||||||
|
bool includeCode = input["include_code"].toBool(false);
|
||||||
|
int maxResults = input["max_results"].toInt(10);
|
||||||
|
QString groupBy = input["group_by"].toString("type");
|
||||||
|
|
||||||
|
if (symbolName.isEmpty()) {
|
||||||
|
QString error = "Error: 'symbol_name' parameter is required";
|
||||||
|
LOG_MESSAGE(error);
|
||||||
|
throw std::invalid_argument(error.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useRegex && useWildcard) {
|
||||||
|
QString error = "Error: 'use_regex' and 'use_wildcard' cannot be used together";
|
||||||
|
LOG_MESSAGE(error);
|
||||||
|
throw std::invalid_argument(error.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolType type = parseSymbolType(symbolTypeStr);
|
||||||
|
LOG_MESSAGE(QString("Searching for symbol: '%1', type: %2, scope: '%3', "
|
||||||
|
"case_sensitive: %4, regex: %5, wildcard: %6")
|
||||||
|
.arg(symbolName, symbolTypeStr, scopeFilter)
|
||||||
|
.arg(caseSensitive)
|
||||||
|
.arg(useRegex)
|
||||||
|
.arg(useWildcard));
|
||||||
|
|
||||||
|
QList<SymbolInfo> symbols
|
||||||
|
= findSymbols(symbolName, type, scopeFilter, caseSensitive, useRegex, useWildcard);
|
||||||
|
|
||||||
|
if (symbols.isEmpty()) {
|
||||||
|
QString msg = QString("No symbol matching '%1' found in the project").arg(symbolName);
|
||||||
|
if (!scopeFilter.isEmpty()) {
|
||||||
|
msg += QString(" within scope '%1'").arg(scopeFilter);
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbols.size() > maxResults) {
|
||||||
|
symbols = symbols.mid(0, maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeCode) {
|
||||||
|
for (SymbolInfo &info : symbols) {
|
||||||
|
info.code = extractSymbolCode(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatResults(symbols, includeCode, groupBy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
FindSymbolTool::SymbolType FindSymbolTool::parseSymbolType(const QString &typeStr) const
|
||||||
|
{
|
||||||
|
if (typeStr == "class")
|
||||||
|
return SymbolType::Class;
|
||||||
|
if (typeStr == "function")
|
||||||
|
return SymbolType::Function;
|
||||||
|
if (typeStr == "enum")
|
||||||
|
return SymbolType::Enum;
|
||||||
|
if (typeStr == "variable")
|
||||||
|
return SymbolType::Variable;
|
||||||
|
if (typeStr == "typedef")
|
||||||
|
return SymbolType::Typedef;
|
||||||
|
if (typeStr == "namespace")
|
||||||
|
return SymbolType::Namespace;
|
||||||
|
return SymbolType::All;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<FindSymbolTool::SymbolInfo> FindSymbolTool::findSymbols(
|
||||||
|
const QString &symbolName,
|
||||||
|
SymbolType type,
|
||||||
|
const QString &scopeFilter,
|
||||||
|
bool caseSensitive,
|
||||||
|
bool useRegex,
|
||||||
|
bool useWildcard) const
|
||||||
|
{
|
||||||
|
QList<SymbolInfo> results;
|
||||||
|
|
||||||
|
auto *modelManager = CppEditor::CppModelManager::instance();
|
||||||
|
if (!modelManager) {
|
||||||
|
LOG_MESSAGE("CppModelManager not available");
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegularExpression searchPattern;
|
||||||
|
if (useRegex) {
|
||||||
|
QRegularExpression::PatternOptions options = QRegularExpression::NoPatternOption;
|
||||||
|
if (!caseSensitive) {
|
||||||
|
options |= QRegularExpression::CaseInsensitiveOption;
|
||||||
|
}
|
||||||
|
searchPattern.setPattern(symbolName);
|
||||||
|
searchPattern.setPatternOptions(options);
|
||||||
|
|
||||||
|
if (!searchPattern.isValid()) {
|
||||||
|
LOG_MESSAGE(QString("Invalid regex pattern: %1").arg(symbolName));
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
} else if (useWildcard) {
|
||||||
|
QString regexPattern = QRegularExpression::wildcardToRegularExpression(symbolName);
|
||||||
|
QRegularExpression::PatternOptions options = QRegularExpression::NoPatternOption;
|
||||||
|
if (!caseSensitive) {
|
||||||
|
options |= QRegularExpression::CaseInsensitiveOption;
|
||||||
|
}
|
||||||
|
searchPattern.setPattern(regexPattern);
|
||||||
|
searchPattern.setPatternOptions(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CPlusPlus::Snapshot snapshot = modelManager->snapshot();
|
||||||
|
CPlusPlus::Overview overview;
|
||||||
|
|
||||||
|
for (auto it = snapshot.begin(); it != snapshot.end(); ++it) {
|
||||||
|
CPlusPlus::Document::Ptr doc = it.value();
|
||||||
|
if (!doc || !doc->globalNamespace()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString filePath = doc->filePath().toUserOutput();
|
||||||
|
|
||||||
|
auto project = ProjectExplorer::ProjectManager::projectForFile(
|
||||||
|
Utils::FilePath::fromString(filePath));
|
||||||
|
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchInScope(
|
||||||
|
doc->globalNamespace(),
|
||||||
|
symbolName,
|
||||||
|
type,
|
||||||
|
scopeFilter,
|
||||||
|
filePath,
|
||||||
|
overview,
|
||||||
|
QString(),
|
||||||
|
caseSensitive,
|
||||||
|
useRegex,
|
||||||
|
useWildcard,
|
||||||
|
useRegex || useWildcard ? &searchPattern : nullptr,
|
||||||
|
results);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindSymbolTool::searchInScope(
|
||||||
|
CPlusPlus::Scope *scope,
|
||||||
|
const QString &symbolName,
|
||||||
|
SymbolType searchType,
|
||||||
|
const QString &scopeFilter,
|
||||||
|
const QString &filePath,
|
||||||
|
const CPlusPlus::Overview &overview,
|
||||||
|
const QString ¤tScope,
|
||||||
|
bool caseSensitive,
|
||||||
|
bool useRegex,
|
||||||
|
bool useWildcard,
|
||||||
|
QRegularExpression *searchPattern,
|
||||||
|
QList<SymbolInfo> &results) const
|
||||||
|
{
|
||||||
|
if (!scope) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < scope->memberCount(); ++i) {
|
||||||
|
CPlusPlus::Symbol *symbol = scope->memberAt(i);
|
||||||
|
if (!symbol || !symbol->name()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString currentSymbolName = overview.prettyName(symbol->name());
|
||||||
|
QString fullScope = buildFullScope(currentScope, currentSymbolName);
|
||||||
|
|
||||||
|
if (matchesSymbolName(currentSymbolName,
|
||||||
|
symbolName,
|
||||||
|
caseSensitive,
|
||||||
|
useRegex,
|
||||||
|
useWildcard,
|
||||||
|
searchPattern)
|
||||||
|
&& matchesType(symbol, searchType)) {
|
||||||
|
if (scopeFilter.isEmpty() || matchesScopeFilter(currentScope, scopeFilter)) {
|
||||||
|
results.append(createSymbolInfo(symbol, filePath, currentScope, overview));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol->asNamespace() || symbol->asClass() || symbol->asEnum()) {
|
||||||
|
searchInScope(symbol->asScope(),
|
||||||
|
symbolName,
|
||||||
|
searchType,
|
||||||
|
scopeFilter,
|
||||||
|
filePath,
|
||||||
|
overview,
|
||||||
|
fullScope,
|
||||||
|
caseSensitive,
|
||||||
|
useRegex,
|
||||||
|
useWildcard,
|
||||||
|
searchPattern,
|
||||||
|
results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindSymbolTool::matchesScopeFilter(const QString &fullScope, const QString &scopeFilter) const
|
||||||
|
{
|
||||||
|
if (scopeFilter.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match if the full scope contains the filter
|
||||||
|
// E.g., "MyNamespace::MyClass" matches filter "MyNamespace" or "MyClass"
|
||||||
|
return fullScope.contains(scopeFilter) || fullScope.endsWith(scopeFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindSymbolTool::buildFullScope(const QString ¤tScope, const QString &symbolName) const
|
||||||
|
{
|
||||||
|
if (currentScope.isEmpty()) {
|
||||||
|
return symbolName;
|
||||||
|
}
|
||||||
|
return currentScope + "::" + symbolName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindSymbolTool::matchesSymbolName(
|
||||||
|
const QString &symbolName,
|
||||||
|
const QString &searchPattern,
|
||||||
|
bool caseSensitive,
|
||||||
|
bool useRegex,
|
||||||
|
bool useWildcard,
|
||||||
|
QRegularExpression *regex) const
|
||||||
|
{
|
||||||
|
if (useRegex || useWildcard) {
|
||||||
|
// Use regex pattern matching
|
||||||
|
if (regex && regex->isValid()) {
|
||||||
|
return regex->match(symbolName).hasMatch();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exact match (with optional case sensitivity)
|
||||||
|
if (caseSensitive) {
|
||||||
|
return symbolName == searchPattern;
|
||||||
|
} else {
|
||||||
|
return symbolName.compare(searchPattern, Qt::CaseInsensitive) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindSymbolTool::matchesType(CPlusPlus::Symbol *symbol, SymbolType type) const
|
||||||
|
{
|
||||||
|
if (type == SymbolType::All) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case SymbolType::Class:
|
||||||
|
return symbol->asClass() != nullptr;
|
||||||
|
case SymbolType::Function:
|
||||||
|
return symbol->asFunction() != nullptr;
|
||||||
|
case SymbolType::Enum:
|
||||||
|
return symbol->asEnum() != nullptr;
|
||||||
|
case SymbolType::Namespace:
|
||||||
|
return symbol->asNamespace() != nullptr;
|
||||||
|
case SymbolType::Variable:
|
||||||
|
return symbol->asDeclaration() != nullptr && !symbol->type()->asFunctionType();
|
||||||
|
case SymbolType::Typedef:
|
||||||
|
return symbol->asTypenameArgument() != nullptr
|
||||||
|
|| (symbol->asDeclaration() && symbol->asDeclaration()->isTypedef());
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FindSymbolTool::SymbolInfo FindSymbolTool::createSymbolInfo(
|
||||||
|
CPlusPlus::Symbol *symbol,
|
||||||
|
const QString &filePath,
|
||||||
|
const QString &fullScope,
|
||||||
|
const CPlusPlus::Overview &overview) const
|
||||||
|
{
|
||||||
|
SymbolInfo info;
|
||||||
|
info.name = overview.prettyName(symbol->name());
|
||||||
|
info.filePath = filePath;
|
||||||
|
info.line = symbol->line();
|
||||||
|
info.scope = fullScope;
|
||||||
|
|
||||||
|
// Build qualified name
|
||||||
|
if (fullScope.isEmpty()) {
|
||||||
|
info.qualifiedName = info.name;
|
||||||
|
} else {
|
||||||
|
info.qualifiedName = fullScope + "::" + info.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine symbol type and extract additional information
|
||||||
|
if (symbol->asClass()) {
|
||||||
|
info.type = SymbolType::Class;
|
||||||
|
info.typeString = "Class";
|
||||||
|
info.endLine = findSymbolEndLine(filePath, symbol->line(), SymbolType::Class);
|
||||||
|
} else if (auto *function = symbol->asFunction()) {
|
||||||
|
info.type = SymbolType::Function;
|
||||||
|
info.typeString = "Function";
|
||||||
|
info.signature = overview.prettyType(symbol->type());
|
||||||
|
info.isConst = function->isConst();
|
||||||
|
info.isStatic = function->isStatic();
|
||||||
|
info.isVirtual = function->isVirtual();
|
||||||
|
info.endLine = findSymbolEndLine(filePath, symbol->line(), SymbolType::Function);
|
||||||
|
} else if (symbol->asEnum()) {
|
||||||
|
info.type = SymbolType::Enum;
|
||||||
|
info.typeString = "Enum";
|
||||||
|
info.endLine = findSymbolEndLine(filePath, symbol->line(), SymbolType::Enum);
|
||||||
|
} else if (symbol->asNamespace()) {
|
||||||
|
info.type = SymbolType::Namespace;
|
||||||
|
info.typeString = "Namespace";
|
||||||
|
info.endLine = symbol->line(); // Namespaces can span multiple files
|
||||||
|
} else if (auto *declaration = symbol->asDeclaration()) {
|
||||||
|
if (declaration->isTypedef()) {
|
||||||
|
info.type = SymbolType::Typedef;
|
||||||
|
info.typeString = "Typedef";
|
||||||
|
info.signature = overview.prettyType(symbol->type());
|
||||||
|
} else {
|
||||||
|
info.type = SymbolType::Variable;
|
||||||
|
info.typeString = "Variable";
|
||||||
|
info.signature = overview.prettyType(symbol->type());
|
||||||
|
info.isStatic = declaration->isStatic();
|
||||||
|
}
|
||||||
|
info.endLine = symbol->line();
|
||||||
|
} else {
|
||||||
|
info.typeString = "Symbol";
|
||||||
|
info.endLine = symbol->line();
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FindSymbolTool::findSymbolEndLine(const QString &filePath, int startLine, SymbolType type) const
|
||||||
|
{
|
||||||
|
// For simple types, just return the start line
|
||||||
|
if (type == SymbolType::Variable || type == SymbolType::Typedef
|
||||||
|
|| type == SymbolType::Namespace) {
|
||||||
|
return startLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For classes, enums, and functions, find the closing brace
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
return startLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream stream(&file);
|
||||||
|
int currentLine = 1;
|
||||||
|
|
||||||
|
// Skip to start line
|
||||||
|
while (currentLine < startLine && !stream.atEnd()) {
|
||||||
|
stream.readLine();
|
||||||
|
currentLine++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool foundOpenBrace = false;
|
||||||
|
int braceCount = 0;
|
||||||
|
|
||||||
|
while (!stream.atEnd()) {
|
||||||
|
QString line = stream.readLine();
|
||||||
|
|
||||||
|
for (QChar ch : line) {
|
||||||
|
if (ch == '{') {
|
||||||
|
foundOpenBrace = true;
|
||||||
|
braceCount++;
|
||||||
|
} else if (ch == '}') {
|
||||||
|
braceCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundOpenBrace && braceCount == 0) {
|
||||||
|
file.close();
|
||||||
|
return currentLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For function declarations without body (e.g., in header), stop at semicolon
|
||||||
|
if (type == SymbolType::Function && !foundOpenBrace && line.contains(';')) {
|
||||||
|
file.close();
|
||||||
|
return currentLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLine++;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
return startLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindSymbolTool::extractSymbolCode(const SymbolInfo &info) const
|
||||||
|
{
|
||||||
|
// Try to use TextDocument first (for open files)
|
||||||
|
auto *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
||||||
|
Utils::FilePath::fromString(info.filePath));
|
||||||
|
|
||||||
|
if (textDocument && textDocument->document()) {
|
||||||
|
QTextDocument *doc = textDocument->document();
|
||||||
|
|
||||||
|
QTextBlock startBlock = doc->findBlockByNumber(info.line - 1);
|
||||||
|
QTextBlock endBlock = doc->findBlockByNumber(info.endLine - 1);
|
||||||
|
|
||||||
|
if (startBlock.isValid() && endBlock.isValid()) {
|
||||||
|
int startPos = startBlock.position();
|
||||||
|
int endPos = endBlock.position() + endBlock.length();
|
||||||
|
|
||||||
|
QTextCursor cursor(doc);
|
||||||
|
cursor.setPosition(startPos);
|
||||||
|
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
|
||||||
|
|
||||||
|
return cursor.selectedText().replace(QChar::ParagraphSeparator, '\n').trimmed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to file reading
|
||||||
|
return extractCodeFromFile(info.filePath, info.line, info.endLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindSymbolTool::extractCodeFromFile(const QString &filePath, int startLine, int endLine) const
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
LOG_MESSAGE(QString("Failed to open file: %1").arg(filePath));
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream stream(&file);
|
||||||
|
QString result;
|
||||||
|
int currentLine = 1;
|
||||||
|
|
||||||
|
while (currentLine < startLine && !stream.atEnd()) {
|
||||||
|
stream.readLine();
|
||||||
|
currentLine++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (currentLine <= endLine && !stream.atEnd()) {
|
||||||
|
QString line = stream.readLine();
|
||||||
|
result += line + "\n";
|
||||||
|
currentLine++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.endsWith('\n')) {
|
||||||
|
result.chop(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindSymbolTool::formatResults(
|
||||||
|
const QList<SymbolInfo> &symbols, bool includeCode, const QString &groupBy) const
|
||||||
|
{
|
||||||
|
QString output = QString("Found %1 symbol(s):\n\n").arg(symbols.size());
|
||||||
|
|
||||||
|
if (groupBy == "file") {
|
||||||
|
// Group by file
|
||||||
|
QMap<QString, QList<SymbolInfo>> grouped;
|
||||||
|
for (const SymbolInfo &info : symbols) {
|
||||||
|
grouped[info.filePath].append(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = grouped.begin(); it != grouped.end(); ++it) {
|
||||||
|
output += QString("File: %1\n").arg(it.key());
|
||||||
|
for (const SymbolInfo &info : it.value()) {
|
||||||
|
output += formatSymbolInfo(info, includeCode, 2);
|
||||||
|
}
|
||||||
|
output += "\n";
|
||||||
|
}
|
||||||
|
} else if (groupBy == "scope") {
|
||||||
|
// Group by scope
|
||||||
|
QMap<QString, QList<SymbolInfo>> grouped;
|
||||||
|
for (const SymbolInfo &info : symbols) {
|
||||||
|
QString scopeKey = info.scope.isEmpty() ? "Global" : info.scope;
|
||||||
|
grouped[scopeKey].append(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = grouped.begin(); it != grouped.end(); ++it) {
|
||||||
|
output += QString("Scope: %1\n").arg(it.key());
|
||||||
|
for (const SymbolInfo &info : it.value()) {
|
||||||
|
output += formatSymbolInfo(info, includeCode, 2);
|
||||||
|
}
|
||||||
|
output += "\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Group by type (default)
|
||||||
|
QMap<SymbolType, QList<SymbolInfo>> grouped;
|
||||||
|
for (const SymbolInfo &info : symbols) {
|
||||||
|
grouped[info.type].append(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = grouped.begin(); it != grouped.end(); ++it) {
|
||||||
|
const QList<SymbolInfo> &group = it.value();
|
||||||
|
if (!group.isEmpty()) {
|
||||||
|
output += QString("%1s:\n").arg(group.first().typeString);
|
||||||
|
for (const SymbolInfo &info : group) {
|
||||||
|
output += formatSymbolInfo(info, includeCode, 2);
|
||||||
|
}
|
||||||
|
output += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindSymbolTool::formatSymbolInfo(
|
||||||
|
const SymbolInfo &info, bool includeCode, int indentLevel) const
|
||||||
|
{
|
||||||
|
QString indent = QString(indentLevel, ' ');
|
||||||
|
QString output;
|
||||||
|
|
||||||
|
// Basic information
|
||||||
|
output += QString("%1%2:%3 - %4")
|
||||||
|
.arg(indent)
|
||||||
|
.arg(info.filePath)
|
||||||
|
.arg(info.line)
|
||||||
|
.arg(info.qualifiedName);
|
||||||
|
|
||||||
|
// Add signature if available
|
||||||
|
if (!info.signature.isEmpty()) {
|
||||||
|
output += QString(" : %1").arg(info.signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add modifiers
|
||||||
|
QStringList modifiers;
|
||||||
|
if (info.isStatic)
|
||||||
|
modifiers << "static";
|
||||||
|
if (info.isVirtual)
|
||||||
|
modifiers << "virtual";
|
||||||
|
if (info.isConst)
|
||||||
|
modifiers << "const";
|
||||||
|
|
||||||
|
if (!modifiers.isEmpty()) {
|
||||||
|
output += QString(" [%1]").arg(modifiers.join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
output += "\n";
|
||||||
|
|
||||||
|
// Add code if requested
|
||||||
|
if (includeCode && !info.code.isEmpty()) {
|
||||||
|
output += "\n```cpp\n" + info.code + "\n```\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Tools
|
||||||
121
tools/FindSymbolTool.hpp
Normal file
121
tools/FindSymbolTool.hpp
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <context/IgnoreManager.hpp>
|
||||||
|
#include <llmcore/BaseTool.hpp>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
|
namespace CPlusPlus {
|
||||||
|
class Symbol;
|
||||||
|
class Scope;
|
||||||
|
class Overview;
|
||||||
|
class Document;
|
||||||
|
} // namespace CPlusPlus
|
||||||
|
|
||||||
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
|
class FindSymbolTool : public LLMCore::BaseTool
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit FindSymbolTool(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
QString name() const override;
|
||||||
|
QString stringName() const override;
|
||||||
|
QString description() const override;
|
||||||
|
QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override;
|
||||||
|
LLMCore::ToolPermissions requiredPermissions() const override;
|
||||||
|
|
||||||
|
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class SymbolType { All, Class, Function, Enum, Variable, Typedef, Namespace };
|
||||||
|
|
||||||
|
struct SymbolInfo
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QString qualifiedName;
|
||||||
|
QString filePath;
|
||||||
|
int line;
|
||||||
|
int endLine;
|
||||||
|
QString scope;
|
||||||
|
SymbolType type;
|
||||||
|
QString typeString;
|
||||||
|
QString signature;
|
||||||
|
QString code;
|
||||||
|
bool isConst = false;
|
||||||
|
bool isStatic = false;
|
||||||
|
bool isVirtual = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
QList<SymbolInfo> findSymbols(
|
||||||
|
const QString &symbolName,
|
||||||
|
SymbolType type,
|
||||||
|
const QString &scopeFilter,
|
||||||
|
bool caseSensitive,
|
||||||
|
bool useRegex,
|
||||||
|
bool useWildcard) const;
|
||||||
|
QString formatResults(
|
||||||
|
const QList<SymbolInfo> &symbols,
|
||||||
|
bool includeCode,
|
||||||
|
const QString &groupBy) const;
|
||||||
|
SymbolType parseSymbolType(const QString &typeStr) const;
|
||||||
|
|
||||||
|
void searchInScope(
|
||||||
|
CPlusPlus::Scope *scope,
|
||||||
|
const QString &symbolName,
|
||||||
|
SymbolType searchType,
|
||||||
|
const QString &scopeFilter,
|
||||||
|
const QString &filePath,
|
||||||
|
const CPlusPlus::Overview &overview,
|
||||||
|
const QString ¤tScope,
|
||||||
|
bool caseSensitive,
|
||||||
|
bool useRegex,
|
||||||
|
bool useWildcard,
|
||||||
|
QRegularExpression *searchPattern,
|
||||||
|
QList<SymbolInfo> &results) const;
|
||||||
|
|
||||||
|
bool matchesType(CPlusPlus::Symbol *symbol, SymbolType type) const;
|
||||||
|
bool matchesScopeFilter(const QString &fullScope, const QString &scopeFilter) const;
|
||||||
|
bool matchesSymbolName(
|
||||||
|
const QString &symbolName,
|
||||||
|
const QString &searchPattern,
|
||||||
|
bool caseSensitive,
|
||||||
|
bool useRegex,
|
||||||
|
bool useWildcard,
|
||||||
|
QRegularExpression *regex) const;
|
||||||
|
|
||||||
|
SymbolInfo createSymbolInfo(
|
||||||
|
CPlusPlus::Symbol *symbol,
|
||||||
|
const QString &filePath,
|
||||||
|
const QString &fullScope,
|
||||||
|
const CPlusPlus::Overview &overview) const;
|
||||||
|
|
||||||
|
QString extractSymbolCode(const SymbolInfo &info) const;
|
||||||
|
QString extractCodeFromFile(const QString &filePath, int startLine, int endLine) const;
|
||||||
|
int findSymbolEndLine(const QString &filePath, int startLine, SymbolType type) const;
|
||||||
|
QString buildFullScope(const QString ¤tScope, const QString &symbolName) const;
|
||||||
|
QString formatSymbolInfo(const SymbolInfo &info, bool includeCode, int indentLevel) const;
|
||||||
|
|
||||||
|
Context::IgnoreManager *m_ignoreManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Tools
|
||||||
@ -49,7 +49,8 @@ QString ListProjectFilesTool::stringName() const
|
|||||||
QString ListProjectFilesTool::description() const
|
QString ListProjectFilesTool::description() const
|
||||||
{
|
{
|
||||||
return "Get a list of all source files in the current project. "
|
return "Get a list of all source files in the current project. "
|
||||||
"Returns a structured list of files with their relative paths from the project root. "
|
"Returns a structured list of files with the absolute path to the project root "
|
||||||
|
"and relative paths for each file. "
|
||||||
"Useful for understanding project structure and finding specific files. "
|
"Useful for understanding project structure and finding specific files. "
|
||||||
"No parameters required.";
|
"No parameters required.";
|
||||||
}
|
}
|
||||||
@ -107,6 +108,7 @@ QFuture<QString> ListProjectFilesTool::executeAsync(const QJsonObject &input)
|
|||||||
|
|
||||||
QStringList fileList;
|
QStringList fileList;
|
||||||
QString projectPath = project->projectDirectory().toUrlishString();
|
QString projectPath = project->projectDirectory().toUrlishString();
|
||||||
|
QString projectAbsolutePath = project->projectDirectory().toFSPathString();
|
||||||
|
|
||||||
for (const auto &filePath : projectFiles) {
|
for (const auto &filePath : projectFiles) {
|
||||||
QString absolutePath = filePath.toUrlishString();
|
QString absolutePath = filePath.toUrlishString();
|
||||||
@ -132,6 +134,7 @@ QFuture<QString> ListProjectFilesTool::executeAsync(const QJsonObject &input)
|
|||||||
result += QString("Project '%1' (%2 files):\n")
|
result += QString("Project '%1' (%2 files):\n")
|
||||||
.arg(project->displayName())
|
.arg(project->displayName())
|
||||||
.arg(fileList.size());
|
.arg(fileList.size());
|
||||||
|
result += QString("Project root: %1\n\n").arg(projectAbsolutePath);
|
||||||
for (const QString &file : fileList) {
|
for (const QString &file : fileList) {
|
||||||
result += QString("- %1\n").arg(file);
|
result += QString("- %1\n").arg(file);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ReadProjectFileByNameTool.hpp"
|
#include "ReadFileByPathTool.hpp"
|
||||||
|
|
||||||
#include <coreplugin/documentmanager.h>
|
#include <coreplugin/documentmanager.h>
|
||||||
#include <logger/Logger.hpp>
|
#include <logger/Logger.hpp>
|
||||||
@ -46,21 +46,18 @@ QString ReadProjectFileByNameTool::name() const
|
|||||||
|
|
||||||
QString ReadProjectFileByNameTool::stringName() const
|
QString ReadProjectFileByNameTool::stringName() const
|
||||||
{
|
{
|
||||||
return {"Reading project file by name"};
|
return {"Reading project file"};
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ReadProjectFileByNameTool::description() const
|
QString ReadProjectFileByNameTool::description() const
|
||||||
{
|
{
|
||||||
return "Read the content of a specific file from the current project by providing its filename "
|
return "Read the content of a specific file from the current project by providing its "
|
||||||
"or "
|
"absolute file path. "
|
||||||
"relative path. This tool searches for files within the project scope and supports:\n"
|
"The file must exist, be within the project scope, and not excluded by "
|
||||||
"- Exact filename match (e.g., 'main.cpp')\n"
|
".qodeassistignore.\n"
|
||||||
"- Relative path from project root (e.g., 'src/utils/helper.cpp')\n"
|
"Input parameter: 'filename' - the absolute path to the file (e.g., "
|
||||||
"- Partial path matching (e.g., 'utils/helper.cpp')\n"
|
"'/path/to/project/src/main.cpp').\n"
|
||||||
"- Case-insensitive filename search as fallback\n"
|
"Use 'list_project_files' tool first to get the exact file paths.";
|
||||||
"Input parameter: 'filename' - the name or path of the file to read.\n"
|
|
||||||
"Use this when you need to examine specific project files that are not currently open "
|
|
||||||
"in the editor.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject ReadProjectFileByNameTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
QJsonObject ReadProjectFileByNameTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
||||||
@ -68,7 +65,7 @@ QJsonObject ReadProjectFileByNameTool::getDefinition(LLMCore::ToolSchemaFormat f
|
|||||||
QJsonObject properties;
|
QJsonObject properties;
|
||||||
QJsonObject filenameProperty;
|
QJsonObject filenameProperty;
|
||||||
filenameProperty["type"] = "string";
|
filenameProperty["type"] = "string";
|
||||||
filenameProperty["description"] = "The filename or relative path to read";
|
filenameProperty["description"] = "The absolute file path to read";
|
||||||
properties["filename"] = filenameProperty;
|
properties["filename"] = filenameProperty;
|
||||||
|
|
||||||
QJsonObject definition;
|
QJsonObject definition;
|
||||||
@ -101,44 +98,52 @@ LLMCore::ToolPermissions ReadProjectFileByNameTool::requiredPermissions() const
|
|||||||
QFuture<QString> ReadProjectFileByNameTool::executeAsync(const QJsonObject &input)
|
QFuture<QString> ReadProjectFileByNameTool::executeAsync(const QJsonObject &input)
|
||||||
{
|
{
|
||||||
return QtConcurrent::run([this, input]() -> QString {
|
return QtConcurrent::run([this, input]() -> QString {
|
||||||
QString filename = input["filename"].toString();
|
QString filePath = input["filename"].toString();
|
||||||
if (filename.isEmpty()) {
|
if (filePath.isEmpty()) {
|
||||||
QString error = "Error: filename parameter is required";
|
QString error = "Error: filename parameter is required";
|
||||||
throw std::invalid_argument(error.toStdString());
|
throw std::invalid_argument(error.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString filePath = findFileInProject(filename);
|
// Validate that file exists
|
||||||
if (filePath.isEmpty()) {
|
QFileInfo fileInfo(filePath);
|
||||||
QString error = QString("Error: File '%1' not found").arg(filename);
|
if (!fileInfo.exists() || !fileInfo.isFile()) {
|
||||||
|
QString error = QString("Error: File '%1' does not exist").arg(filePath);
|
||||||
throw std::runtime_error(error.toStdString());
|
throw std::runtime_error(error.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString canonicalPath = fileInfo.canonicalFilePath();
|
||||||
|
|
||||||
|
// Check if file is part of the project
|
||||||
|
if (!isFileInProject(canonicalPath)) {
|
||||||
|
QString error = QString("Error: File '%1' is not part of the project").arg(filePath);
|
||||||
|
throw std::runtime_error(error.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file is ignored
|
||||||
auto project = ProjectExplorer::ProjectManager::projectForFile(
|
auto project = ProjectExplorer::ProjectManager::projectForFile(
|
||||||
Utils::FilePath::fromString(filePath));
|
Utils::FilePath::fromString(canonicalPath));
|
||||||
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
|
if (project && m_ignoreManager->shouldIgnore(canonicalPath, project)) {
|
||||||
QString error
|
QString error
|
||||||
= QString("Error: File '%1' is excluded by .qodeassistignore").arg(filename);
|
= QString("Error: File '%1' is excluded by .qodeassistignore").arg(filePath);
|
||||||
throw std::runtime_error(error.toStdString());
|
throw std::runtime_error(error.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString content = readFileContent(filePath);
|
// Read file content
|
||||||
|
QString content = readFileContent(canonicalPath);
|
||||||
if (content.isNull()) {
|
if (content.isNull()) {
|
||||||
QString error = QString("Error: Could not read file '%1'").arg(filePath);
|
QString error = QString("Error: Could not read file '%1'").arg(canonicalPath);
|
||||||
throw std::runtime_error(error.toStdString());
|
throw std::runtime_error(error.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString result = QString("File: %1\n\nContent:\n%2").arg(filePath, content);
|
QString result = QString("File: %1\n\nContent:\n%2").arg(canonicalPath, content);
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ReadProjectFileByNameTool::findFileInProject(const QString &fileName) const
|
bool ReadProjectFileByNameTool::isFileInProject(const QString &filePath) const
|
||||||
{
|
{
|
||||||
QList<ProjectExplorer::Project *> projects = ProjectExplorer::ProjectManager::projects();
|
QList<ProjectExplorer::Project *> projects = ProjectExplorer::ProjectManager::projects();
|
||||||
if (projects.isEmpty()) {
|
Utils::FilePath targetPath = Utils::FilePath::fromString(filePath);
|
||||||
LOG_MESSAGE("No projects found");
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto project : projects) {
|
for (auto project : projects) {
|
||||||
if (!project)
|
if (!project)
|
||||||
@ -147,45 +152,13 @@ QString ReadProjectFileByNameTool::findFileInProject(const QString &fileName) co
|
|||||||
Utils::FilePaths projectFiles = project->files(ProjectExplorer::Project::SourceFiles);
|
Utils::FilePaths projectFiles = project->files(ProjectExplorer::Project::SourceFiles);
|
||||||
|
|
||||||
for (const auto &projectFile : std::as_const(projectFiles)) {
|
for (const auto &projectFile : std::as_const(projectFiles)) {
|
||||||
QString absolutePath = projectFile.path();
|
if (projectFile == targetPath) {
|
||||||
|
return true;
|
||||||
if (m_ignoreManager->shouldIgnore(absolutePath, project)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFileInfo fileInfo(absolutePath);
|
|
||||||
if (fileInfo.fileName() == fileName) {
|
|
||||||
return absolutePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &projectFile : std::as_const(projectFiles)) {
|
|
||||||
QString absolutePath = projectFile.path();
|
|
||||||
|
|
||||||
if (m_ignoreManager->shouldIgnore(absolutePath, project)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projectFile.endsWith(fileName)) {
|
|
||||||
return absolutePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &projectFile : std::as_const(projectFiles)) {
|
|
||||||
QString absolutePath = projectFile.path();
|
|
||||||
|
|
||||||
if (m_ignoreManager->shouldIgnore(absolutePath, project)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFileInfo fileInfo(absolutePath);
|
|
||||||
if (fileInfo.fileName().contains(fileName, Qt::CaseInsensitive)) {
|
|
||||||
return absolutePath;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QString();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ReadProjectFileByNameTool::readFileContent(const QString &filePath) const
|
QString ReadProjectFileByNameTool::readFileContent(const QString &filePath) const
|
||||||
@ -39,8 +39,8 @@ public:
|
|||||||
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
|
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString findFileInProject(const QString &fileName) const;
|
|
||||||
QString readFileContent(const QString &filePath) const;
|
QString readFileContent(const QString &filePath) const;
|
||||||
|
bool isFileInProject(const QString &filePath) const;
|
||||||
Context::IgnoreManager *m_ignoreManager;
|
Context::IgnoreManager *m_ignoreManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,9 +25,10 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "EditProjectFileTool.hpp"
|
#include "EditProjectFileTool.hpp"
|
||||||
|
#include "FindSymbolTool.hpp"
|
||||||
#include "GetIssuesListTool.hpp"
|
#include "GetIssuesListTool.hpp"
|
||||||
#include "ListProjectFilesTool.hpp"
|
#include "ListProjectFilesTool.hpp"
|
||||||
#include "ReadProjectFileByNameTool.hpp"
|
#include "ReadFileByPathTool.hpp"
|
||||||
#include "ReadVisibleFilesTool.hpp"
|
#include "ReadVisibleFilesTool.hpp"
|
||||||
#include "SearchInProjectTool.hpp"
|
#include "SearchInProjectTool.hpp"
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ void ToolsFactory::registerTools()
|
|||||||
registerTool(new SearchInProjectTool(this));
|
registerTool(new SearchInProjectTool(this));
|
||||||
registerTool(new GetIssuesListTool(this));
|
registerTool(new GetIssuesListTool(this));
|
||||||
registerTool(new EditProjectFileTool(this));
|
registerTool(new EditProjectFileTool(this));
|
||||||
|
registerTool(new FindSymbolTool(this));
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Registered %1 tools").arg(m_tools.size()));
|
LOG_MESSAGE(QString("Registered %1 tools").arg(m_tools.size()));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user