refactor: Find symbol tool return only url and line

This commit is contained in:
Petr Mironychev
2025-10-23 15:07:22 +02:00
parent 5dc28fc1ad
commit cab8718979
2 changed files with 23 additions and 288 deletions

View File

@ -27,14 +27,9 @@
#include <logger/Logger.hpp> #include <logger/Logger.hpp>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h> #include <projectexplorer/projectmanager.h>
#include <texteditor/textdocument.h>
#include <utils/filepath.h> #include <utils/filepath.h>
#include <QFile>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextStream>
#include <QtConcurrent> #include <QtConcurrent>
namespace QodeAssist::Tools { namespace QodeAssist::Tools {
@ -56,8 +51,10 @@ QString FindSymbolTool::stringName() const
QString FindSymbolTool::description() const QString FindSymbolTool::description() const
{ {
return "Find C++ symbols (classes, functions, enums, variables, typedefs, namespaces) in the project. " return "Find C++ symbols (classes, functions, enums, variables, typedefs, namespaces) in the "
"Returns file paths, line numbers, qualified names, and optionally source code. " "project. "
"Returns file paths and line numbers where symbols are defined. "
"Use read_project_file_by_path to read the actual code. "
"Supports exact match, wildcard patterns, and regular expressions."; "Supports exact match, wildcard patterns, and regular expressions.";
} }
@ -81,8 +78,7 @@ QJsonObject FindSymbolTool::getDefinition(LLMCore::ToolSchemaFormat format) cons
QJsonObject scopeFilterProperty; QJsonObject scopeFilterProperty;
scopeFilterProperty["type"] = "string"; scopeFilterProperty["type"] = "string";
scopeFilterProperty["description"] scopeFilterProperty["description"] = "Filter results by scope (e.g., 'MyNamespace', 'MyClass')";
= "Filter results by scope (e.g., 'MyNamespace', 'MyClass')";
properties["scope_filter"] = scopeFilterProperty; properties["scope_filter"] = scopeFilterProperty;
QJsonObject caseSensitiveProperty; QJsonObject caseSensitiveProperty;
@ -92,8 +88,7 @@ QJsonObject FindSymbolTool::getDefinition(LLMCore::ToolSchemaFormat format) cons
QJsonObject useRegexProperty; QJsonObject useRegexProperty;
useRegexProperty["type"] = "boolean"; useRegexProperty["type"] = "boolean";
useRegexProperty["description"] useRegexProperty["description"] = "Treat symbol_name as regular expression (default: false)";
= "Treat symbol_name as regular expression (default: false)";
properties["use_regex"] = useRegexProperty; properties["use_regex"] = useRegexProperty;
QJsonObject useWildcardProperty; QJsonObject useWildcardProperty;
@ -102,22 +97,12 @@ QJsonObject FindSymbolTool::getDefinition(LLMCore::ToolSchemaFormat format) cons
= "Treat symbol_name as wildcard pattern like 'find*', '*Symbol' (default: false)"; = "Treat symbol_name as wildcard pattern like 'find*', '*Symbol' (default: false)";
properties["use_wildcard"] = useWildcardProperty; properties["use_wildcard"] = useWildcardProperty;
QJsonObject includeCodeProperty;
includeCodeProperty["type"] = "boolean";
includeCodeProperty["description"] = "Include source code of found symbols";
properties["include_code"] = includeCodeProperty;
QJsonObject maxResultsProperty; QJsonObject maxResultsProperty;
maxResultsProperty["type"] = "integer"; maxResultsProperty["type"] = "integer";
maxResultsProperty["description"] = "Maximum number of results to return"; maxResultsProperty["description"] = "Maximum number of results to return (default: 50)";
maxResultsProperty["default"] = 50;
properties["max_results"] = maxResultsProperty; 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; QJsonObject definition;
definition["type"] = "object"; definition["type"] = "object";
definition["properties"] = properties; definition["properties"] = properties;
@ -151,9 +136,7 @@ QFuture<QString> FindSymbolTool::executeAsync(const QJsonObject &input)
bool caseSensitive = input["case_sensitive"].toBool(true); bool caseSensitive = input["case_sensitive"].toBool(true);
bool useRegex = input["use_regex"].toBool(false); bool useRegex = input["use_regex"].toBool(false);
bool useWildcard = input["use_wildcard"].toBool(false); bool useWildcard = input["use_wildcard"].toBool(false);
bool includeCode = input["include_code"].toBool(false); int maxResults = input["max_results"].toInt(50);
int maxResults = input["max_results"].toInt(10);
QString groupBy = input["group_by"].toString("type");
if (symbolName.isEmpty()) { if (symbolName.isEmpty()) {
QString error = "Error: 'symbol_name' parameter is required"; QString error = "Error: 'symbol_name' parameter is required";
@ -168,7 +151,8 @@ QFuture<QString> FindSymbolTool::executeAsync(const QJsonObject &input)
} }
SymbolType type = parseSymbolType(symbolTypeStr); SymbolType type = parseSymbolType(symbolTypeStr);
LOG_MESSAGE(QString("Searching for symbol: '%1', type: %2, scope: '%3', " LOG_MESSAGE(QString(
"Searching for symbol: '%1', type: %2, scope: '%3', "
"case_sensitive: %4, regex: %5, wildcard: %6") "case_sensitive: %4, regex: %5, wildcard: %6")
.arg(symbolName, symbolTypeStr, scopeFilter) .arg(symbolName, symbolTypeStr, scopeFilter)
.arg(caseSensitive) .arg(caseSensitive)
@ -190,13 +174,7 @@ QFuture<QString> FindSymbolTool::executeAsync(const QJsonObject &input)
symbols = symbols.mid(0, maxResults); symbols = symbols.mid(0, maxResults);
} }
if (includeCode) { return formatResults(symbols);
for (SymbolInfo &info : symbols) {
info.code = extractSymbolCode(info);
}
}
return formatResults(symbols, includeCode, groupBy);
}); });
} }
@ -421,270 +399,44 @@ FindSymbolTool::SymbolInfo FindSymbolTool::createSymbolInfo(
const QString &fullScope, const QString &fullScope,
const CPlusPlus::Overview &overview) const const CPlusPlus::Overview &overview) const
{ {
Q_UNUSED(fullScope)
Q_UNUSED(overview)
SymbolInfo info; SymbolInfo info;
info.name = overview.prettyName(symbol->name());
info.filePath = filePath; info.filePath = filePath;
info.line = symbol->line(); info.line = symbol->line();
info.scope = fullScope;
// Build qualified name // Determine symbol type
if (fullScope.isEmpty()) {
info.qualifiedName = info.name;
} else {
info.qualifiedName = fullScope + "::" + info.name;
}
// Determine symbol type and extract additional information
if (symbol->asClass()) { if (symbol->asClass()) {
info.type = SymbolType::Class; info.type = SymbolType::Class;
info.typeString = "Class"; } else if (symbol->asFunction()) {
info.endLine = findSymbolEndLine(filePath, symbol->line(), SymbolType::Class);
} else if (auto *function = symbol->asFunction()) {
info.type = SymbolType::Function; 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()) { } else if (symbol->asEnum()) {
info.type = SymbolType::Enum; info.type = SymbolType::Enum;
info.typeString = "Enum";
info.endLine = findSymbolEndLine(filePath, symbol->line(), SymbolType::Enum);
} else if (symbol->asNamespace()) { } else if (symbol->asNamespace()) {
info.type = SymbolType::Namespace; info.type = SymbolType::Namespace;
info.typeString = "Namespace";
info.endLine = symbol->line(); // Namespaces can span multiple files
} else if (auto *declaration = symbol->asDeclaration()) { } else if (auto *declaration = symbol->asDeclaration()) {
if (declaration->isTypedef()) { if (declaration->isTypedef()) {
info.type = SymbolType::Typedef; info.type = SymbolType::Typedef;
info.typeString = "Typedef";
info.signature = overview.prettyType(symbol->type());
} else { } else {
info.type = SymbolType::Variable; info.type = SymbolType::Variable;
info.typeString = "Variable";
info.signature = overview.prettyType(symbol->type());
info.isStatic = declaration->isStatic();
} }
info.endLine = symbol->line();
} else { } else {
info.typeString = "Symbol"; info.type = SymbolType::All;
info.endLine = symbol->line();
} }
return info; return info;
} }
int FindSymbolTool::findSymbolEndLine(const QString &filePath, int startLine, SymbolType type) const QString FindSymbolTool::formatResults(const QList<SymbolInfo> &symbols) 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()); 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) { for (const SymbolInfo &info : symbols) {
grouped[info.filePath].append(info); output += QString("Path: %1\nLine:%2\n").arg(info.filePath).arg(info.line);
}
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(); 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 } // namespace QodeAssist::Tools

View File

@ -51,19 +51,9 @@ private:
struct SymbolInfo struct SymbolInfo
{ {
QString name;
QString qualifiedName;
QString filePath; QString filePath;
int line; int line;
int endLine;
QString scope;
SymbolType type; SymbolType type;
QString typeString;
QString signature;
QString code;
bool isConst = false;
bool isStatic = false;
bool isVirtual = false;
}; };
QList<SymbolInfo> findSymbols( QList<SymbolInfo> findSymbols(
@ -73,10 +63,7 @@ private:
bool caseSensitive, bool caseSensitive,
bool useRegex, bool useRegex,
bool useWildcard) const; bool useWildcard) const;
QString formatResults( QString formatResults(const QList<SymbolInfo> &symbols) const;
const QList<SymbolInfo> &symbols,
bool includeCode,
const QString &groupBy) const;
SymbolType parseSymbolType(const QString &typeStr) const; SymbolType parseSymbolType(const QString &typeStr) const;
void searchInScope( void searchInScope(
@ -109,11 +96,7 @@ private:
const QString &fullScope, const QString &fullScope,
const CPlusPlus::Overview &overview) const; 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 &currentScope, const QString &symbolName) const; QString buildFullScope(const QString &currentScope, const QString &symbolName) const;
QString formatSymbolInfo(const SymbolInfo &info, bool includeCode, int indentLevel) const;
Context::IgnoreManager *m_ignoreManager; Context::IgnoreManager *m_ignoreManager;
}; };