mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2025-11-13 13:32:55 -05:00
refactor: Optimize searching tools
refactor: Merge read and find tool
This commit is contained in:
@ -115,18 +115,15 @@ 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/ReadFilesByPathTool.hpp tools/ReadFilesByPathTool.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
|
||||||
tools/ToolsManager.hpp tools/ToolsManager.cpp
|
tools/ToolsManager.hpp tools/ToolsManager.cpp
|
||||||
tools/SearchInProjectTool.hpp tools/SearchInProjectTool.cpp
|
|
||||||
tools/GetIssuesListTool.hpp tools/GetIssuesListTool.cpp
|
tools/GetIssuesListTool.hpp tools/GetIssuesListTool.cpp
|
||||||
|
|
||||||
tools/FindSymbolTool.hpp tools/FindSymbolTool.cpp
|
|
||||||
tools/FindFileTool.hpp tools/FindFileTool.cpp
|
|
||||||
tools/CreateNewFileTool.hpp tools/CreateNewFileTool.cpp
|
tools/CreateNewFileTool.hpp tools/CreateNewFileTool.cpp
|
||||||
tools/BuildProjectTool.hpp tools/BuildProjectTool.cpp
|
tools/BuildProjectTool.hpp tools/BuildProjectTool.cpp
|
||||||
|
tools/ProjectSearchTool.hpp tools/ProjectSearchTool.cpp
|
||||||
|
tools/FindAndReadFileTool.hpp tools/FindAndReadFileTool.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
|
||||||
|
|||||||
359
tools/FindAndReadFileTool.cpp
Normal file
359
tools/FindAndReadFileTool.cpp
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
/*
|
||||||
|
* 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 "FindAndReadFileTool.hpp"
|
||||||
|
#include "ToolExceptions.hpp"
|
||||||
|
|
||||||
|
#include <context/ProjectUtils.hpp>
|
||||||
|
#include <logger/Logger.hpp>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <settings/GeneralSettings.hpp>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
|
FindAndReadFileTool::FindAndReadFileTool(QObject *parent)
|
||||||
|
: BaseTool(parent)
|
||||||
|
, m_ignoreManager(new Context::IgnoreManager(this))
|
||||||
|
{}
|
||||||
|
|
||||||
|
QString FindAndReadFileTool::name() const
|
||||||
|
{
|
||||||
|
return "find_and_read_file";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindAndReadFileTool::stringName() const
|
||||||
|
{
|
||||||
|
return "Finding and reading file";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindAndReadFileTool::description() const
|
||||||
|
{
|
||||||
|
return "Search for a file by name/path and optionally read its content. "
|
||||||
|
"Returns the best matching file and its content.";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject FindAndReadFileTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
||||||
|
{
|
||||||
|
QJsonObject properties;
|
||||||
|
|
||||||
|
properties["query"] = QJsonObject{
|
||||||
|
{"type", "string"},
|
||||||
|
{"description", "Filename, partial name, or path to search for (case-insensitive)"}};
|
||||||
|
|
||||||
|
properties["file_pattern"] = QJsonObject{
|
||||||
|
{"type", "string"}, {"description", "File pattern filter (e.g., '*.cpp', '*.h', '*.qml')"}};
|
||||||
|
|
||||||
|
properties["read_content"] = QJsonObject{
|
||||||
|
{"type", "boolean"},
|
||||||
|
{"description", "Read file content in addition to finding path (default: true)"}};
|
||||||
|
|
||||||
|
QJsonObject definition;
|
||||||
|
definition["type"] = "object";
|
||||||
|
definition["properties"] = properties;
|
||||||
|
definition["required"] = QJsonArray{"query"};
|
||||||
|
|
||||||
|
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 FindAndReadFileTool::requiredPermissions() const
|
||||||
|
{
|
||||||
|
return LLMCore::ToolPermission::FileSystemRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<QString> FindAndReadFileTool::executeAsync(const QJsonObject &input)
|
||||||
|
{
|
||||||
|
return QtConcurrent::run([this, input]() -> QString {
|
||||||
|
QString query = input["query"].toString().trimmed();
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
throw ToolInvalidArgument("Query parameter is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString filePattern = input["file_pattern"].toString();
|
||||||
|
bool readContent = input["read_content"].toBool(true);
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("FindAndReadFileTool: Searching for '%1' (pattern: %2, read: %3)")
|
||||||
|
.arg(query, filePattern.isEmpty() ? "none" : filePattern)
|
||||||
|
.arg(readContent));
|
||||||
|
|
||||||
|
FileMatch bestMatch = findBestMatch(query, filePattern, 10);
|
||||||
|
|
||||||
|
if (bestMatch.absolutePath.isEmpty()) {
|
||||||
|
return QString("No file found matching '%1'").arg(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readContent) {
|
||||||
|
bestMatch.content = readFileContent(bestMatch.absolutePath);
|
||||||
|
if (bestMatch.content.isNull()) {
|
||||||
|
bestMatch.error = "Could not read file";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatResult(bestMatch, readContent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
FindAndReadFileTool::FileMatch FindAndReadFileTool::findBestMatch(
|
||||||
|
const QString &query, const QString &filePattern, int maxResults)
|
||||||
|
{
|
||||||
|
QList<FileMatch> candidates;
|
||||||
|
auto projects = ProjectExplorer::ProjectManager::projects();
|
||||||
|
|
||||||
|
if (projects.isEmpty()) {
|
||||||
|
return FileMatch{};
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo queryInfo(query);
|
||||||
|
if (queryInfo.isAbsolute() && queryInfo.exists() && queryInfo.isFile()) {
|
||||||
|
FileMatch match;
|
||||||
|
match.absolutePath = queryInfo.canonicalFilePath();
|
||||||
|
|
||||||
|
for (auto project : projects) {
|
||||||
|
if (!project)
|
||||||
|
continue;
|
||||||
|
QString projectDir = project->projectDirectory().path();
|
||||||
|
if (match.absolutePath.startsWith(projectDir)) {
|
||||||
|
match.relativePath = QDir(projectDir).relativeFilePath(match.absolutePath);
|
||||||
|
match.projectName = project->displayName();
|
||||||
|
match.matchType = MatchType::ExactName;
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match.relativePath = queryInfo.fileName();
|
||||||
|
match.projectName = "External";
|
||||||
|
match.matchType = MatchType::ExactName;
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString lowerQuery = query.toLower();
|
||||||
|
|
||||||
|
for (auto project : projects) {
|
||||||
|
if (!project)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto projectFiles = project->files(ProjectExplorer::Project::SourceFiles);
|
||||||
|
QString projectDir = project->projectDirectory().path();
|
||||||
|
QString projectName = project->displayName();
|
||||||
|
|
||||||
|
for (const auto &filePath : projectFiles) {
|
||||||
|
QString absolutePath = filePath.path();
|
||||||
|
if (m_ignoreManager->shouldIgnore(absolutePath, project))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QFileInfo fileInfo(absolutePath);
|
||||||
|
QString fileName = fileInfo.fileName();
|
||||||
|
|
||||||
|
if (!filePattern.isEmpty() && !matchesFilePattern(fileName, filePattern))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QString relativePath = QDir(projectDir).relativeFilePath(absolutePath);
|
||||||
|
|
||||||
|
FileMatch match;
|
||||||
|
match.absolutePath = absolutePath;
|
||||||
|
match.relativePath = relativePath;
|
||||||
|
match.projectName = projectName;
|
||||||
|
|
||||||
|
QString lowerFileName = fileName.toLower();
|
||||||
|
QString lowerRelativePath = relativePath.toLower();
|
||||||
|
|
||||||
|
if (lowerFileName == lowerQuery) {
|
||||||
|
match.matchType = MatchType::ExactName;
|
||||||
|
candidates.append(match);
|
||||||
|
} else if (lowerRelativePath.contains(lowerQuery)) {
|
||||||
|
match.matchType = MatchType::PathMatch;
|
||||||
|
candidates.append(match);
|
||||||
|
} else if (lowerFileName.contains(lowerQuery)) {
|
||||||
|
match.matchType = MatchType::PartialName;
|
||||||
|
candidates.append(match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.isEmpty() || candidates.first().matchType != MatchType::ExactName) {
|
||||||
|
for (auto project : projects) {
|
||||||
|
if (!project)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QString projectDir = project->projectDirectory().path();
|
||||||
|
QString projectName = project->displayName();
|
||||||
|
int depth = 0;
|
||||||
|
searchInFileSystem(
|
||||||
|
projectDir,
|
||||||
|
lowerQuery,
|
||||||
|
projectName,
|
||||||
|
projectDir,
|
||||||
|
project,
|
||||||
|
candidates,
|
||||||
|
maxResults,
|
||||||
|
depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.isEmpty()) {
|
||||||
|
return FileMatch{};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(candidates.begin(), candidates.end());
|
||||||
|
return candidates.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindAndReadFileTool::searchInFileSystem(
|
||||||
|
const QString &dirPath,
|
||||||
|
const QString &query,
|
||||||
|
const QString &projectName,
|
||||||
|
const QString &projectDir,
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
QList<FileMatch> &matches,
|
||||||
|
int maxResults,
|
||||||
|
int ¤tDepth,
|
||||||
|
int maxDepth)
|
||||||
|
{
|
||||||
|
if (currentDepth >= maxDepth || matches.size() >= maxResults)
|
||||||
|
return;
|
||||||
|
|
||||||
|
currentDepth++;
|
||||||
|
QDir dir(dirPath);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
currentDepth--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto entries = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
for (const auto &entry : entries) {
|
||||||
|
if (matches.size() >= maxResults)
|
||||||
|
break;
|
||||||
|
|
||||||
|
QString absolutePath = entry.absoluteFilePath();
|
||||||
|
if (m_ignoreManager->shouldIgnore(absolutePath, project))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QString fileName = entry.fileName();
|
||||||
|
|
||||||
|
if (entry.isDir()) {
|
||||||
|
searchInFileSystem(
|
||||||
|
absolutePath,
|
||||||
|
query,
|
||||||
|
projectName,
|
||||||
|
projectDir,
|
||||||
|
project,
|
||||||
|
matches,
|
||||||
|
maxResults,
|
||||||
|
currentDepth,
|
||||||
|
maxDepth);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString lowerFileName = fileName.toLower();
|
||||||
|
QString relativePath = QDir(projectDir).relativeFilePath(absolutePath);
|
||||||
|
QString lowerRelativePath = relativePath.toLower();
|
||||||
|
|
||||||
|
FileMatch match;
|
||||||
|
match.absolutePath = absolutePath;
|
||||||
|
match.relativePath = relativePath;
|
||||||
|
match.projectName = projectName;
|
||||||
|
|
||||||
|
if (lowerFileName == query) {
|
||||||
|
match.matchType = MatchType::ExactName;
|
||||||
|
matches.append(match);
|
||||||
|
} else if (lowerRelativePath.contains(query)) {
|
||||||
|
match.matchType = MatchType::PathMatch;
|
||||||
|
matches.append(match);
|
||||||
|
} else if (lowerFileName.contains(query)) {
|
||||||
|
match.matchType = MatchType::PartialName;
|
||||||
|
matches.append(match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDepth--;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindAndReadFileTool::matchesFilePattern(const QString &fileName, const QString &pattern) const
|
||||||
|
{
|
||||||
|
if (pattern.isEmpty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (pattern.startsWith("*.")) {
|
||||||
|
QString extension = pattern.mid(1);
|
||||||
|
return fileName.endsWith(extension, Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName.compare(pattern, Qt::CaseInsensitive) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindAndReadFileTool::readFileContent(const QString &filePath) const
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString canonicalPath = QFileInfo(filePath).canonicalFilePath();
|
||||||
|
bool isInProject = Context::ProjectUtils::isFileInProject(canonicalPath);
|
||||||
|
|
||||||
|
if (!isInProject) {
|
||||||
|
const auto &settings = Settings::generalSettings();
|
||||||
|
if (!settings.allowAccessOutsideProject()) {
|
||||||
|
LOG_MESSAGE(QString("Access denied to file outside project: %1").arg(canonicalPath));
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
LOG_MESSAGE(QString("Reading file outside project scope: %1").arg(canonicalPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream stream(&file);
|
||||||
|
stream.setAutoDetectUnicode(true);
|
||||||
|
QString content = stream.readAll();
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FindAndReadFileTool::formatResult(const FileMatch &match, bool readContent) const
|
||||||
|
{
|
||||||
|
QString result
|
||||||
|
= QString("Found file: %1\nAbsolute path: %2").arg(match.relativePath, match.absolutePath);
|
||||||
|
|
||||||
|
if (readContent) {
|
||||||
|
if (!match.error.isEmpty()) {
|
||||||
|
result += QString("\nError: %1").arg(match.error);
|
||||||
|
} else {
|
||||||
|
result += QString("\n\n=== Content ===\n%1").arg(match.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Tools
|
||||||
@ -21,42 +21,48 @@
|
|||||||
|
|
||||||
#include <context/IgnoreManager.hpp>
|
#include <context/IgnoreManager.hpp>
|
||||||
#include <llmcore/BaseTool.hpp>
|
#include <llmcore/BaseTool.hpp>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
namespace QodeAssist::Tools {
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
class FindFileTool : public LLMCore::BaseTool
|
class FindAndReadFileTool : public LLMCore::BaseTool
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FindFileTool(QObject *parent = nullptr);
|
explicit FindAndReadFileTool(QObject *parent = nullptr);
|
||||||
|
|
||||||
QString name() const override;
|
QString name() const override;
|
||||||
QString stringName() const override;
|
QString stringName() const override;
|
||||||
QString description() const override;
|
QString description() const override;
|
||||||
QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override;
|
QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override;
|
||||||
LLMCore::ToolPermissions requiredPermissions() const override;
|
LLMCore::ToolPermissions requiredPermissions() const override;
|
||||||
|
QFuture<QString> executeAsync(const QJsonObject &input) override;
|
||||||
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class MatchType { ExactName, PathMatch, PartialName };
|
||||||
|
|
||||||
struct FileMatch
|
struct FileMatch
|
||||||
{
|
{
|
||||||
QString absolutePath;
|
QString absolutePath;
|
||||||
QString relativePath;
|
QString relativePath;
|
||||||
QString projectName;
|
QString projectName;
|
||||||
enum MatchType { ExactName, PartialName, PathMatch } matchType;
|
QString content;
|
||||||
|
MatchType matchType;
|
||||||
|
bool contentRead = false;
|
||||||
|
QString error;
|
||||||
|
|
||||||
bool operator<(const FileMatch &other) const
|
bool operator<(const FileMatch &other) const
|
||||||
{
|
{
|
||||||
if (matchType != other.matchType) {
|
return static_cast<int>(matchType) < static_cast<int>(other.matchType);
|
||||||
return matchType < other.matchType;
|
|
||||||
}
|
|
||||||
return relativePath < other.relativePath;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
QList<FileMatch> findMatchingFiles(const QString &query, int maxResults) const;
|
FileMatch findBestMatch(const QString &query, const QString &filePattern, int maxResults);
|
||||||
void searchInFileSystem(const QString &dirPath,
|
void searchInFileSystem(
|
||||||
|
const QString &dirPath,
|
||||||
const QString &query,
|
const QString &query,
|
||||||
const QString &projectName,
|
const QString &projectName,
|
||||||
const QString &projectDir,
|
const QString &projectDir,
|
||||||
@ -64,11 +70,11 @@ private:
|
|||||||
QList<FileMatch> &matches,
|
QList<FileMatch> &matches,
|
||||||
int maxResults,
|
int maxResults,
|
||||||
int ¤tDepth,
|
int ¤tDepth,
|
||||||
int maxDepth = 10) const;
|
int maxDepth = 5);
|
||||||
QString formatResults(const QList<FileMatch> &matches, int totalFound, int maxResults) const;
|
|
||||||
bool matchesFilePattern(const QString &fileName, const QString &pattern) const;
|
bool matchesFilePattern(const QString &fileName, const QString &pattern) const;
|
||||||
|
QString readFileContent(const QString &filePath) const;
|
||||||
|
QString formatResult(const FileMatch &match, bool readContent) const;
|
||||||
|
|
||||||
static constexpr int DEFAULT_MAX_RESULTS = 50;
|
|
||||||
Context::IgnoreManager *m_ignoreManager;
|
Context::IgnoreManager *m_ignoreManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1,430 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 "FindFileTool.hpp"
|
|
||||||
#include "ToolExceptions.hpp"
|
|
||||||
|
|
||||||
#include <context/ProjectUtils.hpp>
|
|
||||||
#include <logger/Logger.hpp>
|
|
||||||
#include <projectexplorer/project.h>
|
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
#include <settings/GeneralSettings.hpp>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QtConcurrent>
|
|
||||||
|
|
||||||
namespace QodeAssist::Tools {
|
|
||||||
|
|
||||||
FindFileTool::FindFileTool(QObject *parent)
|
|
||||||
: BaseTool(parent)
|
|
||||||
, m_ignoreManager(new Context::IgnoreManager(this))
|
|
||||||
{}
|
|
||||||
|
|
||||||
QString FindFileTool::name() const
|
|
||||||
{
|
|
||||||
return "find_file";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FindFileTool::stringName() const
|
|
||||||
{
|
|
||||||
return {"Finding file in project"};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FindFileTool::description() const
|
|
||||||
{
|
|
||||||
return "Search for files in the project by filename, partial name, or path. "
|
|
||||||
"Searches both in CMake-registered files and filesystem (finds .gitignore, Python scripts, README, etc.). "
|
|
||||||
"Supports exact/partial filename match, relative/absolute paths, file extension filtering, "
|
|
||||||
"and case-insensitive search. "
|
|
||||||
"Returns matching files with absolute and relative paths.";
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject FindFileTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
|
||||||
{
|
|
||||||
QJsonObject properties;
|
|
||||||
|
|
||||||
QJsonObject queryProperty;
|
|
||||||
queryProperty["type"] = "string";
|
|
||||||
queryProperty["description"]
|
|
||||||
= "The filename, partial filename, or path to search for (case-insensitive). "
|
|
||||||
"Finds ALL files in project directory including .gitignore, README.md, Python scripts, "
|
|
||||||
"config files, etc., even if not in CMake build system";
|
|
||||||
properties["query"] = queryProperty;
|
|
||||||
|
|
||||||
QJsonObject filePatternProperty;
|
|
||||||
filePatternProperty["type"] = "string";
|
|
||||||
filePatternProperty["description"]
|
|
||||||
= "Optional file pattern to filter results (e.g., '*.cpp', '*.h', '*.qml')";
|
|
||||||
properties["file_pattern"] = filePatternProperty;
|
|
||||||
|
|
||||||
QJsonObject maxResultsProperty;
|
|
||||||
maxResultsProperty["type"] = "integer";
|
|
||||||
maxResultsProperty["description"]
|
|
||||||
= "Maximum number of results to return (default: 50, max: 200)";
|
|
||||||
maxResultsProperty["default"] = DEFAULT_MAX_RESULTS;
|
|
||||||
properties["max_results"] = maxResultsProperty;
|
|
||||||
|
|
||||||
QJsonObject definition;
|
|
||||||
definition["type"] = "object";
|
|
||||||
definition["properties"] = properties;
|
|
||||||
|
|
||||||
QJsonArray required;
|
|
||||||
required.append("query");
|
|
||||||
definition["required"] = required;
|
|
||||||
|
|
||||||
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 FindFileTool::requiredPermissions() const
|
|
||||||
{
|
|
||||||
return LLMCore::ToolPermission::FileSystemRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFuture<QString> FindFileTool::executeAsync(const QJsonObject &input)
|
|
||||||
{
|
|
||||||
return QtConcurrent::run([this, input]() -> QString {
|
|
||||||
QString query = input["query"].toString().trimmed();
|
|
||||||
if (query.isEmpty()) {
|
|
||||||
QString error = "Error: query parameter is required and cannot be empty";
|
|
||||||
throw ToolInvalidArgument(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString filePattern = input["file_pattern"].toString().trimmed();
|
|
||||||
int maxResults = input["max_results"].toInt(DEFAULT_MAX_RESULTS);
|
|
||||||
|
|
||||||
maxResults = qBound(1, maxResults, 200);
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("FindFileTool: Searching for '%1'%2 (max: %3)")
|
|
||||||
.arg(query)
|
|
||||||
.arg(
|
|
||||||
filePattern.isEmpty() ? QString("")
|
|
||||||
: QString(" with pattern '%1'").arg(filePattern))
|
|
||||||
.arg(maxResults));
|
|
||||||
|
|
||||||
QFileInfo queryInfo(query);
|
|
||||||
if (queryInfo.isAbsolute() && queryInfo.exists() && queryInfo.isFile()) {
|
|
||||||
QString canonicalPath = queryInfo.canonicalFilePath();
|
|
||||||
bool isInProject = Context::ProjectUtils::isFileInProject(canonicalPath);
|
|
||||||
|
|
||||||
// Check if reading outside project is allowed
|
|
||||||
if (!isInProject) {
|
|
||||||
const auto &settings = Settings::generalSettings();
|
|
||||||
if (!settings.allowAccessOutsideProject()) {
|
|
||||||
QString error = QString("Error: File '%1' exists but is outside the project scope. "
|
|
||||||
"Enable 'Allow file access outside project' in settings to access files outside project scope.")
|
|
||||||
.arg(canonicalPath);
|
|
||||||
throw std::runtime_error(error.toStdString());
|
|
||||||
}
|
|
||||||
LOG_MESSAGE(QString("Finding file outside project scope: %1").arg(canonicalPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto project = isInProject ? ProjectExplorer::ProjectManager::projectForFile(
|
|
||||||
Utils::FilePath::fromString(canonicalPath)) : nullptr;
|
|
||||||
|
|
||||||
if (!isInProject || (project && !m_ignoreManager->shouldIgnore(canonicalPath, project))) {
|
|
||||||
FileMatch match;
|
|
||||||
match.absolutePath = canonicalPath;
|
|
||||||
match.relativePath = isInProject && project
|
|
||||||
? QDir(project->projectDirectory().toFSPathString()).relativeFilePath(canonicalPath)
|
|
||||||
: canonicalPath;
|
|
||||||
match.projectName = isInProject && project ? project->displayName() : "External";
|
|
||||||
match.matchType = FileMatch::PathMatch;
|
|
||||||
|
|
||||||
QList<FileMatch> matches;
|
|
||||||
matches.append(match);
|
|
||||||
return formatResults(matches, 1, maxResults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<FileMatch> matches = findMatchingFiles(query, maxResults);
|
|
||||||
int totalFound = matches.size();
|
|
||||||
|
|
||||||
if (matches.isEmpty()) {
|
|
||||||
QString error = QString(
|
|
||||||
"Error: No files found matching '%1'%2 in the project. "
|
|
||||||
"Try using a different search term or check the file name.")
|
|
||||||
.arg(query)
|
|
||||||
.arg(
|
|
||||||
filePattern.isEmpty()
|
|
||||||
? QString("")
|
|
||||||
: QString(" with pattern '%1'").arg(filePattern));
|
|
||||||
throw std::runtime_error(error.toStdString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatResults(matches, totalFound, maxResults);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<FindFileTool::FileMatch> FindFileTool::findMatchingFiles(const QString &query,
|
|
||||||
int maxResults) const
|
|
||||||
{
|
|
||||||
QList<FileMatch> matches;
|
|
||||||
QList<ProjectExplorer::Project *> projects = ProjectExplorer::ProjectManager::projects();
|
|
||||||
|
|
||||||
if (projects.isEmpty()) {
|
|
||||||
LOG_MESSAGE("FindFileTool: No projects are currently open");
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString lowerQuery = query.toLower();
|
|
||||||
|
|
||||||
for (auto project : projects) {
|
|
||||||
if (!project)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Utils::FilePaths projectFiles = project->files(ProjectExplorer::Project::SourceFiles);
|
|
||||||
Utils::FilePath projectDir = project->projectDirectory();
|
|
||||||
QString projectName = project->displayName();
|
|
||||||
|
|
||||||
for (const auto &filePath : projectFiles) {
|
|
||||||
if (matches.size() >= maxResults) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString absolutePath = filePath.toFSPathString();
|
|
||||||
|
|
||||||
if (m_ignoreManager->shouldIgnore(absolutePath, project)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFileInfo fileInfo(absolutePath);
|
|
||||||
QString fileName = fileInfo.fileName();
|
|
||||||
QString relativePath = QDir(projectDir.toFSPathString()).relativeFilePath(absolutePath);
|
|
||||||
|
|
||||||
FileMatch match;
|
|
||||||
match.absolutePath = absolutePath;
|
|
||||||
match.relativePath = relativePath;
|
|
||||||
match.projectName = projectName;
|
|
||||||
|
|
||||||
if (fileName.toLower() == lowerQuery) {
|
|
||||||
match.matchType = FileMatch::ExactName;
|
|
||||||
matches.append(match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relativePath.toLower().contains(lowerQuery)) {
|
|
||||||
match.matchType = FileMatch::PathMatch;
|
|
||||||
matches.append(match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileName.toLower().contains(lowerQuery)) {
|
|
||||||
match.matchType = FileMatch::PartialName;
|
|
||||||
matches.append(match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matches.size() >= maxResults) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't find enough matches in project files, search the filesystem
|
|
||||||
if (matches.size() < maxResults) {
|
|
||||||
LOG_MESSAGE(QString("FindFileTool: Extending search to filesystem (found %1 matches so far)")
|
|
||||||
.arg(matches.size()));
|
|
||||||
|
|
||||||
for (auto project : projects) {
|
|
||||||
if (!project)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (matches.size() >= maxResults) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::FilePath projectDir = project->projectDirectory();
|
|
||||||
QString projectName = project->displayName();
|
|
||||||
QString projectDirStr = projectDir.toFSPathString();
|
|
||||||
|
|
||||||
int depth = 0;
|
|
||||||
searchInFileSystem(projectDirStr, lowerQuery, projectName, projectDirStr,
|
|
||||||
project, matches, maxResults, depth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(matches.begin(), matches.end());
|
|
||||||
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FindFileTool::searchInFileSystem(const QString &dirPath,
|
|
||||||
const QString &query,
|
|
||||||
const QString &projectName,
|
|
||||||
const QString &projectDir,
|
|
||||||
ProjectExplorer::Project *project,
|
|
||||||
QList<FileMatch> &matches,
|
|
||||||
int maxResults,
|
|
||||||
int ¤tDepth,
|
|
||||||
int maxDepth) const
|
|
||||||
{
|
|
||||||
if (currentDepth > maxDepth || matches.size() >= maxResults) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentDepth++;
|
|
||||||
|
|
||||||
QDir dir(dirPath);
|
|
||||||
if (!dir.exists()) {
|
|
||||||
currentDepth--;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all entries (files and directories)
|
|
||||||
QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden);
|
|
||||||
|
|
||||||
for (const QFileInfo &entry : entries) {
|
|
||||||
if (matches.size() >= maxResults) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString absolutePath = entry.absoluteFilePath();
|
|
||||||
|
|
||||||
// Check if should be ignored
|
|
||||||
if (project && m_ignoreManager->shouldIgnore(absolutePath, project)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip common build/cache directories
|
|
||||||
QString fileName = entry.fileName();
|
|
||||||
if (entry.isDir()) {
|
|
||||||
// Skip common build/cache directories
|
|
||||||
if (fileName == "build" || fileName == ".git" || fileName == "node_modules" ||
|
|
||||||
fileName == "__pycache__" || fileName == ".venv" || fileName == "venv" ||
|
|
||||||
fileName == ".cmake" || fileName == "CMakeFiles" || fileName.startsWith(".qt")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse into subdirectory
|
|
||||||
searchInFileSystem(absolutePath, query, projectName, projectDir,
|
|
||||||
project, matches, maxResults, currentDepth, maxDepth);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if already in matches (avoid duplicates from project files)
|
|
||||||
bool alreadyAdded = false;
|
|
||||||
for (const auto &match : matches) {
|
|
||||||
if (match.absolutePath == absolutePath) {
|
|
||||||
alreadyAdded = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alreadyAdded) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match logic
|
|
||||||
QString lowerFileName = fileName.toLower();
|
|
||||||
QString relativePath = QDir(projectDir).relativeFilePath(absolutePath);
|
|
||||||
QString lowerRelativePath = relativePath.toLower();
|
|
||||||
|
|
||||||
FileMatch match;
|
|
||||||
match.absolutePath = absolutePath;
|
|
||||||
match.relativePath = relativePath;
|
|
||||||
match.projectName = projectName;
|
|
||||||
|
|
||||||
if (lowerFileName == query) {
|
|
||||||
match.matchType = FileMatch::ExactName;
|
|
||||||
matches.append(match);
|
|
||||||
} else if (lowerRelativePath.contains(query)) {
|
|
||||||
match.matchType = FileMatch::PathMatch;
|
|
||||||
matches.append(match);
|
|
||||||
} else if (lowerFileName.contains(query)) {
|
|
||||||
match.matchType = FileMatch::PartialName;
|
|
||||||
matches.append(match);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentDepth--;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FindFileTool::matchesFilePattern(const QString &fileName, const QString &pattern) const
|
|
||||||
{
|
|
||||||
if (pattern.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pattern.startsWith("*.")) {
|
|
||||||
QString extension = pattern.mid(1); // Remove the '*'
|
|
||||||
return fileName.endsWith(extension, Qt::CaseInsensitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileName.compare(pattern, Qt::CaseInsensitive) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FindFileTool::formatResults(const QList<FileMatch> &matches,
|
|
||||||
int totalFound,
|
|
||||||
int maxResults) const
|
|
||||||
{
|
|
||||||
QString result;
|
|
||||||
bool wasTruncated = totalFound > matches.size();
|
|
||||||
|
|
||||||
if (matches.size() == 1) {
|
|
||||||
const FileMatch &match = matches.first();
|
|
||||||
result = QString("Found 1 file:\n\n");
|
|
||||||
result += QString("File: %1\n").arg(match.relativePath);
|
|
||||||
result += QString("Absolute path: %2\n").arg(match.absolutePath);
|
|
||||||
result += QString("Project: %3").arg(match.projectName);
|
|
||||||
} else {
|
|
||||||
result = QString("Found %1 file%2%3:\n\n")
|
|
||||||
.arg(totalFound)
|
|
||||||
.arg(totalFound == 1 ? QString("") : QString("s"))
|
|
||||||
.arg(wasTruncated ? QString(" (showing first %1)").arg(matches.size()) : "");
|
|
||||||
|
|
||||||
QString currentProject;
|
|
||||||
for (const FileMatch &match : matches) {
|
|
||||||
if (currentProject != match.projectName) {
|
|
||||||
if (!currentProject.isEmpty()) {
|
|
||||||
result += "\n";
|
|
||||||
}
|
|
||||||
result += QString("Project '%1':\n").arg(match.projectName);
|
|
||||||
currentProject = match.projectName;
|
|
||||||
}
|
|
||||||
|
|
||||||
result += QString(" - %1\n").arg(match.relativePath);
|
|
||||||
result += QString(" Absolute path: %2\n").arg(match.absolutePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wasTruncated) {
|
|
||||||
result += QString(
|
|
||||||
"\n(Note: %1 additional file%2 not shown. "
|
|
||||||
"Use 'max_results' parameter to see more.)")
|
|
||||||
.arg(totalFound - matches.size())
|
|
||||||
.arg(totalFound - matches.size() == 1 ? QString("") : QString("s"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.trimmed();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Tools
|
|
||||||
@ -1,441 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 "ToolExceptions.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 <utils/filepath.h>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#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 project. "
|
|
||||||
"Returns file paths and line numbers. "
|
|
||||||
"Supports exact match, wildcards (* patterns), and regex. "
|
|
||||||
"Use read_files_by_path to read the actual code after finding symbols.";
|
|
||||||
}
|
|
||||||
|
|
||||||
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 maxResultsProperty;
|
|
||||||
maxResultsProperty["type"] = "integer";
|
|
||||||
maxResultsProperty["description"] = "Maximum number of results to return (default: 50)";
|
|
||||||
maxResultsProperty["default"] = 50;
|
|
||||||
properties["max_results"] = maxResultsProperty;
|
|
||||||
|
|
||||||
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);
|
|
||||||
int maxResults = input["max_results"].toInt(50);
|
|
||||||
|
|
||||||
if (symbolName.isEmpty()) {
|
|
||||||
QString error = "Error: 'symbol_name' parameter is required";
|
|
||||||
LOG_MESSAGE(error);
|
|
||||||
throw ToolInvalidArgument(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useRegex && useWildcard) {
|
|
||||||
QString error = "Error: 'use_regex' and 'use_wildcard' cannot be used together";
|
|
||||||
LOG_MESSAGE(error);
|
|
||||||
throw ToolInvalidArgument(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatResults(symbols);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
Q_UNUSED(fullScope)
|
|
||||||
Q_UNUSED(overview)
|
|
||||||
|
|
||||||
SymbolInfo info;
|
|
||||||
info.filePath = filePath;
|
|
||||||
info.line = symbol->line();
|
|
||||||
|
|
||||||
// Determine symbol type
|
|
||||||
if (symbol->asClass()) {
|
|
||||||
info.type = SymbolType::Class;
|
|
||||||
} else if (symbol->asFunction()) {
|
|
||||||
info.type = SymbolType::Function;
|
|
||||||
} else if (symbol->asEnum()) {
|
|
||||||
info.type = SymbolType::Enum;
|
|
||||||
} else if (symbol->asNamespace()) {
|
|
||||||
info.type = SymbolType::Namespace;
|
|
||||||
} else if (auto *declaration = symbol->asDeclaration()) {
|
|
||||||
if (declaration->isTypedef()) {
|
|
||||||
info.type = SymbolType::Typedef;
|
|
||||||
} else {
|
|
||||||
info.type = SymbolType::Variable;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info.type = SymbolType::All;
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FindSymbolTool::formatResults(const QList<SymbolInfo> &symbols) const
|
|
||||||
{
|
|
||||||
QString output = QString("Found %1 symbol(s):\n\n").arg(symbols.size());
|
|
||||||
|
|
||||||
for (const SymbolInfo &info : symbols) {
|
|
||||||
output += QString("Path: %1\nLine:%2\n").arg(info.filePath).arg(info.line);
|
|
||||||
}
|
|
||||||
|
|
||||||
return output.trimmed();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Tools
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 filePath;
|
|
||||||
int line;
|
|
||||||
SymbolType type;
|
|
||||||
};
|
|
||||||
|
|
||||||
QList<SymbolInfo> findSymbols(
|
|
||||||
const QString &symbolName,
|
|
||||||
SymbolType type,
|
|
||||||
const QString &scopeFilter,
|
|
||||||
bool caseSensitive,
|
|
||||||
bool useRegex,
|
|
||||||
bool useWildcard) const;
|
|
||||||
QString formatResults(const QList<SymbolInfo> &symbols) 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 buildFullScope(const QString ¤tScope, const QString &symbolName) const;
|
|
||||||
|
|
||||||
Context::IgnoreManager *m_ignoreManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Tools
|
|
||||||
358
tools/ProjectSearchTool.cpp
Normal file
358
tools/ProjectSearchTool.cpp
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ProjectSearchTool.hpp"
|
||||||
|
#include "ToolExceptions.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 <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
|
ProjectSearchTool::ProjectSearchTool(QObject *parent)
|
||||||
|
: BaseTool(parent)
|
||||||
|
, m_ignoreManager(new Context::IgnoreManager(this))
|
||||||
|
{}
|
||||||
|
|
||||||
|
QString ProjectSearchTool::name() const
|
||||||
|
{
|
||||||
|
return "search_project";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectSearchTool::stringName() const
|
||||||
|
{
|
||||||
|
return "Searching in project";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectSearchTool::description() const
|
||||||
|
{
|
||||||
|
return "Search project for text content or C++ symbols. "
|
||||||
|
"Text mode: finds text patterns in files. "
|
||||||
|
"Symbol mode: finds C++ definitions (classes, functions, etc).";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ProjectSearchTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
||||||
|
{
|
||||||
|
QJsonObject properties;
|
||||||
|
|
||||||
|
properties["query"]
|
||||||
|
= QJsonObject{{"type", "string"}, {"description", "Text or symbol name to search for"}};
|
||||||
|
|
||||||
|
properties["search_type"] = QJsonObject{
|
||||||
|
{"type", "string"},
|
||||||
|
{"enum", QJsonArray{"text", "symbol"}},
|
||||||
|
{"description", "Search mode: 'text' for content, 'symbol' for C++ definitions"}};
|
||||||
|
|
||||||
|
properties["symbol_type"] = QJsonObject{
|
||||||
|
{"type", "string"},
|
||||||
|
{"enum", QJsonArray{"all", "class", "function", "enum", "variable", "namespace"}},
|
||||||
|
{"description", "Symbol type filter (symbol mode only)"}};
|
||||||
|
|
||||||
|
properties["case_sensitive"]
|
||||||
|
= QJsonObject{{"type", "boolean"}, {"description", "Case-sensitive search"}};
|
||||||
|
|
||||||
|
properties["use_regex"]
|
||||||
|
= QJsonObject{{"type", "boolean"}, {"description", "Use regex patterns"}};
|
||||||
|
|
||||||
|
properties["whole_words"]
|
||||||
|
= QJsonObject{{"type", "boolean"}, {"description", "Match whole words only (text mode)"}};
|
||||||
|
|
||||||
|
properties["file_pattern"] = QJsonObject{
|
||||||
|
{"type", "string"}, {"description", "File filter pattern (e.g., '*.cpp', '*.h')"}};
|
||||||
|
|
||||||
|
QJsonObject definition;
|
||||||
|
definition["type"] = "object";
|
||||||
|
definition["properties"] = properties;
|
||||||
|
definition["required"] = QJsonArray{"query", "search_type"};
|
||||||
|
|
||||||
|
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 ProjectSearchTool::requiredPermissions() const
|
||||||
|
{
|
||||||
|
return LLMCore::ToolPermission::FileSystemRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<QString> ProjectSearchTool::executeAsync(const QJsonObject &input)
|
||||||
|
{
|
||||||
|
return QtConcurrent::run([this, input]() -> QString {
|
||||||
|
QString query = input["query"].toString().trimmed();
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
throw ToolInvalidArgument("Query parameter is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString searchTypeStr = input["search_type"].toString();
|
||||||
|
if (searchTypeStr != "text" && searchTypeStr != "symbol") {
|
||||||
|
throw ToolInvalidArgument("search_type must be 'text' or 'symbol'");
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType searchType = (searchTypeStr == "symbol") ? SearchType::Symbol : SearchType::Text;
|
||||||
|
QList<SearchResult> results;
|
||||||
|
|
||||||
|
if (searchType == SearchType::Text) {
|
||||||
|
bool caseSensitive = input["case_sensitive"].toBool(false);
|
||||||
|
bool useRegex = input["use_regex"].toBool(false);
|
||||||
|
bool wholeWords = input["whole_words"].toBool(false);
|
||||||
|
QString filePattern = input["file_pattern"].toString();
|
||||||
|
|
||||||
|
results = searchText(query, caseSensitive, useRegex, wholeWords, filePattern);
|
||||||
|
} else {
|
||||||
|
SymbolType symbolType = parseSymbolType(input["symbol_type"].toString());
|
||||||
|
bool caseSensitive = input["case_sensitive"].toBool(false);
|
||||||
|
bool useRegex = input["use_regex"].toBool(false);
|
||||||
|
|
||||||
|
results = searchSymbols(query, symbolType, caseSensitive, useRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
return QString("No matches found for '%1'").arg(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatResults(results, query);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ProjectSearchTool::SearchResult> ProjectSearchTool::searchText(
|
||||||
|
const QString &query,
|
||||||
|
bool caseSensitive,
|
||||||
|
bool useRegex,
|
||||||
|
bool wholeWords,
|
||||||
|
const QString &filePattern)
|
||||||
|
{
|
||||||
|
QList<SearchResult> results;
|
||||||
|
auto projects = ProjectExplorer::ProjectManager::projects();
|
||||||
|
if (projects.isEmpty())
|
||||||
|
return results;
|
||||||
|
|
||||||
|
QRegularExpression searchRegex;
|
||||||
|
if (useRegex) {
|
||||||
|
QRegularExpression::PatternOptions options = QRegularExpression::MultilineOption;
|
||||||
|
if (!caseSensitive)
|
||||||
|
options |= QRegularExpression::CaseInsensitiveOption;
|
||||||
|
searchRegex.setPattern(query);
|
||||||
|
searchRegex.setPatternOptions(options);
|
||||||
|
if (!searchRegex.isValid())
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegularExpression fileFilter;
|
||||||
|
if (!filePattern.isEmpty()) {
|
||||||
|
fileFilter.setPattern(QRegularExpression::wildcardToRegularExpression(filePattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto project : projects) {
|
||||||
|
if (!project)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto projectFiles = project->files(ProjectExplorer::Project::SourceFiles);
|
||||||
|
QString projectDir = project->projectDirectory().path();
|
||||||
|
|
||||||
|
for (const auto &filePath : projectFiles) {
|
||||||
|
QString absolutePath = filePath.path();
|
||||||
|
|
||||||
|
if (m_ignoreManager->shouldIgnore(absolutePath, project))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!filePattern.isEmpty()) {
|
||||||
|
QFileInfo fileInfo(absolutePath);
|
||||||
|
if (!fileFilter.match(fileInfo.fileName()).hasMatch())
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(absolutePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QTextStream stream(&file);
|
||||||
|
int lineNumber = 0;
|
||||||
|
while (!stream.atEnd()) {
|
||||||
|
lineNumber++;
|
||||||
|
QString line = stream.readLine();
|
||||||
|
bool matched = false;
|
||||||
|
|
||||||
|
if (useRegex) {
|
||||||
|
matched = searchRegex.match(line).hasMatch();
|
||||||
|
} else {
|
||||||
|
auto cs = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||||
|
if (wholeWords) {
|
||||||
|
QRegularExpression wordRegex(
|
||||||
|
QString("\\b%1\\b").arg(QRegularExpression::escape(query)),
|
||||||
|
caseSensitive ? QRegularExpression::NoPatternOption
|
||||||
|
: QRegularExpression::CaseInsensitiveOption);
|
||||||
|
matched = wordRegex.match(line).hasMatch();
|
||||||
|
} else {
|
||||||
|
matched = line.contains(query, cs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
|
SearchResult result;
|
||||||
|
result.filePath = absolutePath;
|
||||||
|
result.relativePath = QDir(projectDir).relativeFilePath(absolutePath);
|
||||||
|
result.content = line.trimmed();
|
||||||
|
result.lineNumber = lineNumber;
|
||||||
|
results.append(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ProjectSearchTool::SearchResult> ProjectSearchTool::searchSymbols(
|
||||||
|
const QString &query, SymbolType symbolType, bool caseSensitive, bool useRegex)
|
||||||
|
{
|
||||||
|
QList<SearchResult> results;
|
||||||
|
auto modelManager = CppEditor::CppModelManager::instance();
|
||||||
|
if (!modelManager)
|
||||||
|
return results;
|
||||||
|
|
||||||
|
QRegularExpression searchRegex;
|
||||||
|
if (useRegex) {
|
||||||
|
QRegularExpression::PatternOptions options = caseSensitive
|
||||||
|
? QRegularExpression::NoPatternOption
|
||||||
|
: QRegularExpression::CaseInsensitiveOption;
|
||||||
|
searchRegex.setPattern(query);
|
||||||
|
searchRegex.setPatternOptions(options);
|
||||||
|
if (!searchRegex.isValid())
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
CPlusPlus::Overview overview;
|
||||||
|
auto snapshot = modelManager->snapshot();
|
||||||
|
|
||||||
|
for (auto it = snapshot.begin(); it != snapshot.end(); ++it) {
|
||||||
|
auto document = it.value();
|
||||||
|
if (!document || !document->globalNamespace())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QString filePath = document->filePath().path();
|
||||||
|
if (m_ignoreManager->shouldIgnore(filePath, nullptr))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto searchInScope = [&](auto self, CPlusPlus::Scope *scope) -> void {
|
||||||
|
if (!scope)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < scope->memberCount(); ++i) {
|
||||||
|
auto symbol = scope->memberAt(i);
|
||||||
|
if (!symbol || !symbol->name())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QString symbolName = overview.prettyName(symbol->name());
|
||||||
|
bool nameMatches = false;
|
||||||
|
|
||||||
|
if (useRegex) {
|
||||||
|
nameMatches = searchRegex.match(symbolName).hasMatch();
|
||||||
|
} else {
|
||||||
|
nameMatches = caseSensitive
|
||||||
|
? symbolName == query
|
||||||
|
: symbolName.compare(query, Qt::CaseInsensitive) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool typeMatches = (symbolType == SymbolType::All)
|
||||||
|
|| (symbolType == SymbolType::Class && symbol->asClass())
|
||||||
|
|| (symbolType == SymbolType::Function && symbol->asFunction())
|
||||||
|
|| (symbolType == SymbolType::Enum && symbol->asEnum())
|
||||||
|
|| (symbolType == SymbolType::Variable && symbol->asDeclaration())
|
||||||
|
|| (symbolType == SymbolType::Namespace && symbol->asNamespace());
|
||||||
|
|
||||||
|
if (nameMatches && typeMatches) {
|
||||||
|
SearchResult result;
|
||||||
|
result.filePath = filePath;
|
||||||
|
|
||||||
|
auto projects = ProjectExplorer::ProjectManager::projects();
|
||||||
|
if (!projects.isEmpty()) {
|
||||||
|
QString projectDir = projects.first()->projectDirectory().path();
|
||||||
|
result.relativePath = QDir(projectDir).relativeFilePath(filePath);
|
||||||
|
} else {
|
||||||
|
result.relativePath = QFileInfo(filePath).fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.content = symbolName;
|
||||||
|
result.lineNumber = symbol->line();
|
||||||
|
result.context = overview.prettyType(symbol->type());
|
||||||
|
results.append(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto nestedScope = symbol->asScope()) {
|
||||||
|
self(self, nestedScope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
searchInScope(searchInScope, document->globalNamespace());
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectSearchTool::SymbolType ProjectSearchTool::parseSymbolType(const QString &typeStr)
|
||||||
|
{
|
||||||
|
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 == "namespace")
|
||||||
|
return SymbolType::Namespace;
|
||||||
|
return SymbolType::All;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectSearchTool::formatResults(const QList<SearchResult> &results, const QString &query)
|
||||||
|
{
|
||||||
|
QString output = QString("Query: %1\n Found %2 matches:\n\n").arg(query).arg(results.size());
|
||||||
|
int count = 0;
|
||||||
|
for (const auto &r : results) {
|
||||||
|
if (++count > 100) {
|
||||||
|
output += QString("... and %1 more matches").arg(results.size() - 20);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output += QString("%1:%2: %3\n").arg(r.relativePath).arg(r.lineNumber).arg(r.content);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Tools
|
||||||
@ -21,42 +21,51 @@
|
|||||||
|
|
||||||
#include <context/IgnoreManager.hpp>
|
#include <context/IgnoreManager.hpp>
|
||||||
#include <llmcore/BaseTool.hpp>
|
#include <llmcore/BaseTool.hpp>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
namespace QodeAssist::Tools {
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
class SearchInProjectTool : public LLMCore::BaseTool
|
class ProjectSearchTool : public LLMCore::BaseTool
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SearchInProjectTool(QObject *parent = nullptr);
|
explicit ProjectSearchTool(QObject *parent = nullptr);
|
||||||
|
|
||||||
QString name() const override;
|
QString name() const override;
|
||||||
QString stringName() const override;
|
QString stringName() const override;
|
||||||
QString description() const override;
|
QString description() const override;
|
||||||
QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override;
|
QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override;
|
||||||
LLMCore::ToolPermissions requiredPermissions() const override;
|
LLMCore::ToolPermissions requiredPermissions() const override;
|
||||||
|
QFuture<QString> executeAsync(const QJsonObject &input) override;
|
||||||
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class SearchType { Text, Symbol };
|
||||||
|
enum class SymbolType { All, Class, Function, Enum, Variable, Namespace };
|
||||||
|
|
||||||
struct SearchResult
|
struct SearchResult
|
||||||
{
|
{
|
||||||
QString filePath;
|
QString filePath;
|
||||||
int lineNumber;
|
QString relativePath;
|
||||||
QString lineContent;
|
QString content;
|
||||||
|
int lineNumber = 0;
|
||||||
QString context;
|
QString context;
|
||||||
};
|
};
|
||||||
|
|
||||||
QList<SearchResult> searchInFiles(
|
QList<SearchResult> searchText(
|
||||||
const QString &searchText,
|
const QString &query,
|
||||||
bool caseSensitive,
|
bool caseSensitive,
|
||||||
bool useRegex,
|
bool useRegex,
|
||||||
bool wholeWords,
|
bool wholeWords,
|
||||||
const QString &filePattern) const;
|
const QString &filePattern);
|
||||||
|
|
||||||
QString formatResults(const QList<SearchResult> &results,
|
QList<SearchResult> searchSymbols(
|
||||||
int maxResults,
|
const QString &query, SymbolType symbolType, bool caseSensitive, bool useRegex);
|
||||||
const QString &searchQuery) const;
|
|
||||||
|
SymbolType parseSymbolType(const QString &typeStr);
|
||||||
|
QString formatResults(const QList<SearchResult> &results, const QString &query);
|
||||||
|
|
||||||
Context::IgnoreManager *m_ignoreManager;
|
Context::IgnoreManager *m_ignoreManager;
|
||||||
};
|
};
|
||||||
@ -1,261 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 "ReadFilesByPathTool.hpp"
|
|
||||||
#include "ToolExceptions.hpp"
|
|
||||||
|
|
||||||
#include <context/ProjectUtils.hpp>
|
|
||||||
#include <coreplugin/documentmanager.h>
|
|
||||||
#include <logger/Logger.hpp>
|
|
||||||
#include <projectexplorer/project.h>
|
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
#include <settings/GeneralSettings.hpp>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QTextStream>
|
|
||||||
#include <QtConcurrent>
|
|
||||||
|
|
||||||
namespace QodeAssist::Tools {
|
|
||||||
|
|
||||||
ReadFilesByPathTool::ReadFilesByPathTool(QObject *parent)
|
|
||||||
: BaseTool(parent)
|
|
||||||
, m_ignoreManager(new Context::IgnoreManager(this))
|
|
||||||
{}
|
|
||||||
|
|
||||||
QString ReadFilesByPathTool::name() const
|
|
||||||
{
|
|
||||||
return "read_files_by_path";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ReadFilesByPathTool::stringName() const
|
|
||||||
{
|
|
||||||
return {"Reading file(s)"};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ReadFilesByPathTool::description() const
|
|
||||||
{
|
|
||||||
return "Read content of project file(s) by absolute path. "
|
|
||||||
"Use 'filepath' for single file or 'filepaths' array for multiple files (e.g., .h and .cpp). "
|
|
||||||
"Files must exist and not be excluded by .qodeassistignore.";
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject ReadFilesByPathTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
|
||||||
{
|
|
||||||
QJsonObject properties;
|
|
||||||
|
|
||||||
QJsonObject filepathProperty;
|
|
||||||
filepathProperty["type"] = "string";
|
|
||||||
filepathProperty["description"] = "The absolute file path to read (for single file)";
|
|
||||||
properties["filepath"] = filepathProperty;
|
|
||||||
|
|
||||||
QJsonObject filepathsProperty;
|
|
||||||
filepathsProperty["type"] = "array";
|
|
||||||
QJsonObject itemsProperty;
|
|
||||||
itemsProperty["type"] = "string";
|
|
||||||
filepathsProperty["items"] = itemsProperty;
|
|
||||||
filepathsProperty["description"] = "Array of absolute file paths to read (for multiple files, "
|
|
||||||
"e.g., both .h and .cpp)";
|
|
||||||
properties["filepaths"] = filepathsProperty;
|
|
||||||
|
|
||||||
QJsonObject definition;
|
|
||||||
definition["type"] = "object";
|
|
||||||
definition["properties"] = properties;
|
|
||||||
definition["description"] = "Provide either 'filepath' for a single file or 'filepaths' for "
|
|
||||||
"multiple files";
|
|
||||||
|
|
||||||
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 ReadFilesByPathTool::requiredPermissions() const
|
|
||||||
{
|
|
||||||
return LLMCore::ToolPermission::FileSystemRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFuture<QString> ReadFilesByPathTool::executeAsync(const QJsonObject &input)
|
|
||||||
{
|
|
||||||
return QtConcurrent::run([this, input]() -> QString {
|
|
||||||
QStringList filePaths;
|
|
||||||
|
|
||||||
QString singlePath = input["filepath"].toString();
|
|
||||||
if (!singlePath.isEmpty()) {
|
|
||||||
filePaths.append(singlePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.contains("filepaths") && input["filepaths"].isArray()) {
|
|
||||||
QJsonArray pathsArray = input["filepaths"].toArray();
|
|
||||||
for (const auto &pathValue : pathsArray) {
|
|
||||||
QString path = pathValue.toString();
|
|
||||||
if (!path.isEmpty()) {
|
|
||||||
filePaths.append(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filePaths.isEmpty()) {
|
|
||||||
QString error = "Error: either 'filepath' or 'filepaths' parameter is required";
|
|
||||||
throw ToolInvalidArgument(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Processing %1 file(s)").arg(filePaths.size()));
|
|
||||||
|
|
||||||
QList<FileResult> results;
|
|
||||||
for (const QString &filePath : filePaths) {
|
|
||||||
results.append(processFile(filePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatResults(results);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ReadFilesByPathTool::readFileContent(const QString &filePath) const
|
|
||||||
{
|
|
||||||
QFile file(filePath);
|
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
||||||
LOG_MESSAGE(QString("Could not open file: %1, error: %2").arg(filePath, file.errorString()));
|
|
||||||
throw ToolRuntimeError(
|
|
||||||
QString("Error: Could not open file '%1': %2").arg(filePath, file.errorString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
QTextStream stream(&file);
|
|
||||||
stream.setAutoDetectUnicode(true);
|
|
||||||
QString content = stream.readAll();
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Successfully read file: %1, size: %2 bytes, isEmpty: %3")
|
|
||||||
.arg(filePath)
|
|
||||||
.arg(content.length())
|
|
||||||
.arg(content.isEmpty()));
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadFilesByPathTool::FileResult ReadFilesByPathTool::processFile(const QString &filePath) const
|
|
||||||
{
|
|
||||||
FileResult result;
|
|
||||||
result.path = filePath;
|
|
||||||
result.success = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
QFileInfo fileInfo(filePath);
|
|
||||||
LOG_MESSAGE(QString("Checking file: %1, exists: %2, isFile: %3")
|
|
||||||
.arg(filePath)
|
|
||||||
.arg(fileInfo.exists())
|
|
||||||
.arg(fileInfo.isFile()));
|
|
||||||
|
|
||||||
if (!fileInfo.exists() || !fileInfo.isFile()) {
|
|
||||||
result.error = QString("File does not exist");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString canonicalPath = fileInfo.canonicalFilePath();
|
|
||||||
LOG_MESSAGE(QString("Canonical path: %1").arg(canonicalPath));
|
|
||||||
|
|
||||||
bool isInProject = Context::ProjectUtils::isFileInProject(canonicalPath);
|
|
||||||
|
|
||||||
if (!isInProject) {
|
|
||||||
const auto &settings = Settings::generalSettings();
|
|
||||||
if (!settings.allowAccessOutsideProject()) {
|
|
||||||
result.error = QString(
|
|
||||||
"File is not part of the project. "
|
|
||||||
"Enable 'Allow file access outside project' in settings "
|
|
||||||
"to read files outside project scope.");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
LOG_MESSAGE(QString("Reading file outside project scope: %1").arg(canonicalPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto project = isInProject ? ProjectExplorer::ProjectManager::projectForFile(
|
|
||||||
Utils::FilePath::fromString(canonicalPath))
|
|
||||||
: nullptr;
|
|
||||||
if (isInProject && project && m_ignoreManager->shouldIgnore(canonicalPath, project)) {
|
|
||||||
result.error = QString("File is excluded by .qodeassistignore");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.content = readFileContent(canonicalPath);
|
|
||||||
result.success = true;
|
|
||||||
result.path = canonicalPath;
|
|
||||||
|
|
||||||
} catch (const ToolRuntimeError &e) {
|
|
||||||
result.error = e.message();
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
result.error = QString("Unexpected error: %1").arg(QString::fromUtf8(e.what()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ReadFilesByPathTool::formatResults(const QList<FileResult> &results) const
|
|
||||||
{
|
|
||||||
if (results.size() == 1) {
|
|
||||||
const FileResult &result = results.first();
|
|
||||||
if (!result.success) {
|
|
||||||
throw ToolRuntimeError(QString("Error: %1 - %2").arg(result.path, result.error));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.content.isEmpty()) {
|
|
||||||
return QString("File: %1\n\nThe file is empty").arg(result.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return QString("File: %1\n\nContent:\n%2").arg(result.path, result.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList output;
|
|
||||||
int successCount = 0;
|
|
||||||
|
|
||||||
for (const FileResult &result : results) {
|
|
||||||
if (result.success) {
|
|
||||||
successCount++;
|
|
||||||
output.append(QString("=== File: %1 ===\n").arg(result.path));
|
|
||||||
|
|
||||||
if (result.content.isEmpty()) {
|
|
||||||
output.append("[Empty file]\n");
|
|
||||||
} else {
|
|
||||||
output.append(result.content);
|
|
||||||
}
|
|
||||||
output.append("\n");
|
|
||||||
} else {
|
|
||||||
output.append(QString("=== File: %1 ===\n").arg(result.path));
|
|
||||||
output.append(QString("[Error: %1]\n\n").arg(result.error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString summary
|
|
||||||
= QString("Successfully read %1 of %2 file(s)\n\n").arg(successCount).arg(results.size());
|
|
||||||
|
|
||||||
return summary + output.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Tools
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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>
|
|
||||||
|
|
||||||
namespace QodeAssist::Tools {
|
|
||||||
|
|
||||||
class ReadFilesByPathTool : public LLMCore::BaseTool
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ReadFilesByPathTool(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:
|
|
||||||
struct FileResult
|
|
||||||
{
|
|
||||||
QString path;
|
|
||||||
QString content;
|
|
||||||
bool success;
|
|
||||||
QString error;
|
|
||||||
};
|
|
||||||
|
|
||||||
QString readFileContent(const QString &filePath) const;
|
|
||||||
FileResult processFile(const QString &filePath) const;
|
|
||||||
QString formatResults(const QList<FileResult> &results) const;
|
|
||||||
Context::IgnoreManager *m_ignoreManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Tools
|
|
||||||
@ -1,309 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 "SearchInProjectTool.hpp"
|
|
||||||
#include "ToolExceptions.hpp"
|
|
||||||
|
|
||||||
#include <logger/Logger.hpp>
|
|
||||||
#include <projectexplorer/project.h>
|
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QTextStream>
|
|
||||||
#include <QtConcurrent>
|
|
||||||
|
|
||||||
namespace QodeAssist::Tools {
|
|
||||||
|
|
||||||
SearchInProjectTool::SearchInProjectTool(QObject *parent)
|
|
||||||
: BaseTool(parent)
|
|
||||||
, m_ignoreManager(new Context::IgnoreManager(this))
|
|
||||||
{}
|
|
||||||
|
|
||||||
QString SearchInProjectTool::name() const
|
|
||||||
{
|
|
||||||
return "search_in_project";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SearchInProjectTool::stringName() const
|
|
||||||
{
|
|
||||||
return {"Searching in project files"};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SearchInProjectTool::description() const
|
|
||||||
{
|
|
||||||
return "Search for text or regex patterns across project files. "
|
|
||||||
"Returns matching lines with file paths, line numbers, and context. "
|
|
||||||
"Supports case-sensitive/insensitive, whole word matching, and file pattern filtering (*.cpp, *.h).";
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject SearchInProjectTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
|
||||||
{
|
|
||||||
QJsonObject properties;
|
|
||||||
|
|
||||||
QJsonObject queryProperty;
|
|
||||||
queryProperty["type"] = "string";
|
|
||||||
queryProperty["description"] = "Text or regex pattern to search for";
|
|
||||||
properties["query"] = queryProperty;
|
|
||||||
|
|
||||||
QJsonObject caseSensitiveProperty;
|
|
||||||
caseSensitiveProperty["type"] = "boolean";
|
|
||||||
caseSensitiveProperty["description"] = "Enable case-sensitive search";
|
|
||||||
properties["case_sensitive"] = caseSensitiveProperty;
|
|
||||||
|
|
||||||
QJsonObject useRegexProperty;
|
|
||||||
useRegexProperty["type"] = "boolean";
|
|
||||||
useRegexProperty["description"] = "Treat query as regular expression";
|
|
||||||
properties["use_regex"] = useRegexProperty;
|
|
||||||
|
|
||||||
QJsonObject wholeWordsProperty;
|
|
||||||
wholeWordsProperty["type"] = "boolean";
|
|
||||||
wholeWordsProperty["description"] = "Match whole words only";
|
|
||||||
properties["whole_words"] = wholeWordsProperty;
|
|
||||||
|
|
||||||
QJsonObject filePatternProperty;
|
|
||||||
filePatternProperty["type"] = "string";
|
|
||||||
filePatternProperty["description"] = "File pattern to filter results (e.g., '*.cpp', '*.h')";
|
|
||||||
properties["file_pattern"] = filePatternProperty;
|
|
||||||
|
|
||||||
QJsonObject maxResultsProperty;
|
|
||||||
maxResultsProperty["type"] = "integer";
|
|
||||||
maxResultsProperty["description"] = "Maximum number of results to return (default: 50)";
|
|
||||||
properties["max_results"] = maxResultsProperty;
|
|
||||||
|
|
||||||
QJsonObject definition;
|
|
||||||
definition["type"] = "object";
|
|
||||||
definition["properties"] = properties;
|
|
||||||
|
|
||||||
QJsonArray required;
|
|
||||||
required.append("query");
|
|
||||||
definition["required"] = required;
|
|
||||||
|
|
||||||
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 SearchInProjectTool::requiredPermissions() const
|
|
||||||
{
|
|
||||||
return LLMCore::ToolPermission::FileSystemRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFuture<QString> SearchInProjectTool::executeAsync(const QJsonObject &input)
|
|
||||||
{
|
|
||||||
return QtConcurrent::run([this, input]() -> QString {
|
|
||||||
QString query = input["query"].toString();
|
|
||||||
if (query.isEmpty()) {
|
|
||||||
QString error = "Error: query parameter is required";
|
|
||||||
throw ToolInvalidArgument(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool caseSensitive = input["case_sensitive"].toBool(false);
|
|
||||||
bool useRegex = input["use_regex"].toBool(false);
|
|
||||||
bool wholeWords = input["whole_words"].toBool(false);
|
|
||||||
QString filePattern = input["file_pattern"].toString();
|
|
||||||
int maxResults = input["max_results"].toInt(50);
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Searching for: '%1' (case_sensitive: %2, regex: %3, whole_words: %4)")
|
|
||||||
.arg(query)
|
|
||||||
.arg(caseSensitive)
|
|
||||||
.arg(useRegex)
|
|
||||||
.arg(wholeWords));
|
|
||||||
|
|
||||||
QList<SearchResult> results
|
|
||||||
= searchInFiles(query, caseSensitive, useRegex, wholeWords, filePattern);
|
|
||||||
|
|
||||||
if (results.isEmpty()) {
|
|
||||||
return QString("No matches found for '%1'").arg(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatResults(results, maxResults, query);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<SearchInProjectTool::SearchResult> SearchInProjectTool::searchInFiles(
|
|
||||||
const QString &searchText,
|
|
||||||
bool caseSensitive,
|
|
||||||
bool useRegex,
|
|
||||||
bool wholeWords,
|
|
||||||
const QString &filePattern) const
|
|
||||||
{
|
|
||||||
QList<SearchResult> results;
|
|
||||||
|
|
||||||
QList<ProjectExplorer::Project *> projects = ProjectExplorer::ProjectManager::projects();
|
|
||||||
if (projects.isEmpty()) {
|
|
||||||
LOG_MESSAGE("No projects found");
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
QRegularExpression searchRegex;
|
|
||||||
if (useRegex) {
|
|
||||||
QRegularExpression::PatternOptions options = QRegularExpression::MultilineOption;
|
|
||||||
if (!caseSensitive) {
|
|
||||||
options |= QRegularExpression::CaseInsensitiveOption;
|
|
||||||
}
|
|
||||||
searchRegex.setPattern(searchText);
|
|
||||||
searchRegex.setPatternOptions(options);
|
|
||||||
|
|
||||||
if (!searchRegex.isValid()) {
|
|
||||||
LOG_MESSAGE(QString("Invalid regex pattern: %1").arg(searchText));
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QRegularExpression filePatternRegex;
|
|
||||||
if (!filePattern.isEmpty()) {
|
|
||||||
QString pattern = QRegularExpression::wildcardToRegularExpression(filePattern);
|
|
||||||
filePatternRegex.setPattern(pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto project : projects) {
|
|
||||||
if (!project)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Utils::FilePaths projectFiles = project->files(ProjectExplorer::Project::SourceFiles);
|
|
||||||
|
|
||||||
for (const auto &filePath : projectFiles) {
|
|
||||||
QString absolutePath = filePath.path();
|
|
||||||
|
|
||||||
if (m_ignoreManager->shouldIgnore(absolutePath, project)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filePattern.isEmpty()) {
|
|
||||||
QFileInfo fileInfo(absolutePath);
|
|
||||||
if (!filePatternRegex.match(fileInfo.fileName()).hasMatch()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile file(absolutePath);
|
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QTextStream stream(&file);
|
|
||||||
stream.setAutoDetectUnicode(true);
|
|
||||||
int lineNumber = 0;
|
|
||||||
|
|
||||||
while (!stream.atEnd()) {
|
|
||||||
lineNumber++;
|
|
||||||
QString line = stream.readLine();
|
|
||||||
|
|
||||||
bool matched = false;
|
|
||||||
|
|
||||||
if (useRegex) {
|
|
||||||
matched = searchRegex.match(line).hasMatch();
|
|
||||||
} else {
|
|
||||||
Qt::CaseSensitivity cs = caseSensitive ? Qt::CaseSensitive
|
|
||||||
: Qt::CaseInsensitive;
|
|
||||||
|
|
||||||
if (wholeWords) {
|
|
||||||
QRegularExpression wordRegex(
|
|
||||||
QString("\\b%1\\b").arg(QRegularExpression::escape(searchText)),
|
|
||||||
caseSensitive ? QRegularExpression::NoPatternOption
|
|
||||||
: QRegularExpression::CaseInsensitiveOption);
|
|
||||||
matched = wordRegex.match(line).hasMatch();
|
|
||||||
} else {
|
|
||||||
matched = line.contains(searchText, cs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matched) {
|
|
||||||
SearchResult result;
|
|
||||||
result.filePath = absolutePath;
|
|
||||||
result.lineNumber = lineNumber;
|
|
||||||
result.lineContent = line.trimmed();
|
|
||||||
|
|
||||||
QString context;
|
|
||||||
long long currentPos = stream.pos();
|
|
||||||
stream.seek(0);
|
|
||||||
int contextLineNum = 0;
|
|
||||||
while (contextLineNum < lineNumber - 1 && !stream.atEnd()) {
|
|
||||||
stream.readLine();
|
|
||||||
contextLineNum++;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList contextLines;
|
|
||||||
for (int i = qMax(1, lineNumber - 2); i < lineNumber; ++i) {
|
|
||||||
if (!stream.atEnd()) {
|
|
||||||
contextLines.append(stream.readLine().trimmed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!contextLines.isEmpty()) {
|
|
||||||
result.context = contextLines.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.seek(currentPos);
|
|
||||||
|
|
||||||
results.append(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SearchInProjectTool::formatResults(const QList<SearchResult> &results,
|
|
||||||
int maxResults,
|
|
||||||
const QString &searchQuery) const
|
|
||||||
{
|
|
||||||
QString output = QString("Search query: '%1'\n").arg(searchQuery);
|
|
||||||
output += QString("Found %1 matches:\n\n").arg(results.size());
|
|
||||||
|
|
||||||
int displayCount = qMin(results.size(), maxResults);
|
|
||||||
for (int i = 0; i < displayCount; ++i) {
|
|
||||||
const SearchResult &result = results[i];
|
|
||||||
|
|
||||||
output += QString("%1:%2\n").arg(result.filePath).arg(result.lineNumber);
|
|
||||||
output += QString(" %1\n").arg(result.lineContent);
|
|
||||||
|
|
||||||
if (!result.context.isEmpty()) {
|
|
||||||
output += QString(" Context:\n");
|
|
||||||
for (const QString &contextLine : result.context.split('\n')) {
|
|
||||||
output += QString(" %1\n").arg(contextLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output += "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.size() > maxResults) {
|
|
||||||
output += QString("... and %1 more matches\n").arg(results.size() - maxResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
return output.trimmed();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Tools
|
|
||||||
@ -26,13 +26,11 @@
|
|||||||
|
|
||||||
#include "BuildProjectTool.hpp"
|
#include "BuildProjectTool.hpp"
|
||||||
#include "CreateNewFileTool.hpp"
|
#include "CreateNewFileTool.hpp"
|
||||||
#include "FindFileTool.hpp"
|
#include "FindAndReadFileTool.hpp"
|
||||||
#include "FindSymbolTool.hpp"
|
|
||||||
#include "GetIssuesListTool.hpp"
|
#include "GetIssuesListTool.hpp"
|
||||||
#include "ListProjectFilesTool.hpp"
|
#include "ListProjectFilesTool.hpp"
|
||||||
#include "ReadFilesByPathTool.hpp"
|
#include "ProjectSearchTool.hpp"
|
||||||
#include "ReadVisibleFilesTool.hpp"
|
#include "ReadVisibleFilesTool.hpp"
|
||||||
#include "SearchInProjectTool.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Tools {
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
@ -45,14 +43,12 @@ ToolsFactory::ToolsFactory(QObject *parent)
|
|||||||
void ToolsFactory::registerTools()
|
void ToolsFactory::registerTools()
|
||||||
{
|
{
|
||||||
registerTool(new ReadVisibleFilesTool(this));
|
registerTool(new ReadVisibleFilesTool(this));
|
||||||
registerTool(new ReadFilesByPathTool(this));
|
|
||||||
registerTool(new ListProjectFilesTool(this));
|
registerTool(new ListProjectFilesTool(this));
|
||||||
registerTool(new SearchInProjectTool(this));
|
|
||||||
registerTool(new GetIssuesListTool(this));
|
registerTool(new GetIssuesListTool(this));
|
||||||
registerTool(new FindSymbolTool(this));
|
|
||||||
registerTool(new FindFileTool(this));
|
|
||||||
registerTool(new CreateNewFileTool(this));
|
registerTool(new CreateNewFileTool(this));
|
||||||
registerTool(new BuildProjectTool(this));
|
registerTool(new BuildProjectTool(this));
|
||||||
|
registerTool(new ProjectSearchTool(this));
|
||||||
|
registerTool(new FindAndReadFileTool(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