From da2e60fa3e59c88ee73e12ce84bb86b672cc74b0 Mon Sep 17 00:00:00 2001 From: Mathias Hasselmann Date: Sun, 7 Jan 2024 10:50:34 +0100 Subject: [PATCH] Core: Show context of filter matches When inspecting logs often enough the relevant information is next to the line with the unique expression that's easy to match. The `grep` tool solves this problem by providing various `--*context` options which configure how much context to show for each match. This change tries to replicate this feature. Task-number: QTCREATORBUG-30167 Change-Id: I6432870c0b958df8c5dc616009aea4ca54973245 Reviewed-by: hjk Reviewed-by: Eike Ziller --- share/qtcreator/translations/qtcreator_de.ts | 18 +++++++ src/plugins/autotest/testresultspane.cpp | 2 +- src/plugins/coreplugin/ioutputpane.h | 11 ++++ .../coreplugin/messageoutputwindow.cpp | 7 ++- src/plugins/coreplugin/messageoutputwindow.h | 2 + src/plugins/coreplugin/outputpanemanager.cpp | 53 ++++++++++++++++++- src/plugins/coreplugin/outputwindow.cpp | 39 ++++++++++++-- src/plugins/coreplugin/outputwindow.h | 6 ++- src/plugins/projectexplorer/appoutputpane.cpp | 11 +++- src/plugins/projectexplorer/appoutputpane.h | 2 + .../projectexplorer/compileoutputwindow.cpp | 8 ++- .../projectexplorer/compileoutputwindow.h | 2 + src/plugins/projectexplorer/projectwindow.cpp | 4 +- .../serialterminal/serialoutputpane.cpp | 5 ++ src/plugins/serialterminal/serialoutputpane.h | 2 + 15 files changed, 157 insertions(+), 15 deletions(-) diff --git a/share/qtcreator/translations/qtcreator_de.ts b/share/qtcreator/translations/qtcreator_de.ts index 1f1d52059cb..9c18f5e66c7 100644 --- a/share/qtcreator/translations/qtcreator_de.ts +++ b/share/qtcreator/translations/qtcreator_de.ts @@ -19385,6 +19385,16 @@ Wenn die Systemzeiger für das Verändern der Größe von Ansichten nicht korrek Open file "%1" with: Öffne Datei "%1" mit: + + Show {} &preceding lines + The optional placeholder "{}" is replaced by a spin box for selecting a number. + Zeige {} &vorausgehende Zeilen + + + Show {} &subsequent lines + The optional placeholder "{}" is replaced by a spin box for selecting a number. + Zeige {} &nachfolgende Zeilen + Maximize Maximieren @@ -21807,6 +21817,14 @@ Doppelklicken Sie einen Eintrag um ihn zu ändern. Show Non-matching Lines Zeige nicht übereinstimmende Zeilen + + Show {# lines} &before + {# Zeilen} &vorab zeigen + + + Show {# lines} &after + {# Zeilen} &nachfolgend zeigen + Filter output... Ausgabe filtern... diff --git a/src/plugins/autotest/testresultspane.cpp b/src/plugins/autotest/testresultspane.cpp index 894107d5d4b..7492448a026 100644 --- a/src/plugins/autotest/testresultspane.cpp +++ b/src/plugins/autotest/testresultspane.cpp @@ -404,7 +404,7 @@ void TestResultsPane::goToPrev() void TestResultsPane::updateFilter() { m_textOutput->updateFilterProperties(filterText(), filterCaseSensitivity(), filterUsesRegexp(), - filterIsInverted()); + filterIsInverted(), beforeContext(), afterContext()); } void TestResultsPane::onItemActivated(const QModelIndex &index) diff --git a/src/plugins/coreplugin/ioutputpane.h b/src/plugins/coreplugin/ioutputpane.h index 76d196a382f..0618653b20f 100644 --- a/src/plugins/coreplugin/ioutputpane.h +++ b/src/plugins/coreplugin/ioutputpane.h @@ -43,6 +43,8 @@ public: virtual void goToNext() = 0; virtual void goToPrev() = 0; + virtual bool hasFilterContext() const; + void setFont(const QFont &font); void setWheelZoomEnabled(bool enabled); @@ -80,6 +82,8 @@ protected: QString filterText() const; bool filterUsesRegexp() const { return m_filterRegexp; } bool filterIsInverted() const { return m_invertFilter; } + int beforeContext() const { return m_beforeContext; } + int afterContext() const { return m_afterContext; } Qt::CaseSensitivity filterCaseSensitivity() const { return m_filterCaseSensitivity; } void setFilteringEnabled(bool enable); QWidget *filterWidget() const { return m_filterOutputLineEdit; } @@ -96,15 +100,22 @@ private: Utils::Id filterRegexpActionId() const; Utils::Id filterCaseSensitivityActionId() const; Utils::Id filterInvertedActionId() const; + Utils::Id filterBeforeActionId() const; + Utils::Id filterAfterActionId() const; Utils::Id m_id; QString m_displayName; int m_priority = -1; QToolButton *m_zoomInButton; QToolButton *m_zoomOutButton; + QAction *m_filterActionRegexp = nullptr; + QAction *m_filterActionCaseSensitive = nullptr; + QAction *m_invertFilterAction = nullptr; Utils::FancyLineEdit *m_filterOutputLineEdit = nullptr; bool m_filterRegexp = false; bool m_invertFilter = false; + int m_beforeContext = 0; + int m_afterContext = 0; Qt::CaseSensitivity m_filterCaseSensitivity = Qt::CaseInsensitive; }; diff --git a/src/plugins/coreplugin/messageoutputwindow.cpp b/src/plugins/coreplugin/messageoutputwindow.cpp index 1cfe384ac00..b13a301efed 100644 --- a/src/plugins/coreplugin/messageoutputwindow.cpp +++ b/src/plugins/coreplugin/messageoutputwindow.cpp @@ -99,10 +99,15 @@ bool MessageOutputWindow::canNavigate() const return false; } +bool MessageOutputWindow::hasFilterContext() const +{ + return true; +} + void MessageOutputWindow::updateFilter() { m_widget->updateFilterProperties(filterText(), filterCaseSensitivity(), filterUsesRegexp(), - filterIsInverted()); + filterIsInverted(), beforeContext(), afterContext()); } } // namespace Internal diff --git a/src/plugins/coreplugin/messageoutputwindow.h b/src/plugins/coreplugin/messageoutputwindow.h index ef0d93a97cf..eabc2adcff3 100644 --- a/src/plugins/coreplugin/messageoutputwindow.h +++ b/src/plugins/coreplugin/messageoutputwindow.h @@ -33,6 +33,8 @@ public: void goToPrev() override; bool canNavigate() const override; + bool hasFilterContext() const override; + private: void updateFilter() override; diff --git a/src/plugins/coreplugin/outputpanemanager.cpp b/src/plugins/coreplugin/outputpanemanager.cpp index 0b141df46e9..45054f6185c 100644 --- a/src/plugins/coreplugin/outputpanemanager.cpp +++ b/src/plugins/coreplugin/outputpanemanager.cpp @@ -161,6 +161,11 @@ void IOutputPane::visibilityChanged(bool /*visible*/) { } +bool IOutputPane::hasFilterContext() const +{ + return false; +} + void IOutputPane::setFont(const QFont &font) { emit fontChanged(font); @@ -191,6 +196,32 @@ void IOutputPane::setupFilterUi(const Key &historyKey) updateFilter(); }); + ActionBuilder filterBeforeAction(this, filterBeforeActionId()); + //: The placeholder "{}" is replaced by a spin box for selecting a number. + filterBeforeAction.setText(Tr::tr("Show {} &preceding lines")); + QAction *action = filterBeforeAction.contextAction(); + NumericOption::set(action, NumericOption{0, 0, 9}); + NumericOption::set(filterBeforeAction.commandAction(), NumericOption{0, 0, 9}); + connect(action, &QAction::changed, this, [this, action] { + const std::optional option = NumericOption::get(action); + QTC_ASSERT(option, return); + m_beforeContext = option->currentValue; + updateFilter(); + }); + + ActionBuilder filterAfterAction(this, filterAfterActionId()); + //: The placeholder "{}" is replaced by a spin box for selecting a number. + filterAfterAction.setText(Tr::tr("Show {} &subsequent lines")); + action = filterAfterAction.contextAction(); + NumericOption::set(action, NumericOption{0, 0, 9}); + NumericOption::set(filterAfterAction.commandAction(), NumericOption{0, 0, 9}); + connect(action, &QAction::changed, this, [this, action] { + const std::optional option = NumericOption::get(action); + QTC_ASSERT(option, return); + m_afterContext = option->currentValue; + updateFilter(); + }); + m_filterOutputLineEdit = new FancyLineEdit; m_filterOutputLineEdit->setPlaceholderText(Tr::tr("Filter output...")); m_filterOutputLineEdit->setButtonVisible(FancyLineEdit::Left, true); @@ -252,8 +283,16 @@ void IOutputPane::updateFilter() void IOutputPane::filterOutputButtonClicked() { - auto popup = new Core::OptionsPopup(m_filterOutputLineEdit, - {filterRegexpActionId(), filterCaseSensitivityActionId(), filterInvertedActionId()}); + QVector commands = {filterRegexpActionId(), + filterCaseSensitivityActionId(), + filterInvertedActionId()}; + + if (hasFilterContext()) { + commands.emplaceBack(filterBeforeActionId()); + commands.emplaceBack(filterAfterActionId()); + } + + auto popup = new Core::OptionsPopup(m_filterOutputLineEdit, commands); popup->show(); } @@ -278,6 +317,16 @@ Id IOutputPane::filterInvertedActionId() const return Id("OutputFilter.Invert").withSuffix(metaObject()->className()); } +Id IOutputPane::filterBeforeActionId() const +{ + return Id("OutputFilter.BeforeContext").withSuffix(metaObject()->className()); +} + +Id IOutputPane::filterAfterActionId() const +{ + return Id("OutputFilter.AfterContext").withSuffix(metaObject()->className()); +} + void IOutputPane::setCaseSensitive(bool caseSensitive) { m_filterCaseSensitivity = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; diff --git a/src/plugins/coreplugin/outputwindow.cpp b/src/plugins/coreplugin/outputwindow.cpp index c9b3ec86f32..e792bbcb7f9 100644 --- a/src/plugins/coreplugin/outputwindow.cpp +++ b/src/plugins/coreplugin/outputwindow.cpp @@ -70,6 +70,8 @@ public: int lastFilteredBlockNumber = -1; QPalette originalPalette; OutputWindow::FilterModeFlags filterMode = OutputWindow::FilterModeFlag::Default; + int beforeContext = 0; + int afterContext = 0; QTimer scrollTimer; QElapsedTimer lastMessage; QHash> taskPositions; @@ -337,14 +339,19 @@ void OutputWindow::updateFilterProperties( const QString &filterText, Qt::CaseSensitivity caseSensitivity, bool isRegexp, - bool isInverted + bool isInverted, + int beforeContext, + int afterContext ) { FilterModeFlags flags; flags.setFlag(FilterModeFlag::CaseSensitive, caseSensitivity == Qt::CaseSensitive) .setFlag(FilterModeFlag::RegExp, isRegexp) .setFlag(FilterModeFlag::Inverted, isInverted); - if (d->filterMode == flags && d->filterText == filterText) + if (d->filterMode == flags + && d->filterText == filterText + && d->beforeContext == beforeContext + && d->afterContext == afterContext) return; d->lastFilteredBlockNumber = -1; if (d->filterText != filterText) { @@ -371,6 +378,8 @@ void OutputWindow::updateFilterProperties( } } d->filterMode = flags; + d->beforeContext = beforeContext; + d->afterContext = afterContext; filterNewContent(); } @@ -409,13 +418,33 @@ void OutputWindow::filterNewContent() QTC_ASSERT(findNextMatch, return); const bool invert = d->filterMode.testFlag(FilterModeFlag::Inverted) && !d->filterText.isEmpty(); + const int requiredBacklog = std::max(d->beforeContext, d->afterContext); + const int firstBlockIndex = d->lastFilteredBlockNumber - requiredBacklog; - QTextBlock lastBlock = document()->findBlockByNumber(d->lastFilteredBlockNumber); + std::vector matchedBlocks; + QTextBlock lastBlock = document()->findBlockByNumber(firstBlockIndex); if (!lastBlock.isValid()) lastBlock = document()->begin(); - for (; lastBlock != document()->end(); lastBlock = lastBlock.next()) - lastBlock.setVisible(findNextMatch(lastBlock.text()) != invert); + // Find matching text blocks for the current filter. + for (; lastBlock != document()->end(); lastBlock = lastBlock.next()) { + const bool isMatch = findNextMatch(lastBlock.text()) != invert; + + if (isMatch) + matchedBlocks.emplace_back(lastBlock.blockNumber()); + + lastBlock.setVisible(isMatch); + } + + // Reveal the context lines before and after the match. + if (!d->filterText.isEmpty()) { + for (int blockNumber : matchedBlocks) { + for (auto i = 1; i <= d->beforeContext; ++i) + document()->findBlockByNumber(blockNumber - i).setVisible(true); + for (auto i = 1; i <= d->afterContext; ++i) + document()->findBlockByNumber(blockNumber + i).setVisible(true); + } + } d->lastFilteredBlockNumber = document()->lastBlock().blockNumber(); diff --git a/src/plugins/coreplugin/outputwindow.h b/src/plugins/coreplugin/outputwindow.h index 19ab9a3c568..b6fc49001f3 100644 --- a/src/plugins/coreplugin/outputwindow.h +++ b/src/plugins/coreplugin/outputwindow.h @@ -65,7 +65,9 @@ public: const QString &filterText, Qt::CaseSensitivity caseSensitivity, bool regexp, - bool isInverted); + bool isInverted, + int beforeContext, + int afterContext); void setOutputFileNameHint(const QString &fileName); @@ -97,7 +99,7 @@ private: void handleOutputChunk(const QString &output, Utils::OutputFormat format); void updateAutoScroll(); - using TextMatchingFunction = std::function; + using TextMatchingFunction = std::function; TextMatchingFunction makeMatchingFunction() const; Internal::OutputWindowPrivate *d = nullptr; diff --git a/src/plugins/projectexplorer/appoutputpane.cpp b/src/plugins/projectexplorer/appoutputpane.cpp index 3d489460247..8d8455f5aee 100644 --- a/src/plugins/projectexplorer/appoutputpane.cpp +++ b/src/plugins/projectexplorer/appoutputpane.cpp @@ -339,7 +339,8 @@ void AppOutputPane::updateFilter() { if (RunControlTab * const tab = currentTab()) { tab->window->updateFilterProperties(filterText(), filterCaseSensitivity(), - filterUsesRegexp(), filterIsInverted()); + filterUsesRegexp(), filterIsInverted(), + beforeContext(), afterContext()); } } @@ -730,7 +731,8 @@ void AppOutputPane::tabChanged(int i) RunControlTab * const controlTab = tabFor(m_tabWidget->widget(i)); if (i != -1 && controlTab) { controlTab->window->updateFilterProperties(filterText(), filterCaseSensitivity(), - filterUsesRegexp(), filterIsInverted()); + filterUsesRegexp(), filterIsInverted(), + beforeContext(), afterContext()); enableButtons(controlTab->runControl); } else { enableDefaultButtons(); @@ -810,6 +812,11 @@ bool AppOutputPane::canNavigate() const return false; } +bool AppOutputPane::hasFilterContext() const +{ + return true; +} + class AppOutputSettingsWidget : public Core::IOptionsPageWidget { public: diff --git a/src/plugins/projectexplorer/appoutputpane.h b/src/plugins/projectexplorer/appoutputpane.h index f7cbb653ff3..6438e6c2c1d 100644 --- a/src/plugins/projectexplorer/appoutputpane.h +++ b/src/plugins/projectexplorer/appoutputpane.h @@ -57,6 +57,8 @@ public: void goToPrev() override; bool canNavigate() const override; + bool hasFilterContext() const override; + void createNewOutputWindow(RunControl *rc); void showTabFor(RunControl *rc); void setBehaviorOnOutput(RunControl *rc, AppOutputPaneMode mode); diff --git a/src/plugins/projectexplorer/compileoutputwindow.cpp b/src/plugins/projectexplorer/compileoutputwindow.cpp index c5255d127f5..eae2e2bc805 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.cpp +++ b/src/plugins/projectexplorer/compileoutputwindow.cpp @@ -203,6 +203,11 @@ bool CompileOutputWindow::canNavigate() const return false; } +bool CompileOutputWindow::hasFilterContext() const +{ + return true; +} + void CompileOutputWindow::registerPositionOf(const Task &task, int linkedOutputLines, int skipLines, int offset) { @@ -227,7 +232,8 @@ Utils::OutputFormatter *CompileOutputWindow::outputFormatter() const void CompileOutputWindow::updateFilter() { m_outputWindow->updateFilterProperties(filterText(), filterCaseSensitivity(), - filterUsesRegexp(), filterIsInverted()); + filterUsesRegexp(), filterIsInverted(), + beforeContext(), afterContext()); } // CompileOutputSettings diff --git a/src/plugins/projectexplorer/compileoutputwindow.h b/src/plugins/projectexplorer/compileoutputwindow.h index 21cebcb2dba..cab706c9f52 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.h +++ b/src/plugins/projectexplorer/compileoutputwindow.h @@ -59,6 +59,8 @@ public: void goToPrev() override; bool canNavigate() const override; + bool hasFilterContext() const override; + void appendText(const QString &text, BuildStep::OutputFormat format); void registerPositionOf(const Task &task, int linkedOutputLines, int skipLines, int offset = 0); diff --git a/src/plugins/projectexplorer/projectwindow.cpp b/src/plugins/projectexplorer/projectwindow.cpp index c3214b64984..70ba2bbc972 100644 --- a/src/plugins/projectexplorer/projectwindow.cpp +++ b/src/plugins/projectexplorer/projectwindow.cpp @@ -206,7 +206,9 @@ void BuildSystemOutputWindow::updateFilter() m_filterActionCaseSensitive.isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive, m_filterActionRegexp.isChecked(), - m_invertFilterAction.isChecked()); + m_invertFilterAction.isChecked(), + 0 /* before context */, + 0 /* after context */); } class VanishedTargetPanelItem : public TreeItem diff --git a/src/plugins/serialterminal/serialoutputpane.cpp b/src/plugins/serialterminal/serialoutputpane.cpp index 2f30bf75fcd..7c4ca50b47c 100644 --- a/src/plugins/serialterminal/serialoutputpane.cpp +++ b/src/plugins/serialterminal/serialoutputpane.cpp @@ -227,6 +227,11 @@ bool SerialOutputPane::canNavigate() const return false; } +bool SerialOutputPane::hasFilterContext() const +{ + return true; +} + void SerialOutputPane::appendMessage(SerialControl *rc, const QString &out, Utils::OutputFormat format) { const int index = indexOf(rc); diff --git a/src/plugins/serialterminal/serialoutputpane.h b/src/plugins/serialterminal/serialoutputpane.h index 1f970e2f18e..9a4097b3f5f 100644 --- a/src/plugins/serialterminal/serialoutputpane.h +++ b/src/plugins/serialterminal/serialoutputpane.h @@ -62,6 +62,8 @@ public: void goToPrev() final; bool canNavigate() const final; + bool hasFilterContext() const final; + void createNewOutputWindow(SerialControl *rc); bool closeTabs(CloseTabMode mode);