Skip to content

Commit

Permalink
implement syntax highlighting with QTextLayout
Browse files Browse the repository at this point in the history
Using QTextDocument was a little bit overkill since we don't need rich
text support. This replaces it with a simple QTextLayout implementation.

closed: #522, #505
  • Loading branch information
lievenhey committed Oct 30, 2023
1 parent ebabd91 commit f765a2f
Show file tree
Hide file tree
Showing 15 changed files with 451 additions and 235 deletions.
3 changes: 2 additions & 1 deletion src/models/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ add_library(
disassemblyoutput.cpp
eventmodel.cpp
filterandzoomstack.cpp
formattingutils.cpp
frequencymodel.cpp
highlighter.cpp
highlightedtext.cpp
processfiltermodel.cpp
processlist_unix.cpp
processmodel.cpp
Expand Down
39 changes: 14 additions & 25 deletions src/models/disassemblymodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,15 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/

#include <QFontDatabase>
#include <QTextBlock>
#include <QTextDocument>

#include "disassemblymodel.h"

#include "highlighter.hpp"
#include "search.h"
#include "sourcecodemodel.h"

DisassemblyModel::DisassemblyModel(KSyntaxHighlighting::Repository* repository, QObject* parent)
: QAbstractTableModel(parent)
, m_document(new QTextDocument(this))
, m_highlighter(new Highlighter(m_document, repository, this))
, m_highlightedText(repository)
{
m_document->setUndoRedoEnabled(false);
}

DisassemblyModel::~DisassemblyModel() = default;
Expand Down Expand Up @@ -56,17 +49,13 @@ void DisassemblyModel::setDisassembly(const DisassemblyOutput& disassemblyOutput
m_results = results;
m_numTypes = results.selfCosts.numTypes();

m_document->clear();

QTextCursor cursor(m_document);
cursor.beginEditBlock();
for (const auto& it : disassemblyOutput.disassemblyLines) {
cursor.insertText(it.disassembly);
cursor.insertBlock();
}
cursor.endEditBlock();
QStringList assemblyLines;
assemblyLines.reserve(disassemblyOutput.disassemblyLines.size());
std::transform(disassemblyOutput.disassemblyLines.cbegin(), disassemblyOutput.disassemblyLines.cend(),
std::back_inserter(assemblyLines),
[](const DisassemblyOutput::DisassemblyLine& line) { return line.disassembly; });

m_document->setTextWidth(m_document->idealWidth());
m_highlightedText.setText(assemblyLines);

endResetModel();
}
Expand Down Expand Up @@ -112,6 +101,7 @@ QVariant DisassemblyModel::data(const QModelIndex& index, int role) const
}

const auto& data = m_data.disassemblyLines.at(index.row());
const auto& line = m_highlightedText.textAt(index.row());
if (role == AddrRole)
return data.addr;

Expand All @@ -127,10 +117,10 @@ QVariant DisassemblyModel::data(const QModelIndex& index, int role) const
} else if (index.column() == HexdumpColumn) {
return data.hexdump;
} else if (index.column() == DisassemblyColumn) {
const auto block = m_document->findBlockByLineNumber(index.row());
if (role == SyntaxHighlightRole)
return QVariant::fromValue(block.layout()->lineAt(0));
return block.text();
if (role == SyntaxHighlightRole) {
return QVariant::fromValue(m_highlightedText.lineAt(index.row()));
}
return m_highlightedText.textAt(index.row());
}
}

Expand All @@ -153,7 +143,7 @@ QVariant DisassemblyModel::data(const QModelIndex& index, int role) const
return totalCost;
} else if (role == Qt::ToolTipRole) {
auto tooltip = tr("addr: <tt>%1</tt><br/>assembly: <tt>%2</tt><br/>disassembly: <tt>%3</tt>")
.arg(QString::number(data.addr, 16), data.disassembly);
.arg(QString::number(data.addr, 16), line);
return Util::formatTooltip(tooltip, locationCost, m_results.selfCosts);
}

Expand All @@ -162,8 +152,7 @@ QVariant DisassemblyModel::data(const QModelIndex& index, int role) const
return Util::formatCostRelative(costLine, totalCost, true);
} else {
if (role == Qt::ToolTipRole)
return tr("<qt><tt>%1</tt><hr/>No samples at this location.</qt>")
.arg(data.disassembly.toHtmlEscaped());
return tr("<qt><tt>%1</tt><hr/>No samples at this location.</qt>").arg(line.toHtmlEscaped());
else
return QString();
}
Expand Down
11 changes: 4 additions & 7 deletions src/models/disassemblymodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@

#include "data.h"
#include "disassemblyoutput.h"

class QTextDocument;
class Highlighter;
#include "highlightedtext.h"

namespace KSyntaxHighlighting {
class Definition;
Expand Down Expand Up @@ -47,9 +45,9 @@ class DisassemblyModel : public QAbstractTableModel
Data::FileLine fileLineForIndex(const QModelIndex& index) const;
QModelIndex indexForFileLine(const Data::FileLine& line) const;

Highlighter* highlighter() const
HighlightedText* highlightedText()
{
return m_highlighter;
return &m_highlightedText;
}

enum Columns
Expand Down Expand Up @@ -82,8 +80,7 @@ public slots:
void find(const QString& search, Direction direction, int offset);

private:
QTextDocument* m_document;
Highlighter* m_highlighter;
HighlightedText m_highlightedText;
DisassemblyOutput m_data;
Data::CallerCalleeResults m_results;
int m_numTypes = 0;
Expand Down
40 changes: 35 additions & 5 deletions src/models/disassemblyoutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,43 @@
#include <QProcess>
#include <QStandardPaths>

#include "formattingutils.h"

namespace {
Q_LOGGING_CATEGORY(disassemblyoutput, "hotspot.disassemblyoutput")

bool canVisualizeJumps(const QString& objdump)
QByteArray objdumpHelp(const QString& objdump)
{
QProcess process;
process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
process.start(objdump, {QStringLiteral("-H")});
if (!process.waitForFinished(1000)) {
qCWarning(disassemblyoutput) << "failed to query objdump help output:" << objdump << process.errorString();
return false;
return {};
}
const auto help = process.readAllStandardOutput();
return help.contains("--visualize-jumps");
return process.readAllStandardOutput();
}

bool canVisualizeJumps(const QString& objdump)
{
return objdumpHelp(objdump).contains(QByteArrayLiteral("--visualize-jumps"));
}

bool canUseSyntaxHighlighting(const QString& objdump)
{
return objdumpHelp(objdump).contains(QByteArrayLiteral("--disassembler-color"));
}

DisassemblyOutput::LinkedFunction extractLinkedFunction(const QString& disassembly)
DisassemblyOutput::LinkedFunction extractLinkedFunction(const QString& disassemblyWithAnsi)
{
DisassemblyOutput::LinkedFunction function = {};

const auto disassembly =
disassemblyWithAnsi.contains(Util::escapeChar) ? Util::removeAnsi(disassemblyWithAnsi) : disassemblyWithAnsi;

const auto leftBracketIndex = disassembly.indexOf(QLatin1Char('<'));
const auto rightBracketIndex = disassembly.indexOf(QLatin1Char('>'));

if (leftBracketIndex != -1 && rightBracketIndex != -1) {
if (leftBracketIndex < rightBracketIndex) {
function.name = disassembly.mid(leftBracketIndex + 1, rightBracketIndex - leftBracketIndex - 1);
Expand All @@ -61,6 +76,7 @@ DisassemblyOutput::LinkedFunction extractLinkedFunction(const QString& disassemb
}
}
}

return function;
}

Expand Down Expand Up @@ -213,6 +229,14 @@ DisassemblyOutput::ObjectdumpOutput DisassemblyOutput::objdumpParse(const QByteA

const auto parts = asmLine.split(QLatin1Char('\t'));

if (parts.size() == 1 && asmLine.endsWith(QLatin1Char(':'))) {
// we got a line like:
// std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_local_data():
// pass them to the disassembler since this can be used for inlining
disassemblyLines.push_back({0, asmLine, {}, {}, {}, {currentSourceFileName, sourceCodeLine}});
continue;
}

const auto addr = [addrString = parts.value(0).trimmed(), &asmLine]() -> uint64_t {
const auto suffix = QLatin1Char(':');
if (!addrString.endsWith(suffix))
Expand Down Expand Up @@ -303,6 +327,12 @@ DisassemblyOutput DisassemblyOutput::disassemble(const QString& objdump, const Q
else
qCInfo(disassemblyoutput) << "objdump binary does not support `--visualize-jumps`:" << processPath;

if (canUseSyntaxHighlighting(processPath)) {
arguments.append(QStringLiteral("--disassembler-color=color"));
} else {
qCInfo(disassemblyoutput) << "objdump binary does not support `--disassembler-color`:" << processPath;
}

auto binary = findBinaryForSymbol(debugPaths, extraLibPaths, symbol);
if (binary.isEmpty()) {
disassemblyOutput.errorMessage +=
Expand Down
24 changes: 24 additions & 0 deletions src/models/formattingutils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
SPDX-FileCopyrightText: Lieven Hey <[email protected]>
SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "formattingutils.h"

QString Util::removeAnsi(const QString& stringWithAnsi)
{
const static QChar escapeChar = QLatin1Char('\u001B');
if (!stringWithAnsi.contains(escapeChar)) {
return stringWithAnsi;
}

QString ansiFreeString = stringWithAnsi;
while (ansiFreeString.contains(escapeChar)) {
const auto escapeStart = ansiFreeString.indexOf(escapeChar);
const auto escapeEnd = ansiFreeString.indexOf(QLatin1Char('m'), escapeStart);
ansiFreeString.remove(escapeStart, escapeEnd - escapeStart + 1);
}
return ansiFreeString;
}
17 changes: 17 additions & 0 deletions src/models/formattingutils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
SPDX-FileCopyrightText: Lieven Hey <[email protected]>
SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once

#include <QString>

namespace Util {
// escape character also known as \033 and \e it signals the start of an ansi escape sequence
constexpr auto escapeChar = QLatin1Char('\u001B');

QString removeAnsi(const QString& stringWithAnsi);
}
Loading

0 comments on commit f765a2f

Please sign in to comment.