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 <hjk@qt.io>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Mathias Hasselmann
2024-01-07 10:50:34 +01:00
committed by hjk
parent 54532795ab
commit da2e60fa3e
15 changed files with 157 additions and 15 deletions

View File

@@ -19385,6 +19385,16 @@ Wenn die Systemzeiger für das Verändern der Größe von Ansichten nicht korrek
<source>Open file &quot;%1&quot; with:</source> <source>Open file &quot;%1&quot; with:</source>
<translation>Öffne Datei &quot;%1&quot; mit:</translation> <translation>Öffne Datei &quot;%1&quot; mit:</translation>
</message> </message>
<message>
<source>Show {} &amp;preceding lines</source>
<extracomment>The optional placeholder &quot;{}&quot; is replaced by a spin box for selecting a number.</extracomment>
<translation>Zeige {} &amp;vorausgehende Zeilen</translation>
</message>
<message>
<source>Show {} &amp;subsequent lines</source>
<extracomment>The optional placeholder &quot;{}&quot; is replaced by a spin box for selecting a number.</extracomment>
<translation>Zeige {} &amp;nachfolgende Zeilen</translation>
</message>
<message> <message>
<source>Maximize</source> <source>Maximize</source>
<translation>Maximieren</translation> <translation>Maximieren</translation>
@@ -21807,6 +21817,14 @@ Doppelklicken Sie einen Eintrag um ihn zu ändern.</translation>
<source>Show Non-matching Lines</source> <source>Show Non-matching Lines</source>
<translation>Zeige nicht übereinstimmende Zeilen</translation> <translation>Zeige nicht übereinstimmende Zeilen</translation>
</message> </message>
<message>
<source>Show {# lines} &amp;before</source>
<translation>{# Zeilen} &amp;vorab zeigen</translation>
</message>
<message>
<source>Show {# lines} &amp;after</source>
<translation>{# Zeilen} &amp;nachfolgend zeigen</translation>
</message>
<message> <message>
<source>Filter output...</source> <source>Filter output...</source>
<translation>Ausgabe filtern...</translation> <translation>Ausgabe filtern...</translation>

View File

@@ -404,7 +404,7 @@ void TestResultsPane::goToPrev()
void TestResultsPane::updateFilter() void TestResultsPane::updateFilter()
{ {
m_textOutput->updateFilterProperties(filterText(), filterCaseSensitivity(), filterUsesRegexp(), m_textOutput->updateFilterProperties(filterText(), filterCaseSensitivity(), filterUsesRegexp(),
filterIsInverted()); filterIsInverted(), beforeContext(), afterContext());
} }
void TestResultsPane::onItemActivated(const QModelIndex &index) void TestResultsPane::onItemActivated(const QModelIndex &index)

View File

@@ -43,6 +43,8 @@ public:
virtual void goToNext() = 0; virtual void goToNext() = 0;
virtual void goToPrev() = 0; virtual void goToPrev() = 0;
virtual bool hasFilterContext() const;
void setFont(const QFont &font); void setFont(const QFont &font);
void setWheelZoomEnabled(bool enabled); void setWheelZoomEnabled(bool enabled);
@@ -80,6 +82,8 @@ protected:
QString filterText() const; QString filterText() const;
bool filterUsesRegexp() const { return m_filterRegexp; } bool filterUsesRegexp() const { return m_filterRegexp; }
bool filterIsInverted() const { return m_invertFilter; } 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; } Qt::CaseSensitivity filterCaseSensitivity() const { return m_filterCaseSensitivity; }
void setFilteringEnabled(bool enable); void setFilteringEnabled(bool enable);
QWidget *filterWidget() const { return m_filterOutputLineEdit; } QWidget *filterWidget() const { return m_filterOutputLineEdit; }
@@ -96,15 +100,22 @@ private:
Utils::Id filterRegexpActionId() const; Utils::Id filterRegexpActionId() const;
Utils::Id filterCaseSensitivityActionId() const; Utils::Id filterCaseSensitivityActionId() const;
Utils::Id filterInvertedActionId() const; Utils::Id filterInvertedActionId() const;
Utils::Id filterBeforeActionId() const;
Utils::Id filterAfterActionId() const;
Utils::Id m_id; Utils::Id m_id;
QString m_displayName; QString m_displayName;
int m_priority = -1; int m_priority = -1;
QToolButton *m_zoomInButton; QToolButton *m_zoomInButton;
QToolButton *m_zoomOutButton; QToolButton *m_zoomOutButton;
QAction *m_filterActionRegexp = nullptr;
QAction *m_filterActionCaseSensitive = nullptr;
QAction *m_invertFilterAction = nullptr;
Utils::FancyLineEdit *m_filterOutputLineEdit = nullptr; Utils::FancyLineEdit *m_filterOutputLineEdit = nullptr;
bool m_filterRegexp = false; bool m_filterRegexp = false;
bool m_invertFilter = false; bool m_invertFilter = false;
int m_beforeContext = 0;
int m_afterContext = 0;
Qt::CaseSensitivity m_filterCaseSensitivity = Qt::CaseInsensitive; Qt::CaseSensitivity m_filterCaseSensitivity = Qt::CaseInsensitive;
}; };

View File

@@ -99,10 +99,15 @@ bool MessageOutputWindow::canNavigate() const
return false; return false;
} }
bool MessageOutputWindow::hasFilterContext() const
{
return true;
}
void MessageOutputWindow::updateFilter() void MessageOutputWindow::updateFilter()
{ {
m_widget->updateFilterProperties(filterText(), filterCaseSensitivity(), filterUsesRegexp(), m_widget->updateFilterProperties(filterText(), filterCaseSensitivity(), filterUsesRegexp(),
filterIsInverted()); filterIsInverted(), beforeContext(), afterContext());
} }
} // namespace Internal } // namespace Internal

View File

@@ -33,6 +33,8 @@ public:
void goToPrev() override; void goToPrev() override;
bool canNavigate() const override; bool canNavigate() const override;
bool hasFilterContext() const override;
private: private:
void updateFilter() override; void updateFilter() override;

View File

@@ -161,6 +161,11 @@ void IOutputPane::visibilityChanged(bool /*visible*/)
{ {
} }
bool IOutputPane::hasFilterContext() const
{
return false;
}
void IOutputPane::setFont(const QFont &font) void IOutputPane::setFont(const QFont &font)
{ {
emit fontChanged(font); emit fontChanged(font);
@@ -191,6 +196,32 @@ void IOutputPane::setupFilterUi(const Key &historyKey)
updateFilter(); 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<NumericOption> 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<NumericOption> option = NumericOption::get(action);
QTC_ASSERT(option, return);
m_afterContext = option->currentValue;
updateFilter();
});
m_filterOutputLineEdit = new FancyLineEdit; m_filterOutputLineEdit = new FancyLineEdit;
m_filterOutputLineEdit->setPlaceholderText(Tr::tr("Filter output...")); m_filterOutputLineEdit->setPlaceholderText(Tr::tr("Filter output..."));
m_filterOutputLineEdit->setButtonVisible(FancyLineEdit::Left, true); m_filterOutputLineEdit->setButtonVisible(FancyLineEdit::Left, true);
@@ -252,8 +283,16 @@ void IOutputPane::updateFilter()
void IOutputPane::filterOutputButtonClicked() void IOutputPane::filterOutputButtonClicked()
{ {
auto popup = new Core::OptionsPopup(m_filterOutputLineEdit, QVector<Utils::Id> commands = {filterRegexpActionId(),
{filterRegexpActionId(), filterCaseSensitivityActionId(), filterInvertedActionId()}); filterCaseSensitivityActionId(),
filterInvertedActionId()};
if (hasFilterContext()) {
commands.emplaceBack(filterBeforeActionId());
commands.emplaceBack(filterAfterActionId());
}
auto popup = new Core::OptionsPopup(m_filterOutputLineEdit, commands);
popup->show(); popup->show();
} }
@@ -278,6 +317,16 @@ Id IOutputPane::filterInvertedActionId() const
return Id("OutputFilter.Invert").withSuffix(metaObject()->className()); 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) void IOutputPane::setCaseSensitive(bool caseSensitive)
{ {
m_filterCaseSensitivity = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; m_filterCaseSensitivity = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;

View File

@@ -70,6 +70,8 @@ public:
int lastFilteredBlockNumber = -1; int lastFilteredBlockNumber = -1;
QPalette originalPalette; QPalette originalPalette;
OutputWindow::FilterModeFlags filterMode = OutputWindow::FilterModeFlag::Default; OutputWindow::FilterModeFlags filterMode = OutputWindow::FilterModeFlag::Default;
int beforeContext = 0;
int afterContext = 0;
QTimer scrollTimer; QTimer scrollTimer;
QElapsedTimer lastMessage; QElapsedTimer lastMessage;
QHash<unsigned int, QPair<int, int>> taskPositions; QHash<unsigned int, QPair<int, int>> taskPositions;
@@ -337,14 +339,19 @@ void OutputWindow::updateFilterProperties(
const QString &filterText, const QString &filterText,
Qt::CaseSensitivity caseSensitivity, Qt::CaseSensitivity caseSensitivity,
bool isRegexp, bool isRegexp,
bool isInverted bool isInverted,
int beforeContext,
int afterContext
) )
{ {
FilterModeFlags flags; FilterModeFlags flags;
flags.setFlag(FilterModeFlag::CaseSensitive, caseSensitivity == Qt::CaseSensitive) flags.setFlag(FilterModeFlag::CaseSensitive, caseSensitivity == Qt::CaseSensitive)
.setFlag(FilterModeFlag::RegExp, isRegexp) .setFlag(FilterModeFlag::RegExp, isRegexp)
.setFlag(FilterModeFlag::Inverted, isInverted); .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; return;
d->lastFilteredBlockNumber = -1; d->lastFilteredBlockNumber = -1;
if (d->filterText != filterText) { if (d->filterText != filterText) {
@@ -371,6 +378,8 @@ void OutputWindow::updateFilterProperties(
} }
} }
d->filterMode = flags; d->filterMode = flags;
d->beforeContext = beforeContext;
d->afterContext = afterContext;
filterNewContent(); filterNewContent();
} }
@@ -409,13 +418,33 @@ void OutputWindow::filterNewContent()
QTC_ASSERT(findNextMatch, return); QTC_ASSERT(findNextMatch, return);
const bool invert = d->filterMode.testFlag(FilterModeFlag::Inverted) const bool invert = d->filterMode.testFlag(FilterModeFlag::Inverted)
&& !d->filterText.isEmpty(); && !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<int> matchedBlocks;
QTextBlock lastBlock = document()->findBlockByNumber(firstBlockIndex);
if (!lastBlock.isValid()) if (!lastBlock.isValid())
lastBlock = document()->begin(); lastBlock = document()->begin();
for (; lastBlock != document()->end(); lastBlock = lastBlock.next()) // Find matching text blocks for the current filter.
lastBlock.setVisible(findNextMatch(lastBlock.text()) != invert); 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(); d->lastFilteredBlockNumber = document()->lastBlock().blockNumber();

View File

@@ -65,7 +65,9 @@ public:
const QString &filterText, const QString &filterText,
Qt::CaseSensitivity caseSensitivity, Qt::CaseSensitivity caseSensitivity,
bool regexp, bool regexp,
bool isInverted); bool isInverted,
int beforeContext,
int afterContext);
void setOutputFileNameHint(const QString &fileName); void setOutputFileNameHint(const QString &fileName);
@@ -97,7 +99,7 @@ private:
void handleOutputChunk(const QString &output, Utils::OutputFormat format); void handleOutputChunk(const QString &output, Utils::OutputFormat format);
void updateAutoScroll(); void updateAutoScroll();
using TextMatchingFunction = std::function<qsizetype(const QString &text)>; using TextMatchingFunction = std::function<bool(const QString &text)>;
TextMatchingFunction makeMatchingFunction() const; TextMatchingFunction makeMatchingFunction() const;
Internal::OutputWindowPrivate *d = nullptr; Internal::OutputWindowPrivate *d = nullptr;

View File

@@ -339,7 +339,8 @@ void AppOutputPane::updateFilter()
{ {
if (RunControlTab * const tab = currentTab()) { if (RunControlTab * const tab = currentTab()) {
tab->window->updateFilterProperties(filterText(), filterCaseSensitivity(), 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)); RunControlTab * const controlTab = tabFor(m_tabWidget->widget(i));
if (i != -1 && controlTab) { if (i != -1 && controlTab) {
controlTab->window->updateFilterProperties(filterText(), filterCaseSensitivity(), controlTab->window->updateFilterProperties(filterText(), filterCaseSensitivity(),
filterUsesRegexp(), filterIsInverted()); filterUsesRegexp(), filterIsInverted(),
beforeContext(), afterContext());
enableButtons(controlTab->runControl); enableButtons(controlTab->runControl);
} else { } else {
enableDefaultButtons(); enableDefaultButtons();
@@ -810,6 +812,11 @@ bool AppOutputPane::canNavigate() const
return false; return false;
} }
bool AppOutputPane::hasFilterContext() const
{
return true;
}
class AppOutputSettingsWidget : public Core::IOptionsPageWidget class AppOutputSettingsWidget : public Core::IOptionsPageWidget
{ {
public: public:

View File

@@ -57,6 +57,8 @@ public:
void goToPrev() override; void goToPrev() override;
bool canNavigate() const override; bool canNavigate() const override;
bool hasFilterContext() const override;
void createNewOutputWindow(RunControl *rc); void createNewOutputWindow(RunControl *rc);
void showTabFor(RunControl *rc); void showTabFor(RunControl *rc);
void setBehaviorOnOutput(RunControl *rc, AppOutputPaneMode mode); void setBehaviorOnOutput(RunControl *rc, AppOutputPaneMode mode);

View File

@@ -203,6 +203,11 @@ bool CompileOutputWindow::canNavigate() const
return false; return false;
} }
bool CompileOutputWindow::hasFilterContext() const
{
return true;
}
void CompileOutputWindow::registerPositionOf(const Task &task, int linkedOutputLines, int skipLines, void CompileOutputWindow::registerPositionOf(const Task &task, int linkedOutputLines, int skipLines,
int offset) int offset)
{ {
@@ -227,7 +232,8 @@ Utils::OutputFormatter *CompileOutputWindow::outputFormatter() const
void CompileOutputWindow::updateFilter() void CompileOutputWindow::updateFilter()
{ {
m_outputWindow->updateFilterProperties(filterText(), filterCaseSensitivity(), m_outputWindow->updateFilterProperties(filterText(), filterCaseSensitivity(),
filterUsesRegexp(), filterIsInverted()); filterUsesRegexp(), filterIsInverted(),
beforeContext(), afterContext());
} }
// CompileOutputSettings // CompileOutputSettings

View File

@@ -59,6 +59,8 @@ public:
void goToPrev() override; void goToPrev() override;
bool canNavigate() const override; bool canNavigate() const override;
bool hasFilterContext() const override;
void appendText(const QString &text, BuildStep::OutputFormat format); void appendText(const QString &text, BuildStep::OutputFormat format);
void registerPositionOf(const Task &task, int linkedOutputLines, int skipLines, int offset = 0); void registerPositionOf(const Task &task, int linkedOutputLines, int skipLines, int offset = 0);

View File

@@ -206,7 +206,9 @@ void BuildSystemOutputWindow::updateFilter()
m_filterActionCaseSensitive.isChecked() ? Qt::CaseSensitive m_filterActionCaseSensitive.isChecked() ? Qt::CaseSensitive
: Qt::CaseInsensitive, : Qt::CaseInsensitive,
m_filterActionRegexp.isChecked(), m_filterActionRegexp.isChecked(),
m_invertFilterAction.isChecked()); m_invertFilterAction.isChecked(),
0 /* before context */,
0 /* after context */);
} }
class VanishedTargetPanelItem : public TreeItem class VanishedTargetPanelItem : public TreeItem

View File

@@ -227,6 +227,11 @@ bool SerialOutputPane::canNavigate() const
return false; return false;
} }
bool SerialOutputPane::hasFilterContext() const
{
return true;
}
void SerialOutputPane::appendMessage(SerialControl *rc, const QString &out, Utils::OutputFormat format) void SerialOutputPane::appendMessage(SerialControl *rc, const QString &out, Utils::OutputFormat format)
{ {
const int index = indexOf(rc); const int index = indexOf(rc);

View File

@@ -62,6 +62,8 @@ public:
void goToPrev() final; void goToPrev() final;
bool canNavigate() const final; bool canNavigate() const final;
bool hasFilterContext() const final;
void createNewOutputWindow(SerialControl *rc); void createNewOutputWindow(SerialControl *rc);
bool closeTabs(CloseTabMode mode); bool closeTabs(CloseTabMode mode);