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>
<translation>Öffne Datei &quot;%1&quot; mit:</translation>
</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>
<source>Maximize</source>
<translation>Maximieren</translation>
@@ -21807,6 +21817,14 @@ Doppelklicken Sie einen Eintrag um ihn zu ändern.</translation>
<source>Show Non-matching Lines</source>
<translation>Zeige nicht übereinstimmende Zeilen</translation>
</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>
<source>Filter output...</source>
<translation>Ausgabe filtern...</translation>

View File

@@ -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)

View File

@@ -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;
};

View File

@@ -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

View File

@@ -33,6 +33,8 @@ public:
void goToPrev() override;
bool canNavigate() const override;
bool hasFilterContext() const override;
private:
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)
{
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<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->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<Utils::Id> 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;

View File

@@ -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<unsigned int, QPair<int, int>> 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<int> 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();

View File

@@ -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<qsizetype(const QString &text)>;
using TextMatchingFunction = std::function<bool(const QString &text)>;
TextMatchingFunction makeMatchingFunction() const;
Internal::OutputWindowPrivate *d = nullptr;

View File

@@ -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:

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

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