Remove the limitation that output formatters have to be exclusive

Introduce an aggregating output formatter that forwards its input to a
sub-formatter that feels responsible for it, or otherwise lets the base
class handle it.
Our output panes now use such an aggregating formatter.
In particular, this means that in the future, we won't have to stuff all
run control output formatting into the Qt output formatter anymore.

Change-Id: I5498f200a61db10ccff3ec8974c6825da7f7072d
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Christian Kandeler
2020-03-19 16:00:37 +01:00
parent 7158e67612
commit 04a99c1de1
15 changed files with 314 additions and 85 deletions

View File

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

View File

@@ -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);
// For unit testing only
void overrideTextCharFormat(const QTextCharFormat &fmt);
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);
virtual void clearLastLine();
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

View File

@@ -76,6 +76,8 @@ private slots:
// Locator:
void test_basefilefilter();
void test_basefilefilter_data();
void testOutputFormatter();
#endif
private:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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));
}
void QtOutputFormatter::appendMessagePart(const QString &txt, const QTextCharFormat &fmt)
{
const LinkResult lr = matchLine(txt);
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())
appendLine(lr, txt, fmt);
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(txt, fmt);
cursor().insertText(text, fmt);
}
return Status::Done;
}
QtOutputFormatter::Status QtOutputFormatter::handleMessage(const QString &txt, OutputFormat format)
{
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);

View File

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

View File

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

View File

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

View File

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