diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index c18b7acdf1c..554d2fba576 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -78,7 +78,7 @@ add_qtc_library(Utils jsontreeitem.cpp jsontreeitem.h layoutbuilder.cpp layoutbuilder.h linecolumn.h - link.h + link.cpp link.h listmodel.h listutils.h macroexpander.cpp macroexpander.h diff --git a/src/libs/utils/link.cpp b/src/libs/utils/link.cpp new file mode 100644 index 00000000000..34dcc07ef85 --- /dev/null +++ b/src/libs/utils/link.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "link.h" + +#include + +namespace Utils { + +/*! + Returns the Link to \a fileName. + If \a canContainLineNumber is true the line number, and column number components + are extracted from \a fileName and the found \a postFix is set. + + The following patterns are supported: \c {filepath.txt:19}, + \c{filepath.txt:19:12}, \c {filepath.txt+19}, + \c {filepath.txt+19+12}, and \c {filepath.txt(19)}. +*/ +Link Link::fromString(const QString &fileName, bool canContainLineNumber, QString *postfix) +{ + if (!canContainLineNumber) + return {Utils::FilePath::fromString(fileName)}; + // :10:2 GCC/Clang-style + static const auto regexp = QRegularExpression("[:+](\\d+)?([:+](\\d+)?)?$"); + // (10) MSVC-style + static const auto vsRegexp = QRegularExpression("[(]((\\d+)[)]?)?$"); + const QRegularExpressionMatch match = regexp.match(fileName); + QString filePath = fileName; + int line = -1; + int column = -1; + if (match.hasMatch()) { + if (postfix) + *postfix = match.captured(0); + filePath = fileName.left(match.capturedStart(0)); + line = 0; // for the case that there's only a : at the end + if (match.lastCapturedIndex() > 0) { + line = match.captured(1).toInt(); + if (match.lastCapturedIndex() > 2) // index 2 includes the + or : for the column number + column = match.captured(3).toInt() - 1; //column is 0 based, despite line being 1 based + } + } else { + const QRegularExpressionMatch vsMatch = vsRegexp.match(fileName); + if (postfix) + *postfix = vsMatch.captured(0); + filePath = fileName.left(vsMatch.capturedStart(0)); + if (vsMatch.lastCapturedIndex() > 1) // index 1 includes closing ) + line = vsMatch.captured(2).toInt(); + } + return {Utils::FilePath::fromString(filePath), line, column}; +} + +} // namespace Utils diff --git a/src/libs/utils/link.h b/src/libs/utils/link.h index 53d8f408264..269c7e89655 100644 --- a/src/libs/utils/link.h +++ b/src/libs/utils/link.h @@ -35,7 +35,7 @@ namespace Utils { -struct Link +struct QTCREATOR_UTILS_EXPORT Link { Link(const Utils::FilePath &filePath = Utils::FilePath(), int line = 0, int column = 0) : targetFilePath(filePath) @@ -43,6 +43,10 @@ struct Link , targetColumn(column) {} + static Link fromString(const QString &fileName, + bool canContainLineNumber = false, + QString *postfix = nullptr); + bool hasValidTarget() const { return !targetFilePath.isEmpty(); } diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri index 6aa5ae62dd3..33e09dd4f4c 100644 --- a/src/libs/utils/utils-lib.pri +++ b/src/libs/utils/utils-lib.pri @@ -142,7 +142,8 @@ SOURCES += \ $$PWD/layoutbuilder.cpp \ $$PWD/variablechooser.cpp \ $$PWD/futuresynchronizer.cpp \ - $$PWD/qtcsettings.cpp + $$PWD/qtcsettings.cpp \ + $$PWD/link.cpp \ HEADERS += \ $$PWD/environmentfwd.h \ diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index e079049ac3d..4f91284094a 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -162,6 +162,7 @@ Project { "layoutbuilder.cpp", "layoutbuilder.h", "linecolumn.h", + "link.cpp", "link.h", "listmodel.h", "listutils.h", diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h index 29fe6491a88..f8f9dab8a52 100644 --- a/src/plugins/coreplugin/coreplugin.h +++ b/src/plugins/coreplugin/coreplugin.h @@ -77,8 +77,6 @@ public slots: private slots: void testVcsManager_data(); void testVcsManager(); - void testSplitLineAndColumnNumber(); - void testSplitLineAndColumnNumber_data(); // Locator: void test_basefilefilter(); void test_basefilefilter_data(); diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp index 5c51e6635ff..598508eca55 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -793,10 +794,10 @@ IEditor *EditorManagerPrivate::openEditor(EditorView *view, const FilePath &file int lineNumber = -1; int columnNumber = -1; if ((flags & EditorManager::CanContainLineAndColumnNumber) && !fi.exists()) { - const EditorManager::FilePathInfo fp = EditorManager::splitLineAndColumnNumber(fn); - fn = Utils::FileUtils::normalizePathName(fp.filePath); - lineNumber = fp.lineNumber; - columnNumber = fp.columnNumber; + Link link = Link::fromString(fn, true); + fn = Utils::FileUtils::normalizePathName(link.targetFilePath.toString()); + lineNumber = link.targetLine; + columnNumber = link.targetColumn; } else { fn = Utils::FileUtils::normalizePathName(fn); } @@ -3156,44 +3157,6 @@ void EditorManager::openEditorAtSearchResult(const SearchResultItem &item, newEditor); } -/*! - Returns the file path \a fullFilePath split into its file path, line - number, and column number components. - - The following patterns are supported: \c {filepath.txt:19}, - \c{filepath.txt:19:12}, \c {filepath.txt+19}, - \c {filepath.txt+19+12}, and \c {filepath.txt(19)}. -*/ -EditorManager::FilePathInfo EditorManager::splitLineAndColumnNumber(const QString &fullFilePath) -{ - // :10:2 GCC/Clang-style - static const auto regexp = QRegularExpression("[:+](\\d+)?([:+](\\d+)?)?$"); - // (10) MSVC-style - static const auto vsRegexp = QRegularExpression("[(]((\\d+)[)]?)?$"); - const QRegularExpressionMatch match = regexp.match(fullFilePath); - QString postfix; - QString filePath = fullFilePath; - int line = -1; - int column = -1; - if (match.hasMatch()) { - postfix = match.captured(0); - filePath = fullFilePath.left(match.capturedStart(0)); - line = 0; // for the case that there's only a : at the end - if (match.lastCapturedIndex() > 0) { - line = match.captured(1).toInt(); - if (match.lastCapturedIndex() > 2) // index 2 includes the + or : for the column number - column = match.captured(3).toInt() - 1; //column is 0 based, despite line being 1 based - } - } else { - const QRegularExpressionMatch vsMatch = vsRegexp.match(fullFilePath); - postfix = vsMatch.captured(0); - filePath = fullFilePath.left(vsMatch.capturedStart(0)); - if (vsMatch.lastCapturedIndex() > 1) // index 1 includes closing ) - line = vsMatch.captured(2).toInt(); - } - return {filePath, postfix, line, column}; -} - /*! Returns whether \a fileName is an auto-save file created by \QC. */ @@ -3815,79 +3778,3 @@ void EditorManager::setWindowTitleVcsTopicHandler(WindowTitleHandler handler) { d->m_titleVcsTopicHandler = handler; } - -#if defined(WITH_TESTS) - -void CorePlugin::testSplitLineAndColumnNumber() -{ - QFETCH(QString, testFile); - QFETCH(QString, filePath); - QFETCH(QString, postfix); - QFETCH(int, line); - QFETCH(int, column); - const EditorManager::FilePathInfo fp = EditorManager::splitLineAndColumnNumber(testFile); - QCOMPARE(fp.filePath, filePath); - QCOMPARE(fp.postfix, postfix); - QCOMPARE(fp.lineNumber, line); - QCOMPARE(fp.columnNumber, column); -} - -void CorePlugin::testSplitLineAndColumnNumber_data() -{ - QTest::addColumn("testFile"); - QTest::addColumn("filePath"); - QTest::addColumn("postfix"); - QTest::addColumn("line"); - QTest::addColumn("column"); - - QTest::newRow("no-line-no-column") << QString::fromLatin1("someFile.txt") - << QString::fromLatin1("someFile.txt") - << QString() << -1 << -1; - QTest::newRow(": at end") << QString::fromLatin1("someFile.txt:") - << QString::fromLatin1("someFile.txt") - << QString::fromLatin1(":") << 0 << -1; - QTest::newRow("+ at end") << QString::fromLatin1("someFile.txt+") - << QString::fromLatin1("someFile.txt") - << QString::fromLatin1("+") << 0 << -1; - QTest::newRow(": for column") << QString::fromLatin1("someFile.txt:10:") - << QString::fromLatin1("someFile.txt") - << QString::fromLatin1(":10:") << 10 << -1; - QTest::newRow("+ for column") << QString::fromLatin1("someFile.txt:10+") - << QString::fromLatin1("someFile.txt") - << QString::fromLatin1(":10+") << 10 << -1; - QTest::newRow(": and + at end") << QString::fromLatin1("someFile.txt:+") - << QString::fromLatin1("someFile.txt") - << QString::fromLatin1(":+") << 0 << -1; - QTest::newRow("empty line") << QString::fromLatin1("someFile.txt:+10") - << QString::fromLatin1("someFile.txt") - << QString::fromLatin1(":+10") << 0 << 9; - QTest::newRow(":line-no-column") << QString::fromLatin1("/some/path/file.txt:42") - << QString::fromLatin1("/some/path/file.txt") - << QString::fromLatin1(":42") << 42 << -1; - QTest::newRow("+line-no-column") << QString::fromLatin1("/some/path/file.txt+42") - << QString::fromLatin1("/some/path/file.txt") - << QString::fromLatin1("+42") << 42 << -1; - QTest::newRow(":line-:column") << QString::fromLatin1("/some/path/file.txt:42:3") - << QString::fromLatin1("/some/path/file.txt") - << QString::fromLatin1(":42:3") << 42 << 2; - QTest::newRow(":line-+column") << QString::fromLatin1("/some/path/file.txt:42+33") - << QString::fromLatin1("/some/path/file.txt") - << QString::fromLatin1(":42+33") << 42 << 32; - QTest::newRow("+line-:column") << QString::fromLatin1("/some/path/file.txt+142:30") - << QString::fromLatin1("/some/path/file.txt") - << QString::fromLatin1("+142:30") << 142 << 29; - QTest::newRow("+line-+column") << QString::fromLatin1("/some/path/file.txt+142+33") - << QString::fromLatin1("/some/path/file.txt") - << QString::fromLatin1("+142+33") << 142 << 32; - QTest::newRow("( at end") << QString::fromLatin1("/some/path/file.txt(") - << QString::fromLatin1("/some/path/file.txt") - << QString::fromLatin1("(") << -1 << -1; - QTest::newRow("(42 at end") << QString::fromLatin1("/some/path/file.txt(42") - << QString::fromLatin1("/some/path/file.txt") - << QString::fromLatin1("(42") << 42 << -1; - QTest::newRow("(42) at end") << QString::fromLatin1("/some/path/file.txt(42)") - << QString::fromLatin1("/some/path/file.txt") - << QString::fromLatin1("(42)") << 42 << -1; -} - -#endif // WITH_TESTS diff --git a/src/plugins/coreplugin/editormanager/editormanager.h b/src/plugins/coreplugin/editormanager/editormanager.h index 0d9f2aa4cf1..339a833f051 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.h +++ b/src/plugins/coreplugin/editormanager/editormanager.h @@ -86,14 +86,6 @@ public: }; Q_DECLARE_FLAGS(OpenEditorFlags, OpenEditorFlag) - struct FilePathInfo { - QString filePath; // file path without line/column suffix - QString postfix; // line/column suffix as string, e.g. ":10:1" - int lineNumber; // extracted line number, -1 if none - int columnNumber; // extracted column number, -1 if none - }; - - static FilePathInfo splitLineAndColumnNumber(const QString &filePath); static IEditor *openEditor(const Utils::FilePath &filePath, Utils::Id editorId = {}, OpenEditorFlags flags = NoFlags, bool *newEditor = nullptr); static IEditor *openEditorAt(const Utils::FilePath &filePath, int line, int column = 0, diff --git a/src/plugins/coreplugin/locator/basefilefilter.cpp b/src/plugins/coreplugin/locator/basefilefilter.cpp index ef75c683dcf..ea6e82c7fed 100644 --- a/src/plugins/coreplugin/locator/basefilefilter.cpp +++ b/src/plugins/coreplugin/locator/basefilefilter.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -137,9 +138,10 @@ QList BaseFileFilter::matchesFor(QFutureInterface entries[int(MatchLevel::Count)]; // If search string contains spaces, treat them as wildcard '*' and search in full path const QString entry = QDir::fromNativeSeparators(origEntry).replace(' ', '*'); - const EditorManager::FilePathInfo fp = EditorManager::splitLineAndColumnNumber(entry); + QString postfix; + Link link = Link::fromString(entry, true, &postfix); - const QRegularExpression regexp = createRegExp(fp.filePath); + const QRegularExpression regexp = createRegExp(link.targetFilePath.toString()); if (!regexp.isValid()) { d->m_current.clear(); // free memory return {}; @@ -148,9 +150,9 @@ QList BaseFileFilter::matchesFor(QFutureInterfacem_current.previousEntry.isEmpty() - && fp.filePath.contains(d->m_current.previousEntry); + && link.targetFilePath.toString().contains(d->m_current.previousEntry); const bool pathSeparatorAdded = !containsPathSeparator(d->m_current.previousEntry) && hasPathSeparator; const bool searchInPreviousResults = !d->m_current.forceNewSearchList && containsPreviousEntry @@ -160,7 +162,7 @@ QList BaseFileFilter::matchesFor(QFutureInterfacem_current.iterator.data(), return QList()); d->m_current.previousResultPaths.clear(); - d->m_current.previousEntry = fp.filePath; + d->m_current.previousEntry = link.targetFilePath.toString(); d->m_current.iterator->toFront(); bool canceled = false; while (d->m_current.iterator->hasNext()) { @@ -176,7 +178,7 @@ QList BaseFileFilter::matchesFor(QFutureInterface #include #include +#include #include #include @@ -128,11 +129,11 @@ QList FileSystemFilter::matchesFor(QFutureInterface FileSystemFilter::matchesFor(QFutureInterface FileSystemFilter::matchesFor(QFutureInterface #include #include +#include #include #include @@ -59,9 +60,10 @@ QList OpenDocumentsFilter::matchesFor(QFutureInterface goodEntries; QList betterEntries; - const EditorManager::FilePathInfo fp = EditorManager::splitLineAndColumnNumber(entry); + QString postfix; + Link link = Link::fromString(entry, true, &postfix); - const QRegularExpression regexp = createRegExp(fp.filePath); + const QRegularExpression regexp = createRegExp(link.targetFilePath.toString()); if (!regexp.isValid()) return goodEntries; @@ -75,7 +77,7 @@ QList OpenDocumentsFilter::matchesFor(QFutureInterface #include #include +#include #include #include +#include #include #include -#include #include #include #include @@ -251,17 +252,17 @@ SpotlightLocatorFilter::SpotlightLocatorFilter() void SpotlightLocatorFilter::prepareSearch(const QString &entry) { - const EditorManager::FilePathInfo fp = EditorManager::splitLineAndColumnNumber(entry); - if (fp.filePath.isEmpty()) { + Link link = Utils::Link::fromString(entry, true); + if (link.targetFilePath.isEmpty()) { setFileIterator(new BaseFileFilter::ListIterator(Utils::FilePaths())); } else { // only pass the file name part to allow searches like "somepath/*foo" - int lastSlash = fp.filePath.lastIndexOf(QLatin1Char('/')); - const QString query = fp.filePath.mid(lastSlash + 1); - std::unique_ptr expander(createMacroExpander(query)); + + std::unique_ptr expander(createMacroExpander(link.targetFilePath.fileName())); const QString argumentString = expander->expand( - caseSensitivity(fp.filePath) == Qt::CaseInsensitive ? m_arguments - : m_caseSensitiveArguments); + caseSensitivity(link.targetFilePath.toString()) == Qt::CaseInsensitive + ? m_arguments + : m_caseSensitiveArguments); setFileIterator( new SpotlightIterator(QStringList(m_command) + ProcessArgs::splitArgs(argumentString))); } diff --git a/tests/auto/utils/fileutils/tst_fileutils.cpp b/tests/auto/utils/fileutils/tst_fileutils.cpp index 381f359982c..a995887910f 100644 --- a/tests/auto/utils/fileutils/tst_fileutils.cpp +++ b/tests/auto/utils/fileutils/tst_fileutils.cpp @@ -27,6 +27,7 @@ #include #include +#include //TESTED_COMPONENT=src/libs/utils using namespace Utils; @@ -54,6 +55,8 @@ private slots: void fromToString(); void comparison_data(); void comparison(); + void linkFromString_data(); + void linkFromString(); private: QTemporaryDir tempDir; @@ -364,5 +367,78 @@ void tst_fileutils::comparison_data() QTest::newRow("s4") << "x://y/abc" << "x://y/abc" << false << true; } +void tst_fileutils::linkFromString() +{ + QFETCH(QString, testFile); + QFETCH(Utils::FilePath, filePath); + QFETCH(QString, postfix); + QFETCH(int, line); + QFETCH(int, column); + QString extractedPostfix; + Link link = Link::fromString(testFile, true, &extractedPostfix); + QCOMPARE(link.targetFilePath, filePath); + QCOMPARE(extractedPostfix, postfix); + QCOMPARE(link.targetLine, line); + QCOMPARE(link.targetColumn, column); +} + +void tst_fileutils::linkFromString_data() +{ + QTest::addColumn("testFile"); + QTest::addColumn("filePath"); + QTest::addColumn("postfix"); + QTest::addColumn("line"); + QTest::addColumn("column"); + + QTest::newRow("no-line-no-column") + << QString::fromLatin1("someFile.txt") << Utils::FilePath::fromString("someFile.txt") + << QString() << -1 << -1; + QTest::newRow(": at end") << QString::fromLatin1("someFile.txt:") + << Utils::FilePath::fromString("someFile.txt") + << QString::fromLatin1(":") << 0 << -1; + QTest::newRow("+ at end") << QString::fromLatin1("someFile.txt+") + << Utils::FilePath::fromString("someFile.txt") + << QString::fromLatin1("+") << 0 << -1; + QTest::newRow(": for column") << QString::fromLatin1("someFile.txt:10:") + << Utils::FilePath::fromString("someFile.txt") + << QString::fromLatin1(":10:") << 10 << -1; + QTest::newRow("+ for column") << QString::fromLatin1("someFile.txt:10+") + << Utils::FilePath::fromString("someFile.txt") + << QString::fromLatin1(":10+") << 10 << -1; + QTest::newRow(": and + at end") + << QString::fromLatin1("someFile.txt:+") << Utils::FilePath::fromString("someFile.txt") + << QString::fromLatin1(":+") << 0 << -1; + QTest::newRow("empty line") << QString::fromLatin1("someFile.txt:+10") + << Utils::FilePath::fromString("someFile.txt") + << QString::fromLatin1(":+10") << 0 << 9; + QTest::newRow(":line-no-column") << QString::fromLatin1("/some/path/file.txt:42") + << Utils::FilePath::fromString("/some/path/file.txt") + << QString::fromLatin1(":42") << 42 << -1; + QTest::newRow("+line-no-column") << QString::fromLatin1("/some/path/file.txt+42") + << Utils::FilePath::fromString("/some/path/file.txt") + << QString::fromLatin1("+42") << 42 << -1; + QTest::newRow(":line-:column") << QString::fromLatin1("/some/path/file.txt:42:3") + << Utils::FilePath::fromString("/some/path/file.txt") + << QString::fromLatin1(":42:3") << 42 << 2; + QTest::newRow(":line-+column") << QString::fromLatin1("/some/path/file.txt:42+33") + << Utils::FilePath::fromString("/some/path/file.txt") + << QString::fromLatin1(":42+33") << 42 << 32; + QTest::newRow("+line-:column") << QString::fromLatin1("/some/path/file.txt+142:30") + << Utils::FilePath::fromString("/some/path/file.txt") + << QString::fromLatin1("+142:30") << 142 << 29; + QTest::newRow("+line-+column") << QString::fromLatin1("/some/path/file.txt+142+33") + << Utils::FilePath::fromString("/some/path/file.txt") + << QString::fromLatin1("+142+33") << 142 << 32; + QTest::newRow("( at end") << QString::fromLatin1("/some/path/file.txt(") + << Utils::FilePath::fromString("/some/path/file.txt") + << QString::fromLatin1("(") << -1 << -1; + QTest::newRow("(42 at end") << QString::fromLatin1("/some/path/file.txt(42") + << Utils::FilePath::fromString("/some/path/file.txt") + << QString::fromLatin1("(42") << 42 << -1; + QTest::newRow("(42) at end") << QString::fromLatin1("/some/path/file.txt(42)") + << Utils::FilePath::fromString("/some/path/file.txt") + << QString::fromLatin1("(42)") << 42 << -1; +} + QTEST_APPLESS_MAIN(tst_fileutils) #include "tst_fileutils.moc"