diff --git a/CMakeLists.txt b/CMakeLists.txt index 874b784..a4342d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,18 +115,15 @@ add_qtc_plugin(QodeAssist widgets/QuickRefactorDialog.hpp widgets/QuickRefactorDialog.cpp QuickRefactorHandler.hpp QuickRefactorHandler.cpp tools/ToolsFactory.hpp tools/ToolsFactory.cpp - tools/ReadFilesByPathTool.hpp tools/ReadFilesByPathTool.cpp tools/ReadVisibleFilesTool.hpp tools/ReadVisibleFilesTool.cpp tools/ToolHandler.hpp tools/ToolHandler.cpp tools/ListProjectFilesTool.hpp tools/ListProjectFilesTool.cpp tools/ToolsManager.hpp tools/ToolsManager.cpp - tools/SearchInProjectTool.hpp tools/SearchInProjectTool.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/BuildProjectTool.hpp tools/BuildProjectTool.cpp + tools/ProjectSearchTool.hpp tools/ProjectSearchTool.cpp + tools/FindAndReadFileTool.hpp tools/FindAndReadFileTool.cpp providers/ClaudeMessage.hpp providers/ClaudeMessage.cpp providers/OpenAIMessage.hpp providers/OpenAIMessage.cpp providers/OllamaMessage.hpp providers/OllamaMessage.cpp diff --git a/tools/FindAndReadFileTool.cpp b/tools/FindAndReadFileTool.cpp new file mode 100644 index 0000000..a54b324 --- /dev/null +++ b/tools/FindAndReadFileTool.cpp @@ -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 . + */ + +#include "FindAndReadFileTool.hpp" +#include "ToolExceptions.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 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 &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 diff --git a/tools/FindFileTool.hpp b/tools/FindAndReadFileTool.hpp similarity index 56% rename from tools/FindFileTool.hpp rename to tools/FindAndReadFileTool.hpp index 7262e62..6b98bdf 100644 --- a/tools/FindFileTool.hpp +++ b/tools/FindAndReadFileTool.hpp @@ -21,54 +21,60 @@ #include #include +#include +#include +#include namespace QodeAssist::Tools { -class FindFileTool : public LLMCore::BaseTool +class FindAndReadFileTool : public LLMCore::BaseTool { Q_OBJECT + public: - explicit FindFileTool(QObject *parent = nullptr); + explicit FindAndReadFileTool(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 executeAsync(const QJsonObject &input = QJsonObject()) override; + QFuture executeAsync(const QJsonObject &input) override; private: + enum class MatchType { ExactName, PathMatch, PartialName }; + struct FileMatch { QString absolutePath; QString relativePath; QString projectName; - enum MatchType { ExactName, PartialName, PathMatch } matchType; - + QString content; + MatchType matchType; + bool contentRead = false; + QString error; + bool operator<(const FileMatch &other) const { - if (matchType != other.matchType) { - return matchType < other.matchType; - } - return relativePath < other.relativePath; + return static_cast(matchType) < static_cast(other.matchType); } }; - QList findMatchingFiles(const QString &query, int maxResults) const; - void searchInFileSystem(const QString &dirPath, - const QString &query, - const QString &projectName, - const QString &projectDir, - ProjectExplorer::Project *project, - QList &matches, - int maxResults, - int ¤tDepth, - int maxDepth = 10) const; - QString formatResults(const QList &matches, int totalFound, int maxResults) const; + FileMatch findBestMatch(const QString &query, const QString &filePattern, int maxResults); + void searchInFileSystem( + const QString &dirPath, + const QString &query, + const QString &projectName, + const QString &projectDir, + ProjectExplorer::Project *project, + QList &matches, + int maxResults, + int ¤tDepth, + int maxDepth = 5); 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; }; diff --git a/tools/FindFileTool.cpp b/tools/FindFileTool.cpp deleted file mode 100644 index a83bfd2..0000000 --- a/tools/FindFileTool.cpp +++ /dev/null @@ -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 . - */ - -#include "FindFileTool.hpp" -#include "ToolExceptions.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 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 matches; - matches.append(match); - return formatResults(matches, 1, maxResults); - } - } - - QList 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::findMatchingFiles(const QString &query, - int maxResults) const -{ - QList matches; - QList 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 &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 &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 diff --git a/tools/FindSymbolTool.cpp b/tools/FindSymbolTool.cpp deleted file mode 100644 index b949116..0000000 --- a/tools/FindSymbolTool.cpp +++ /dev/null @@ -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 . - */ - -#include "FindSymbolTool.hpp" -#include "ToolExceptions.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 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 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::findSymbols( - const QString &symbolName, - SymbolType type, - const QString &scopeFilter, - bool caseSensitive, - bool useRegex, - bool useWildcard) const -{ - QList 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 &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 &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 diff --git a/tools/FindSymbolTool.hpp b/tools/FindSymbolTool.hpp deleted file mode 100644 index fe585aa..0000000 --- a/tools/FindSymbolTool.hpp +++ /dev/null @@ -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 . - */ - -#pragma once - -#include -#include -#include - -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 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 findSymbols( - const QString &symbolName, - SymbolType type, - const QString &scopeFilter, - bool caseSensitive, - bool useRegex, - bool useWildcard) const; - QString formatResults(const QList &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 &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 diff --git a/tools/ProjectSearchTool.cpp b/tools/ProjectSearchTool.cpp new file mode 100644 index 0000000..a1bfc65 --- /dev/null +++ b/tools/ProjectSearchTool.cpp @@ -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 . + */ + +#include "ProjectSearchTool.hpp" +#include "ToolExceptions.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 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::searchText( + const QString &query, + bool caseSensitive, + bool useRegex, + bool wholeWords, + const QString &filePattern) +{ + QList 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::searchSymbols( + const QString &query, SymbolType symbolType, bool caseSensitive, bool useRegex) +{ + QList 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 &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 diff --git a/tools/SearchInProjectTool.hpp b/tools/ProjectSearchTool.hpp similarity index 62% rename from tools/SearchInProjectTool.hpp rename to tools/ProjectSearchTool.hpp index 6206c4a..40874ad 100644 --- a/tools/SearchInProjectTool.hpp +++ b/tools/ProjectSearchTool.hpp @@ -21,42 +21,51 @@ #include #include +#include +#include +#include namespace QodeAssist::Tools { -class SearchInProjectTool : public LLMCore::BaseTool +class ProjectSearchTool : public LLMCore::BaseTool { Q_OBJECT + public: - explicit SearchInProjectTool(QObject *parent = nullptr); + explicit ProjectSearchTool(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 executeAsync(const QJsonObject &input = QJsonObject()) override; + QFuture executeAsync(const QJsonObject &input) override; private: + enum class SearchType { Text, Symbol }; + enum class SymbolType { All, Class, Function, Enum, Variable, Namespace }; + struct SearchResult { QString filePath; - int lineNumber; - QString lineContent; + QString relativePath; + QString content; + int lineNumber = 0; QString context; }; - QList searchInFiles( - const QString &searchText, + QList searchText( + const QString &query, bool caseSensitive, bool useRegex, bool wholeWords, - const QString &filePattern) const; + const QString &filePattern); - QString formatResults(const QList &results, - int maxResults, - const QString &searchQuery) const; + QList searchSymbols( + const QString &query, SymbolType symbolType, bool caseSensitive, bool useRegex); + + SymbolType parseSymbolType(const QString &typeStr); + QString formatResults(const QList &results, const QString &query); Context::IgnoreManager *m_ignoreManager; }; diff --git a/tools/ReadFilesByPathTool.cpp b/tools/ReadFilesByPathTool.cpp deleted file mode 100644 index 3b8285e..0000000 --- a/tools/ReadFilesByPathTool.cpp +++ /dev/null @@ -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 . - */ - -#include "ReadFilesByPathTool.hpp" -#include "ToolExceptions.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 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 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 &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 diff --git a/tools/ReadFilesByPathTool.hpp b/tools/ReadFilesByPathTool.hpp deleted file mode 100644 index 2ce8673..0000000 --- a/tools/ReadFilesByPathTool.hpp +++ /dev/null @@ -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 . - */ - -#pragma once - -#include -#include - -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 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 &results) const; - Context::IgnoreManager *m_ignoreManager; -}; - -} // namespace QodeAssist::Tools diff --git a/tools/SearchInProjectTool.cpp b/tools/SearchInProjectTool.cpp deleted file mode 100644 index 5bc2025..0000000 --- a/tools/SearchInProjectTool.cpp +++ /dev/null @@ -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 . - */ - -#include "SearchInProjectTool.hpp" -#include "ToolExceptions.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 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 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::searchInFiles( - const QString &searchText, - bool caseSensitive, - bool useRegex, - bool wholeWords, - const QString &filePattern) const -{ - QList results; - - QList 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 &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 diff --git a/tools/ToolsFactory.cpp b/tools/ToolsFactory.cpp index 70d5135..3d9b343 100644 --- a/tools/ToolsFactory.cpp +++ b/tools/ToolsFactory.cpp @@ -26,13 +26,11 @@ #include "BuildProjectTool.hpp" #include "CreateNewFileTool.hpp" -#include "FindFileTool.hpp" -#include "FindSymbolTool.hpp" +#include "FindAndReadFileTool.hpp" #include "GetIssuesListTool.hpp" #include "ListProjectFilesTool.hpp" -#include "ReadFilesByPathTool.hpp" +#include "ProjectSearchTool.hpp" #include "ReadVisibleFilesTool.hpp" -#include "SearchInProjectTool.hpp" namespace QodeAssist::Tools { @@ -45,14 +43,12 @@ ToolsFactory::ToolsFactory(QObject *parent) void ToolsFactory::registerTools() { registerTool(new ReadVisibleFilesTool(this)); - registerTool(new ReadFilesByPathTool(this)); registerTool(new ListProjectFilesTool(this)); - registerTool(new SearchInProjectTool(this)); registerTool(new GetIssuesListTool(this)); - registerTool(new FindSymbolTool(this)); - registerTool(new FindFileTool(this)); registerTool(new CreateNewFileTool(this)); registerTool(new BuildProjectTool(this)); + registerTool(new ProjectSearchTool(this)); + registerTool(new FindAndReadFileTool(this)); LOG_MESSAGE(QString("Registered %1 tools").arg(m_tools.size())); }