diff --git a/docs/API/knut/project.md b/docs/API/knut/project.md index b8acfb2e..50823735 100644 --- a/docs/API/knut/project.md +++ b/docs/API/knut/project.md @@ -22,6 +22,7 @@ import Knut |array<string> |**[allFilesWithExtension](#allFilesWithExtension)**(string extension, PathType type = RelativeToRoot)| |array<string> |**[allFilesWithExtensions](#allFilesWithExtensions)**(array<string> extensions, PathType type = RelativeToRoot)| ||**[closeAll](#closeAll)**()| +|QVariantList |**[findInFiles](#findInFiles)**(const QString &pattern)| |[Document](../knut/document.md) |**[get](#get)**(string fileName)| |[Document](../knut/document.md) |**[open](#open)**(string fileName)| ||**[openPrevious](#openPrevious)**(int index = 1)| @@ -75,6 +76,14 @@ Returns all files with an extension from `extensions` in the current project. Close all documents. If the document has some changes, save the changes. +#### QVariantList **findInFiles**(const QString &pattern) + +Search for a regex pattern in all files of the current project using ripgrep. +Returns a list of results (QVariantMaps) with the document name and position ("file", "line", "column"). + +Note: The method uses ripgrep (rg) for searching, which must be installed and accessible in PATH. +The `pattern` parameter should be a valid regular expression. + #### [Document](../knut/document.md) **get**(string fileName) Gets the document for the given `fileName`. If the document is not opened yet, open it. If the document diff --git a/src/core/project.cpp b/src/core/project.cpp index f6fecd56..c9396e20 100644 --- a/src/core/project.cpp +++ b/src/core/project.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include #include @@ -380,4 +382,56 @@ Document *Project::openPrevious(int index) LOG_RETURN("document", open(fileName)); } +/*! + * \qmlmethod QVariantList Project::findInFiles(const QString &pattern) + * Search for a regex pattern in all files of the current project using ripgrep. + * Returns a list of results (QVariantMaps) with the document name and position ("file", "line", "column"). + * + * Note: The method uses ripgrep (rg) for searching, which must be installed and accessible in PATH. + * The `pattern` parameter should be a valid regular expression. + */ +QVariantList Project::findInFiles(const QString &pattern) const +{ + LOG("Project::findInFiles", pattern); + + QVariantList result; + + const QString path = QStandardPaths::findExecutable("rg"); + if (path.isEmpty()) { + spdlog::error("Ripgrep (rg) executable not found. Please ensure that ripgrep is installed and its location is " + "included in the PATH environment variable."); + return result; + } + + QProcess process; + + const QStringList arguments {"--vimgrep", "-U", "--multiline-dotall", pattern, m_root}; + + process.start(path, arguments); + if (!process.waitForFinished()) { + spdlog::error("The ripgrep process failed: {}", process.errorString()); + return result; + } + + const QString output = process.readAllStandardOutput(); + + const QString errorOutput = process.readAllStandardError(); + if (!errorOutput.isEmpty()) { + spdlog::error("Ripgrep error: {}", errorOutput); + } + + const auto lines = output.split('\n', Qt::SkipEmptyParts); + result.reserve(lines.count() * 3); + for (const QString &line : lines) { + const auto parts = line.split(':'); + if (parts.size() >= 3) { + QVariantMap matchResult; + matchResult.insert("file", parts[0]); + matchResult.insert("line", parts[1].toInt()); + matchResult.insert("column", parts[2].toInt()); + result.append(matchResult); + } + } + return result; +} } // namespace Core diff --git a/src/core/project.h b/src/core/project.h index 1b6804d4..67ba7773 100644 --- a/src/core/project.h +++ b/src/core/project.h @@ -53,6 +53,7 @@ class Project : public QObject Core::Project::PathType type = RelativeToRoot); Q_INVOKABLE QStringList allFilesWithExtensions(const QStringList &extensions, Core::Project::PathType type = RelativeToRoot); + Q_INVOKABLE QVariantList findInFiles(const QString &pattern) const; public slots: Core::Document *get(const QString &fileName); diff --git a/test_data/tst_project.qml b/test_data/tst_project.qml index 6392ec99..b9e6addf 100644 --- a/test_data/tst_project.qml +++ b/test_data/tst_project.qml @@ -33,4 +33,29 @@ Script { var rcdoc = Project.open("MFC_UpdateGUI.rc") compare(rcdoc.type, Document.Rc) } + + function test_findInFiles() { + let simplePattern = "CTutorialApp::InitInstance()" + let simpleResults = Project.findInFiles(simplePattern) + + compare(simpleResults.length, 2) + + simpleResults.sort((a, b) => a.file.localeCompare(b.file)); + + compare(simpleResults[0].file, Project.root + "/TutorialApp.cpp") + compare(simpleResults[0].line, 21) + compare(simpleResults[0].column, 6) + compare(simpleResults[1].file, Project.root + "/TutorialDlg.h") + compare(simpleResults[1].line, 10) + compare(simpleResults[1].column, 9) + + let multilinePattern = "m_VSliderBar\\.SetRange\\(0,\\s*100,\\s*TRUE\\);\\s*m_VSliderBar\\.SetPos\\(50\\);"; + let multilineResults = Project.findInFiles(multilinePattern) + + compare(multilineResults.length, 1) + + compare(multilineResults[0].file, Project.root + "/TutorialDlg.cpp") + compare(multilineResults[0].line, 65) + compare(multilineResults[0].column, 3) + } }