mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2025-11-13 13:32:55 -05:00
refactor: Find symbol tool return only url and line
This commit is contained in:
@ -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
|
||||||
|
|||||||
@ -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 ¤tScope, const QString &symbolName) const;
|
QString buildFullScope(const QString ¤tScope, const QString &symbolName) const;
|
||||||
QString formatSymbolInfo(const SymbolInfo &info, bool includeCode, int indentLevel) const;
|
|
||||||
|
|
||||||
Context::IgnoreManager *m_ignoreManager;
|
Context::IgnoreManager *m_ignoreManager;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user