aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libs/utils/outputformatter.cpp86
-rw-r--r--src/libs/utils/outputformatter.h39
-rw-r--r--src/plugins/coreplugin/coreplugin.h2
-rw-r--r--src/plugins/coreplugin/outputwindow.cpp114
-rw-r--r--src/plugins/coreplugin/outputwindow.h3
-rw-r--r--src/plugins/projectexplorer/appoutputpane.cpp12
-rw-r--r--src/plugins/projectexplorer/compileoutputwindow.cpp2
-rw-r--r--src/plugins/projectexplorer/runcontrol.cpp28
-rw-r--r--src/plugins/projectexplorer/runcontrol.h4
-rw-r--r--src/plugins/python/pythonrunconfiguration.cpp20
-rw-r--r--src/plugins/qtsupport/qtoutputformatter.cpp67
-rw-r--r--src/plugins/serialterminal/serialoutputpane.cpp2
-rw-r--r--src/plugins/vcsbase/vcsoutputformatter.cpp13
-rw-r--r--src/plugins/vcsbase/vcsoutputformatter.h5
-rw-r--r--src/plugins/vcsbase/vcsoutputwindow.cpp2
15 files changed, 314 insertions, 85 deletions
diff --git a/src/libs/utils/outputformatter.cpp b/src/libs/utils/outputformatter.cpp
index ea40ddadee..a1a98a1fbe 100644
--- a/src/libs/utils/outputformatter.cpp
+++ b/src/libs/utils/outputformatter.cpp
@@ -25,8 +25,10 @@
#include "ansiescapecodehandler.h"
#include "outputformatter.h"
+#include "qtcassert.h"
#include "synchronousprocess.h"
#include "theme/theme.h"
+#include "utils/optional.h"
#include <QPair>
#include <QPlainTextEdit>
@@ -44,6 +46,7 @@ public:
QTextCursor cursor;
AnsiEscapeCodeHandler escapeCodeHandler;
QPair<QString, OutputFormat> incompleteLine;
+ optional<QTextCharFormat> formatOverride;
bool boldFontEnabled = true;
bool prependCarriageReturn = false;
};
@@ -75,9 +78,15 @@ void OutputFormatter::setPlainTextEdit(QPlainTextEdit *plainText)
void OutputFormatter::doAppendMessage(const QString &text, OutputFormat format)
{
- if (!d->cursor.atEnd() && text.startsWith('\n'))
- d->cursor.movePosition(QTextCursor::End);
- doAppendMessage(text, d->formats[format]);
+ if (handleMessage(text, format) == Status::NotHandled)
+ appendMessageDefault(text, format);
+}
+
+OutputFormatter::Status OutputFormatter::handleMessage(const QString &text, OutputFormat format)
+{
+ Q_UNUSED(text);
+ Q_UNUSED(format);
+ return Status::NotHandled;
}
void OutputFormatter::doAppendMessage(const QString &text, const QTextCharFormat &format)
@@ -118,6 +127,16 @@ QTextCharFormat OutputFormatter::linkFormat(const QTextCharFormat &inputFormat,
return result;
}
+void OutputFormatter::overrideTextCharFormat(const QTextCharFormat &fmt)
+{
+ d->formatOverride = fmt;
+}
+
+void OutputFormatter::appendMessageDefault(const QString &text, OutputFormat format)
+{
+ doAppendMessage(text, d->formatOverride ? d->formatOverride.value() : d->formats[format]);
+}
+
void OutputFormatter::clearLastLine()
{
// Note that this approach will fail if the text edit is not read-only and users
@@ -158,9 +177,10 @@ void OutputFormatter::dumpIncompleteLine(const QString &line, OutputFormat forma
d->incompleteLine.second = format;
}
-void OutputFormatter::handleLink(const QString &href)
+bool OutputFormatter::handleLink(const QString &href)
{
Q_UNUSED(href)
+ return false;
}
void OutputFormatter::clear()
@@ -236,4 +256,62 @@ void OutputFormatter::appendMessage(const QString &text, OutputFormat format)
}
}
+class AggregatingOutputFormatter::Private
+{
+public:
+ QList<OutputFormatter *> formatters;
+ OutputFormatter *nextFormatter = nullptr;
+};
+
+AggregatingOutputFormatter::AggregatingOutputFormatter() : d(new Private) {}
+AggregatingOutputFormatter::~AggregatingOutputFormatter() { delete d; }
+
+void AggregatingOutputFormatter::setFormatters(const QList<OutputFormatter *> &formatters)
+{
+ for (OutputFormatter * const f : formatters)
+ f->setPlainTextEdit(plainTextEdit());
+ d->formatters = formatters;
+ d->nextFormatter = nullptr;
+}
+
+OutputFormatter::Status AggregatingOutputFormatter::handleMessage(const QString &text,
+ OutputFormat format)
+{
+ if (d->nextFormatter) {
+ switch (d->nextFormatter->handleMessage(text, format)) {
+ case Status::Done:
+ d->nextFormatter = nullptr;
+ return Status::Done;
+ case Status::InProgress:
+ return Status::InProgress;
+ case Status::NotHandled:
+ QTC_CHECK(false);
+ d->nextFormatter = nullptr;
+ return Status::NotHandled;
+ }
+ }
+ QTC_CHECK(!d->nextFormatter);
+ for (OutputFormatter * const formatter : qAsConst(d->formatters)) {
+ switch (formatter->handleMessage(text, format)) {
+ case Status::Done:
+ return Status::Done;
+ case Status::InProgress:
+ d->nextFormatter = formatter;
+ return Status::InProgress;
+ case Status::NotHandled:
+ break;
+ }
+ }
+ return Status::NotHandled;
+}
+
+bool AggregatingOutputFormatter::handleLink(const QString &href)
+{
+ for (OutputFormatter * const f : qAsConst(d->formatters)) {
+ if (f->handleLink(href))
+ return true;
+ }
+ return false;
+}
+
} // namespace Utils
diff --git a/src/libs/utils/outputformatter.h b/src/libs/utils/outputformatter.h
index a5d9c4b331..0aaf880714 100644
--- a/src/libs/utils/outputformatter.h
+++ b/src/libs/utils/outputformatter.h
@@ -42,8 +42,11 @@ class FormattedText;
namespace Internal { class OutputFormatterPrivate; }
+class QTCREATOR_UTILS_EXPORT AggregatingOutputFormatter;
+
class QTCREATOR_UTILS_EXPORT OutputFormatter : public QObject
{
+ friend class AggregatingOutputFormatter;
public:
OutputFormatter();
~OutputFormatter() override;
@@ -55,26 +58,32 @@ public:
void appendMessage(const QString &text, OutputFormat format);
- virtual void handleLink(const QString &href);
+ virtual bool handleLink(const QString &href);
void clear();
void setBoldFontEnabled(bool enabled);
static QTextCharFormat linkFormat(const QTextCharFormat &inputFormat, const QString &href);
-protected:
- // text contains at most one line feed character, and if it does occur, it's the last character.
- // Either way, the input is to be considered "complete" for formatting purposes.
- virtual void doAppendMessage(const QString &text, OutputFormat format);
+ // For unit testing only
+ void overrideTextCharFormat(const QTextCharFormat &fmt);
- virtual void clearLastLine();
+protected:
+ enum class Status { Done, InProgress, NotHandled };
+ void appendMessageDefault(const QString &text, OutputFormat format);
+ void clearLastLine();
QTextCharFormat charFormat(OutputFormat format) const;
QList<FormattedText> parseAnsi(const QString &text, const QTextCharFormat &format);
QTextCursor &cursor() const;
private:
- void doAppendMessage(const QString &text, const QTextCharFormat &format);
+ // text contains at most one line feed character, and if it does occur, it's the last character.
+ // Either way, the input is to be considered "complete" for formatting purposes.
+ void doAppendMessage(const QString &text, OutputFormat format);
+
+ virtual Status handleMessage(const QString &text, OutputFormat format);
virtual void reset() {}
+ void doAppendMessage(const QString &text, const QTextCharFormat &format);
void append(const QString &text, const QTextCharFormat &format);
void initFormats();
void flushIncompleteLine();
@@ -83,4 +92,20 @@ private:
Internal::OutputFormatterPrivate *d;
};
+class QTCREATOR_UTILS_EXPORT AggregatingOutputFormatter : public OutputFormatter
+{
+public:
+ AggregatingOutputFormatter();
+ ~AggregatingOutputFormatter();
+
+ void setFormatters(const QList<OutputFormatter *> &formatters);
+ bool handleLink(const QString &href) override;
+
+private:
+ Status handleMessage(const QString &text, OutputFormat format) override;
+
+ class Private;
+ Private * const d;
+};
+
} // namespace Utils
diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h
index fee1458eab..04c0b1d117 100644
--- a/src/plugins/coreplugin/coreplugin.h
+++ b/src/plugins/coreplugin/coreplugin.h
@@ -76,6 +76,8 @@ private slots:
// Locator:
void test_basefilefilter();
void test_basefilefilter_data();
+
+ void testOutputFormatter();
#endif
private:
diff --git a/src/plugins/coreplugin/outputwindow.cpp b/src/plugins/coreplugin/outputwindow.cpp
index fc61bcc750..e6b03b2c7a 100644
--- a/src/plugins/coreplugin/outputwindow.cpp
+++ b/src/plugins/coreplugin/outputwindow.cpp
@@ -27,6 +27,7 @@
#include "actionmanager/actionmanager.h"
#include "coreconstants.h"
+#include "coreplugin.h"
#include "icore.h"
#include <utils/outputformatter.h>
@@ -39,6 +40,10 @@
#include <QScrollBar>
#include <QTextBlock>
+#ifdef WITH_TESTS
+#include <QtTest>
+#endif
+
using namespace Utils;
namespace Core {
@@ -60,9 +65,8 @@ public:
}
IContext *outputWindowContext = nullptr;
- QPointer<Utils::OutputFormatter> formatter;
QString settingsKey;
- OutputFormatter defaultFormatter;
+ AggregatingOutputFormatter formatter;
bool scrollToBottom = true;
bool linksActive = true;
@@ -91,7 +95,7 @@ OutputWindow::OutputWindow(Context context, const QString &settingsKey, QWidget
setFrameShape(QFrame::NoFrame);
setMouseTracking(true);
setUndoRedoEnabled(false);
- setFormatter(&d->defaultFormatter);
+ d->formatter.setPlainTextEdit(this);
d->settingsKey = settingsKey;
@@ -168,7 +172,7 @@ void OutputWindow::mouseReleaseEvent(QMouseEvent *e)
{
if (d->linksActive && d->mouseButtonPressed == Qt::LeftButton) {
const QString href = anchorAt(e->pos());
- d->formatter->handleLink(href);
+ d->formatter.handleLink(href);
}
// Mouse was released, activate links again
@@ -212,13 +216,9 @@ void OutputWindow::keyPressEvent(QKeyEvent *ev)
verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum);
}
-void OutputWindow::setFormatter(OutputFormatter *formatter)
+void OutputWindow::setFormatters(const QList<OutputFormatter *> &formatters)
{
- d->formatter = formatter;
- if (d->formatter)
- d->formatter->setPlainTextEdit(this);
- else
- d->formatter = &d->defaultFormatter;
+ d->formatter.setFormatters(formatters);
}
void OutputWindow::showEvent(QShowEvent *e)
@@ -396,7 +396,7 @@ void OutputWindow::appendMessage(const QString &output, OutputFormat format)
const bool atBottom = isScrollbarAtBottom() || m_scrollTimer.isActive();
d->scrollToBottom = true;
- d->formatter->appendMessage(out, format);
+ d->formatter.appendMessage(out, format);
if (atBottom) {
if (m_lastMessage.elapsed() < 5) {
@@ -445,7 +445,12 @@ QMimeData *OutputWindow::createMimeDataFromSelection() const
void OutputWindow::clear()
{
- d->formatter->clear();
+ d->formatter.clear();
+}
+
+void OutputWindow::flush()
+{
+ d->formatter.flush();
}
void OutputWindow::scrollToBottom()
@@ -494,4 +499,89 @@ void OutputWindow::setWordWrapEnabled(bool wrap)
setWordWrapMode(QTextOption::NoWrap);
}
+#ifdef WITH_TESTS
+
+// Handles all lines starting with "A" and the following ones up to and including the next
+// one starting with "A".
+class TestFormatterA : public OutputFormatter
+{
+private:
+ Status handleMessage(const QString &text, OutputFormat format) override
+ {
+ if (m_handling) {
+ appendMessageDefault("handled by A\n", format);
+ if (text.startsWith("A")) {
+ m_handling = false;
+ return Status::Done;
+ }
+ return Status::InProgress;
+ }
+ if (text.startsWith("A")) {
+ m_handling = true;
+ appendMessageDefault("handled by A\n", format);
+ return Status::InProgress;
+ }
+ return Status::NotHandled;
+ }
+
+ void reset() override { m_handling = false; }
+
+ bool m_handling = false;
+};
+
+// Handles all lines starting with "B". No continuation logic
+class TestFormatterB : public OutputFormatter
+{
+private:
+ Status handleMessage(const QString &text, OutputFormat format) override
+ {
+ if (text.startsWith("B")) {
+ appendMessageDefault("handled by B\n", format);
+ return Status::Done;
+ }
+ return Status::NotHandled;
+ }
+};
+
+void Internal::CorePlugin::testOutputFormatter()
+{
+ const QString input =
+ "B to be handled by B\r\n"
+ "not to be handled\n"
+ "A to be handled by A\n"
+ "continuation for A\r\n"
+ "B looks like B, but still continuation for A\r\n"
+ "A end of A\n"
+ "A next A\n"
+ "A end of next A\n"
+ " A trick\r\n"
+ "B to be handled by B\n";
+ const QString output =
+ "handled by B\n"
+ "not to be handled\n"
+ "handled by A\n"
+ "handled by A\n"
+ "handled by A\n"
+ "handled by A\n"
+ "handled by A\n"
+ "handled by A\n"
+ " A trick\n"
+ "handled by B\n";
+ TestFormatterA formatterA;
+ TestFormatterB formatterB;
+ AggregatingOutputFormatter formatter;
+ QPlainTextEdit textEdit;
+ formatter.setPlainTextEdit(&textEdit);
+ formatter.setFormatters({&formatterB, &formatterA});
+
+ // Stress-test the implementation by providing the input in chunks, splitting at all possible
+ // offsets.
+ for (int i = 0; i < input.length(); ++i) {
+ formatter.appendMessage(input.left(i), NormalMessageFormat);
+ formatter.appendMessage(input.mid(i), NormalMessageFormat);
+ QCOMPARE(textEdit.toPlainText(), output);
+ formatter.clear();
+ }
+}
+#endif // WITH_TESTS
} // namespace Core
diff --git a/src/plugins/coreplugin/outputwindow.h b/src/plugins/coreplugin/outputwindow.h
index a90e9ced66..77703f0b9a 100644
--- a/src/plugins/coreplugin/outputwindow.h
+++ b/src/plugins/coreplugin/outputwindow.h
@@ -56,12 +56,13 @@ public:
OutputWindow(Context context, const QString &settingsKey, QWidget *parent = nullptr);
~OutputWindow() override;
- void setFormatter(Utils::OutputFormatter *formatter);
+ void setFormatters(const QList<Utils::OutputFormatter *> &formatters);
void appendMessage(const QString &out, Utils::OutputFormat format);
void grayOutOldContent();
void clear();
+ void flush();
void scrollToBottom();
diff --git a/src/plugins/projectexplorer/appoutputpane.cpp b/src/plugins/projectexplorer/appoutputpane.cpp
index d9261efa5e..cb24b7c1c2 100644
--- a/src/plugins/projectexplorer/appoutputpane.cpp
+++ b/src/plugins/projectexplorer/appoutputpane.cpp
@@ -154,7 +154,7 @@ AppOutputPane::RunControlTab::RunControlTab(RunControl *runControl, Core::Output
runControl(runControl), window(w)
{
if (runControl && w)
- w->setFormatter(runControl->outputFormatter());
+ w->setFormatters(runControl->outputFormatters());
}
AppOutputPane::AppOutputPane() :
@@ -404,7 +404,7 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc)
if (tab.runControl)
tab.runControl->initiateFinish();
tab.runControl = rc;
- tab.window->setFormatter(rc->outputFormatter());
+ tab.window->setFormatters(rc->outputFormatters());
handleOldOutput(tab.window);
@@ -743,8 +743,12 @@ void AppOutputPane::slotRunControlFinished()
{
auto *rc = qobject_cast<RunControl *>(sender());
QTimer::singleShot(0, this, [this, rc]() { slotRunControlFinished2(rc); });
- if (rc->outputFormatter())
- rc->outputFormatter()->flush();
+ for (const RunControlTab &t : m_runControlTabs) {
+ if (t.runControl == rc) {
+ t.window->flush();
+ break;
+ }
+ }
}
void AppOutputPane::slotRunControlFinished2(RunControl *sender)
diff --git a/src/plugins/projectexplorer/compileoutputwindow.cpp b/src/plugins/projectexplorer/compileoutputwindow.cpp
index 3eeedee281..f259d1f766 100644
--- a/src/plugins/projectexplorer/compileoutputwindow.cpp
+++ b/src/plugins/projectexplorer/compileoutputwindow.cpp
@@ -135,7 +135,7 @@ CompileOutputWindow::CompileOutputWindow(QAction *cancelBuildAction) :
m_outputWindow->setReadOnly(true);
m_outputWindow->setUndoRedoEnabled(false);
m_outputWindow->setMaxCharCount(Core::Constants::DEFAULT_MAX_CHAR_COUNT);
- m_outputWindow->setFormatter(m_formatter);
+ m_outputWindow->setFormatters({m_formatter});
// Let selected text be colored as if the text edit was editable,
// otherwise the highlight for searching is too light
diff --git a/src/plugins/projectexplorer/runcontrol.cpp b/src/plugins/projectexplorer/runcontrol.cpp
index 37bf2f3c49..18cf5a3099 100644
--- a/src/plugins/projectexplorer/runcontrol.cpp
+++ b/src/plugins/projectexplorer/runcontrol.cpp
@@ -279,7 +279,6 @@ public:
: q(parent), runMode(mode)
{
icon = Icons::RUN_SMALL_TOOLBAR;
- outputFormatter = new OutputFormatter();
}
~RunControlPrivate() override
@@ -289,7 +288,7 @@ public:
q = nullptr;
qDeleteAll(m_workers);
m_workers.clear();
- delete outputFormatter;
+ qDeleteAll(outputFormatters);
}
Q_ENUM(RunControlState)
@@ -334,7 +333,7 @@ public:
Kit *kit = nullptr; // Not owned.
QPointer<Target> target; // Not owned.
QPointer<Project> project; // Not owned.
- QPointer<Utils::OutputFormatter> outputFormatter = nullptr;
+ QList<Utils::OutputFormatter *> outputFormatters;
std::function<bool(bool*)> promptToStop;
std::vector<RunWorkerFactory> m_factories;
@@ -385,10 +384,8 @@ void RunControl::setTarget(Target *target)
d->buildEnvironment = bc->environment();
}
- delete d->outputFormatter;
- d->outputFormatter = OutputFormatterFactory::createFormatter(target);
- if (!d->outputFormatter)
- d->outputFormatter = new OutputFormatter();
+ QTC_CHECK(d->outputFormatters.isEmpty());
+ d->outputFormatters = OutputFormatterFactory::createFormatters(target);
setKit(target->kit());
d->project = target->project();
@@ -831,9 +828,9 @@ void RunControlPrivate::showError(const QString &msg)
q->appendMessage(msg + '\n', ErrorMessageFormat);
}
-Utils::OutputFormatter *RunControl::outputFormatter() const
+QList<Utils::OutputFormatter *> RunControl::outputFormatters() const
{
- return d->outputFormatter;
+ return d->outputFormatters;
}
Core::Id RunControl::runMode() const
@@ -1601,11 +1598,7 @@ static QList<OutputFormatterFactory *> g_outputFormatterFactories;
OutputFormatterFactory::OutputFormatterFactory()
{
- // This is a bit cheating: We know that only two formatters exist right now,
- // and this here gives the second (python) implicit more priority.
- // For a final solution, probably all matching formatters should be used
- // in parallel, so there's no need to invent a fancy priority system here.
- g_outputFormatterFactories.prepend(this);
+ g_outputFormatterFactories.append(this);
}
OutputFormatterFactory::~OutputFormatterFactory()
@@ -1613,13 +1606,14 @@ OutputFormatterFactory::~OutputFormatterFactory()
g_outputFormatterFactories.removeOne(this);
}
-OutputFormatter *OutputFormatterFactory::createFormatter(Target *target)
+QList<OutputFormatter *> OutputFormatterFactory::createFormatters(Target *target)
{
+ QList<OutputFormatter *> formatters;
for (auto factory : qAsConst(g_outputFormatterFactories)) {
if (auto formatter = factory->m_creator(target))
- return formatter;
+ formatters << formatter;
}
- return nullptr;
+ return formatters;
}
void OutputFormatterFactory::setFormatterCreator
diff --git a/src/plugins/projectexplorer/runcontrol.h b/src/plugins/projectexplorer/runcontrol.h
index cda6d6ab55..99c6a7636c 100644
--- a/src/plugins/projectexplorer/runcontrol.h
+++ b/src/plugins/projectexplorer/runcontrol.h
@@ -238,7 +238,7 @@ public:
Utils::FilePath targetFilePath() const;
Utils::FilePath projectFilePath() const;
- Utils::OutputFormatter *outputFormatter() const;
+ QList<Utils::OutputFormatter *> outputFormatters() const;
Core::Id runMode() const;
const Runnable &runnable() const;
@@ -309,7 +309,7 @@ protected:
public:
virtual ~OutputFormatterFactory();
- static Utils::OutputFormatter *createFormatter(Target *target);
+ static QList<Utils::OutputFormatter *> createFormatters(Target *target);
protected:
void setFormatterCreator(const std::function<Utils::OutputFormatter *(Target *)> &creator);
diff --git a/src/plugins/python/pythonrunconfiguration.cpp b/src/plugins/python/pythonrunconfiguration.cpp
index 5ea0bf1f0f..a01d08e39a 100644
--- a/src/plugins/python/pythonrunconfiguration.cpp
+++ b/src/plugins/python/pythonrunconfiguration.cpp
@@ -71,12 +71,16 @@ public:
}
private:
- void doAppendMessage(const QString &text, OutputFormat format) final
+ Status handleMessage(const QString &text, OutputFormat format) final
{
if (!m_inTraceBack) {
m_inTraceBack = format == StdErrFormat
&& text.startsWith("Traceback (most recent call last):");
- OutputFormatter::doAppendMessage(text, format);
+ if (m_inTraceBack) {
+ OutputFormatter::appendMessageDefault(text, format);
+ return Status::InProgress;
+ }
+ return Status::NotHandled;
}
const Core::Id category(PythonErrorTaskCategory);
@@ -90,9 +94,10 @@ private:
const auto fileName = FilePath::fromString(match.captured(3));
const int lineNumber = match.capturedRef(4).toInt();
m_tasks.append({Task::Warning, QString(), fileName, lineNumber, category});
- return;
+ return Status::InProgress;
}
+ Status status = Status::InProgress;
if (text.startsWith(' ')) {
// Neither traceback start, nor file, nor error message line.
// Not sure if that can actually happen.
@@ -111,18 +116,21 @@ private:
TaskHub::addTask(*rit);
m_tasks.clear();
m_inTraceBack = false;
+ status = Status::Done;
}
- OutputFormatter::doAppendMessage(text, format);
+ OutputFormatter::appendMessageDefault(text, format);
+ return status;
}
- void handleLink(const QString &href) final
+ bool handleLink(const QString &href) final
{
const QRegularExpressionMatch match = filePattern.match(href);
if (!match.hasMatch())
- return;
+ return false;
const QString fileName = match.captured(3);
const int lineNumber = match.capturedRef(4).toInt();
Core::EditorManager::openEditorAt(fileName, lineNumber);
+ return true;
}
void reset() override
diff --git a/src/plugins/qtsupport/qtoutputformatter.cpp b/src/plugins/qtsupport/qtoutputformatter.cpp
index fdaef6004b..76cb10422b 100644
--- a/src/plugins/qtsupport/qtoutputformatter.cpp
+++ b/src/plugins/qtsupport/qtoutputformatter.cpp
@@ -45,6 +45,8 @@
#include <QTextCursor>
#include <QUrl>
+#include <tuple>
+
using namespace ProjectExplorer;
using namespace Utils;
@@ -95,14 +97,13 @@ protected:
virtual void openEditor(const QString &fileName, int line, int column = -1);
private:
- void doAppendMessage(const QString &text, Utils::OutputFormat format) override;
- void handleLink(const QString &href) override;
+ Status handleMessage(const QString &text, Utils::OutputFormat format) override;
+ bool handleLink(const QString &href) override;
void updateProjectFileList();
LinkResult matchLine(const QString &line) const;
- void appendMessagePart(const QString &txt, const QTextCharFormat &fmt);
void appendLine(const LinkResult &lr, const QString &line, const QTextCharFormat &format);
- void doAppendMessage(const QString &txt, const QTextCharFormat &format);
+ Status doAppendMessage(const QString &txt, const QTextCharFormat &format);
QtOutputFormatterPrivate *d;
friend class QtSupportPlugin; // for testing
@@ -160,25 +161,39 @@ LinkResult QtOutputFormatter::matchLine(const QString &line) const
return lr;
}
-void QtOutputFormatter::doAppendMessage(const QString &txt, const QTextCharFormat &format)
+OutputFormatter::Status QtOutputFormatter::doAppendMessage(const QString &txt,
+ const QTextCharFormat &format)
{
+ // FIXME: We'll do the ANSI parsing twice if there is no match.
+ // Ideally, we'd (optionally) pre-process ANSI escape codes in the
+ // base class before passing the text here, but then we can no longer
+ // pass complete lines...
const QList<FormattedText> ansiTextList = parseAnsi(txt, format);
- for (const FormattedText &output : ansiTextList)
- appendMessagePart(output.text, output.format);
-}
-
-void QtOutputFormatter::doAppendMessage(const QString &txt, OutputFormat format)
-{
- doAppendMessage(txt, charFormat(format));
+ QList<std::tuple<QString, QTextCharFormat, LinkResult>> parts;
+ bool hasMatches = false;
+ for (const FormattedText &output : ansiTextList) {
+ const LinkResult lr = matchLine(output.text);
+ if (!lr.href.isEmpty())
+ hasMatches = true;
+ parts << std::make_tuple(output.text, output.format, lr);
+ }
+ if (!hasMatches)
+ return Status::NotHandled;
+ for (const auto &part : parts) {
+ const LinkResult &lr = std::get<2>(part);
+ const QString &text = std::get<0>(part);
+ const QTextCharFormat &fmt = std::get<1>(part);
+ if (!lr.href.isEmpty())
+ appendLine(lr, text, fmt);
+ else
+ cursor().insertText(text, fmt);
+ }
+ return Status::Done;
}
-void QtOutputFormatter::appendMessagePart(const QString &txt, const QTextCharFormat &fmt)
+QtOutputFormatter::Status QtOutputFormatter::handleMessage(const QString &txt, OutputFormat format)
{
- const LinkResult lr = matchLine(txt);
- if (!lr.href.isEmpty())
- appendLine(lr, txt, fmt);
- else
- cursor().insertText(txt, fmt);
+ return doAppendMessage(txt, charFormat(format));
}
void QtOutputFormatter::appendLine(const LinkResult &lr, const QString &line,
@@ -189,7 +204,7 @@ void QtOutputFormatter::appendLine(const LinkResult &lr, const QString &line,
cursor().insertText(line.mid(lr.end), format);
}
-void QtOutputFormatter::handleLink(const QString &href)
+bool QtOutputFormatter::handleLink(const QString &href)
{
if (!href.isEmpty()) {
static const QRegularExpression qmlLineColumnLink("^(" QT_QML_URL_REGEXP ")" // url
@@ -205,7 +220,7 @@ void QtOutputFormatter::handleLink(const QString &href)
const int line = qmlLineColumnMatch.captured(2).toInt();
const int column = qmlLineColumnMatch.captured(3).toInt();
openEditor(getFileToOpen(fileUrl), line, column - 1);
- return;
+ return true;
}
static const QRegularExpression qmlLineLink("^(" QT_QML_URL_REGEXP ")" // url
@@ -220,7 +235,7 @@ void QtOutputFormatter::handleLink(const QString &href)
fileUrl = QUrl::fromLocalFile(filePath.mid(int(strlen(scheme))));
const int line = qmlLineMatch.captured(2).toInt();
openEditor(getFileToOpen(fileUrl), line);
- return;
+ return true;
}
QString fileName;
@@ -250,9 +265,10 @@ void QtOutputFormatter::handleLink(const QString &href)
if (!fileName.isEmpty()) {
fileName = getFileToOpen(QUrl::fromLocalFile(fileName));
openEditor(fileName, line);
- return;
+ return true;
}
}
+ return false;
}
void QtOutputFormatter::openEditor(const QString &fileName, int line, int column)
@@ -504,8 +520,13 @@ void QtSupportPlugin::testQtOutputFormatter_appendMessage()
QFETCH(QString, outputText);
QFETCH(QTextCharFormat, inputFormat);
QFETCH(QTextCharFormat, outputFormat);
+ if (outputFormat == QTextCharFormat())
+ outputFormat = formatter.charFormat(DebugFormat);
+ if (inputFormat != QTextCharFormat())
+ formatter.overrideTextCharFormat(inputFormat);
- formatter.doAppendMessage(inputText, inputFormat);
+ formatter.appendMessage(inputText, DebugFormat);
+ formatter.flush();
QCOMPARE(edit.toPlainText(), outputText);
QCOMPARE(edit.currentCharFormat(), outputFormat);
diff --git a/src/plugins/serialterminal/serialoutputpane.cpp b/src/plugins/serialterminal/serialoutputpane.cpp
index e9dfb50ce7..eab36c46be 100644
--- a/src/plugins/serialterminal/serialoutputpane.cpp
+++ b/src/plugins/serialterminal/serialoutputpane.cpp
@@ -315,7 +315,7 @@ void SerialOutputPane::createNewOutputWindow(SerialControl *rc)
this, fontSettingsChanged);
fontSettingsChanged();
ow->setWindowTitle(tr("Serial Terminal Window"));
- ow->setFormatter(formatter);
+ ow->setFormatters({formatter});
// TODO: wordwrap, maxLineCount, zoom/wheelZoom (add to settings)
auto controlTab = SerialControlTab(rc, ow);
diff --git a/src/plugins/vcsbase/vcsoutputformatter.cpp b/src/plugins/vcsbase/vcsoutputformatter.cpp
index 4915dda403..4145bd0618 100644
--- a/src/plugins/vcsbase/vcsoutputformatter.cpp
+++ b/src/plugins/vcsbase/vcsoutputformatter.cpp
@@ -42,14 +42,17 @@ VcsOutputFormatter::VcsOutputFormatter() :
{
}
-void VcsOutputFormatter::doAppendMessage(const QString &text, Utils::OutputFormat format)
+VcsOutputFormatter::Status VcsOutputFormatter::handleMessage(const QString &text,
+ Utils::OutputFormat format)
{
QRegularExpressionMatchIterator it = m_regexp.globalMatch(text);
+ if (!it.hasNext())
+ return Status::NotHandled;
int begin = 0;
while (it.hasNext()) {
const QRegularExpressionMatch match = it.next();
const QTextCharFormat normalFormat = charFormat(format);
- OutputFormatter::doAppendMessage(text.mid(begin, match.capturedStart() - begin), format);
+ appendMessageDefault(text.mid(begin, match.capturedStart() - begin), format);
QTextCursor tc = plainTextEdit()->textCursor();
QStringView url = match.capturedView();
begin = match.capturedEnd();
@@ -61,15 +64,17 @@ void VcsOutputFormatter::doAppendMessage(const QString &text, Utils::OutputForma
tc.insertText(url.toString(), linkFormat(normalFormat, url.toString()));
tc.movePosition(QTextCursor::End);
}
- OutputFormatter::doAppendMessage(text.mid(begin), format);
+ appendMessageDefault(text.mid(begin), format);
+ return Status::Done;
}
-void VcsOutputFormatter::handleLink(const QString &href)
+bool VcsOutputFormatter::handleLink(const QString &href)
{
if (href.startsWith("http://") || href.startsWith("https://"))
QDesktopServices::openUrl(QUrl(href));
else if (!href.isEmpty())
emit referenceClicked(href);
+ return true;
}
void VcsOutputFormatter::fillLinkContextMenu(
diff --git a/src/plugins/vcsbase/vcsoutputformatter.h b/src/plugins/vcsbase/vcsoutputformatter.h
index fe1bae89fd..5074fc3392 100644
--- a/src/plugins/vcsbase/vcsoutputformatter.h
+++ b/src/plugins/vcsbase/vcsoutputformatter.h
@@ -37,14 +37,15 @@ class VcsOutputFormatter : public Utils::OutputFormatter
public:
VcsOutputFormatter();
~VcsOutputFormatter() override = default;
- void doAppendMessage(const QString &text, Utils::OutputFormat format) override;
- void handleLink(const QString &href) override;
+ bool handleLink(const QString &href) override;
void fillLinkContextMenu(QMenu *menu, const QString &workingDirectory, const QString &href);
signals:
void referenceClicked(const QString &reference);
private:
+ Status handleMessage(const QString &text, Utils::OutputFormat format) override;
+
const QRegularExpression m_regexp;
};
diff --git a/src/plugins/vcsbase/vcsoutputwindow.cpp b/src/plugins/vcsbase/vcsoutputwindow.cpp
index 2391a1bb03..f1ca4da868 100644
--- a/src/plugins/vcsbase/vcsoutputwindow.cpp
+++ b/src/plugins/vcsbase/vcsoutputwindow.cpp
@@ -123,7 +123,7 @@ OutputWindowPlainTextEdit::OutputWindowPlainTextEdit(QWidget *parent) :
setFrameStyle(QFrame::NoFrame);
m_formatter = new VcsOutputFormatter;
m_formatter->setBoldFontEnabled(false);
- setFormatter(m_formatter);
+ setFormatters({m_formatter});
auto agg = new Aggregation::Aggregate;
agg->add(this);
agg->add(new Core::BaseTextFind(this));