diff --git a/src/katvan_compileroutput.cpp b/src/katvan_compileroutput.cpp index 82480c9..0555e25 100644 --- a/src/katvan_compileroutput.cpp +++ b/src/katvan_compileroutput.cpp @@ -18,50 +18,109 @@ #include "katvan_compileroutput.h" #include "katvan_diagnosticsmodel.h" -#include #include +#include +#include namespace katvan { +class DiagnosticsItemDelegate : public QStyledItemDelegate { +public: + DiagnosticsItemDelegate(QObject *parent = nullptr) + : QStyledItemDelegate(parent) {} + +protected: + void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override + { + QStyledItemDelegate::initStyleOption(option, index); + + // Not interested in individual focus rects on columns + option->state &= ~QStyle::State_HasFocus; + } +}; + CompilerOutput::CompilerOutput(QWidget* parent) : QTreeView(parent) { setHeaderHidden(true); setRootIsDecorated(false); setAlternatingRowColors(true); - header()->setStretchLastSection(true); - - setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + setItemDelegate(new DiagnosticsItemDelegate(this)); setLayoutDirection(Qt::LeftToRight); + setMouseTracking(true); + header()->setStretchLastSection(false); - connect(this, &QTreeView::activated, this, &CompilerOutput::itemActivated); + connect(this, &QTreeView::clicked, this, &CompilerOutput::indexClicked); +} + +void CompilerOutput::setModel(QAbstractItemModel* model) +{ + Q_ASSERT(qobject_cast(model) != nullptr); + + QTreeView::setModel(model); + + header()->setSectionResizeMode(DiagnosticsModel::COLUMN_SEVERITY, QHeaderView::Fixed); + header()->setSectionResizeMode(DiagnosticsModel::COLUMN_MESSAGE, QHeaderView::Stretch); + header()->setSectionResizeMode(DiagnosticsModel::COLUMN_SOURCE_LOCATION, QHeaderView::Fixed); } void CompilerOutput::adjustColumnWidths() +{ + adjustColumnWidths(viewport()->size()); +} + +void CompilerOutput::adjustColumnWidths(QSize viewportSize) { int severityWidth = 22; - int locationWidth = fontMetrics().horizontalAdvance("0000:000"); - int availableWidthForFileName = viewport()->width() - severityWidth - locationWidth; - int fileNameSizeHint = sizeHintForColumn(DiagnosticsModel::COLUMN_FILE); + int messageSizeHint = sizeHintForColumn(DiagnosticsModel::COLUMN_MESSAGE); + int locationSizeHint = sizeHintForColumn(DiagnosticsModel::COLUMN_SOURCE_LOCATION); + int availableWidthForLocation = viewportSize.width() - severityWidth - messageSizeHint; // If there is enough space for fully accomodate the file name, give it that - // much width. Otherwise, give it 25% of the total width. - int fileNameWidth = (fileNameSizeHint <= availableWidthForFileName) - ? fileNameSizeHint - : qRound(viewport()->width() / 4.0); + // much width. Otherwise, give it up to 25% of the total width. + int locationWidth = (locationSizeHint <= availableWidthForLocation) + ? locationSizeHint + : qMin(qRound(viewportSize.width() / 4.0), locationSizeHint); setColumnWidth(DiagnosticsModel::COLUMN_SEVERITY, severityWidth); - setColumnWidth(DiagnosticsModel::COLUMN_FILE, fileNameWidth); setColumnWidth(DiagnosticsModel::COLUMN_SOURCE_LOCATION, locationWidth); } void CompilerOutput::resizeEvent(QResizeEvent* event) { + if (!model()) { + return; + } + QTreeView::resizeEvent(event); - adjustColumnWidths(); + adjustColumnWidths(event->size()); +} + +void CompilerOutput::mouseMoveEvent(QMouseEvent* event) +{ + QTreeView::mouseMoveEvent(event); + + QModelIndex index = indexAt(event->pos()); + if (index.isValid()) { + QVariant cursor = index.data(DiagnosticsModel::ROLE_MOUSE_CURSOR); + if (cursor.isValid()) { + setCursor(qvariant_cast(cursor)); + return; + } + } + setCursor(Qt::ArrowCursor); +} + +void CompilerOutput::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Escape) { + clearSelection(); + return; + } + QTreeView::keyPressEvent(event); } -void CompilerOutput::itemActivated(const QModelIndex& index) +void CompilerOutput::indexClicked(const QModelIndex& index) { if (!index.isValid()) { return; diff --git a/src/katvan_compileroutput.h b/src/katvan_compileroutput.h index 5a3f35b..0bca4f7 100644 --- a/src/katvan_compileroutput.h +++ b/src/katvan_compileroutput.h @@ -29,17 +29,22 @@ class CompilerOutput : public QTreeView public: CompilerOutput(QWidget* parent = nullptr); + void setModel(QAbstractItemModel* model) override; + public slots: void adjustColumnWidths(); + void adjustColumnWidths(QSize viewportSize); signals: void goToPosition(int blockNum, int charOffset); protected: void resizeEvent(QResizeEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; private slots: - void itemActivated(const QModelIndex& index); + void indexClicked(const QModelIndex& index); }; } diff --git a/src/katvan_diagnosticsmodel.cpp b/src/katvan_diagnosticsmodel.cpp index a3e4afc..8e2c8d8 100644 --- a/src/katvan_diagnosticsmodel.cpp +++ b/src/katvan_diagnosticsmodel.cpp @@ -17,7 +17,9 @@ */ #include "katvan_diagnosticsmodel.h" +#include #include +#include #include namespace katvan { @@ -27,6 +29,7 @@ static constexpr QLatin1StringView MAIN_SOURCE = QLatin1StringView("MAIN"); DiagnosticsModel::DiagnosticsModel(QObject* parent) : QAbstractTableModel(parent) { + d_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); } void DiagnosticsModel::setInputFileName(const QString& fileName) @@ -103,6 +106,19 @@ QVariant DiagnosticsModel::data(const QModelIndex& index, int role) const const auto& diagnostic = d_diagnostics[index.row()]; + if (role == Qt::FontRole) { + QFont font = d_font; + if (index.column() == COLUMN_SOURCE_LOCATION && diagnostic.file() == MAIN_SOURCE) { + font.setUnderline(true); + } + return font; + } + if (role == ROLE_MOUSE_CURSOR) { + if (index.column() == COLUMN_SOURCE_LOCATION && diagnostic.file() == MAIN_SOURCE) { + return QCursor(Qt::PointingHandCursor); + } + } + if (index.column() == COLUMN_SEVERITY) { auto kind = diagnostic.kind(); if (role == Qt::DecorationRole) { @@ -130,19 +146,6 @@ QVariant DiagnosticsModel::data(const QModelIndex& index, int role) const } } } - else if (index.column() == COLUMN_FILE && (role == Qt::DisplayRole || role == Qt::ToolTipRole)) { - QString fileName = diagnostic.file(); - if (fileName == MAIN_SOURCE) { - return d_shortFileName; - } - return fileName; - } - else if (index.column() == COLUMN_SOURCE_LOCATION && role == Qt::DisplayRole) { - if (diagnostic.location()) { - auto location = diagnostic.location().value(); - return QString("%1:%2").arg(location.line).arg(location.column); - } - } else if (index.column() == COLUMN_MESSAGE && (role == Qt::DisplayRole || role == Qt::ToolTipRole)) { QString message = diagnostic.message(); for (const QString& hint : diagnostic.hints()) { @@ -150,6 +153,18 @@ QVariant DiagnosticsModel::data(const QModelIndex& index, int role) const } return message; } + else if (index.column() == COLUMN_SOURCE_LOCATION && (role == Qt::DisplayRole || role == Qt::ToolTipRole)) { + QString result = diagnostic.file(); + if (result == MAIN_SOURCE) { + result = d_shortFileName; + } + + if (diagnostic.location()) { + auto location = diagnostic.location().value(); + result += QStringLiteral(" (%1:%2)").arg(location.line).arg(location.column); + } + return result; + } return QVariant(); } diff --git a/src/katvan_diagnosticsmodel.h b/src/katvan_diagnosticsmodel.h index 0986765..3c388b8 100644 --- a/src/katvan_diagnosticsmodel.h +++ b/src/katvan_diagnosticsmodel.h @@ -22,6 +22,7 @@ #include "typstdriver_logger.h" #include +#include #include #include @@ -36,12 +37,15 @@ class DiagnosticsModel : public QAbstractTableModel public: enum { COLUMN_SEVERITY = 0, - COLUMN_FILE, - COLUMN_SOURCE_LOCATION, COLUMN_MESSAGE, + COLUMN_SOURCE_LOCATION, COLUMN_COUNT }; + enum { + ROLE_MOUSE_CURSOR = Qt::UserRole + 1, + }; + DiagnosticsModel(QObject* parent = nullptr); void setInputFileName(const QString& fileName); @@ -55,9 +59,10 @@ class DiagnosticsModel : public QAbstractTableModel public slots: void clear(); - void addDiagnostic(const typstdriver::Diagnostic& diagnostic); + void addDiagnostic(const katvan::typstdriver::Diagnostic& diagnostic); private: + QFont d_font; QString d_shortFileName; QList d_diagnostics; }; diff --git a/src/katvan_mainwindow.cpp b/src/katvan_mainwindow.cpp index f7098af..e65c4fe 100644 --- a/src/katvan_mainwindow.cpp +++ b/src/katvan_mainwindow.cpp @@ -74,7 +74,6 @@ MainWindow::MainWindow() connect(d_driver, &TypstDriverWrapper::previewReady, this, &MainWindow::previewReady); connect(d_driver, &TypstDriverWrapper::compilationStatusChanged, this, &MainWindow::compilationStatusChanged); - connect(d_driver, &TypstDriverWrapper::compilationStatusChanged, d_compilerOutput, &CompilerOutput::adjustColumnWidths); connect(d_driver, &TypstDriverWrapper::jumpToPreview, d_previewer, &Previewer::jumpToPreview); connect(d_driver, &TypstDriverWrapper::jumpToEditor, d_editor, &Editor::goToBlock); @@ -860,6 +859,7 @@ void MainWindow::compilationStatusChanged() if (status != TypstDriverWrapper::Status::PROCESSING) { d_compilingMovie->stop(); } + d_compilerOutput->adjustColumnWidths(); if (status == TypstDriverWrapper::Status::PROCESSING) { d_compilationStatusButton->setText(tr("Compiling...")); diff --git a/typstdriver/rust/src/engine.rs b/typstdriver/rust/src/engine.rs index 565afe3..9f2c3df 100644 --- a/typstdriver/rust/src/engine.rs +++ b/typstdriver/rust/src/engine.rs @@ -114,8 +114,7 @@ impl<'a> EngineImpl<'a> { // it) that is located in the main source. let span = std::iter::once(diag.span) .chain(diag.trace.iter().map(|t| t.span)) - .filter(|span| span.id() == Some(*MAIN_ID)) - .next() + .find(|span| span.id() == Some(*MAIN_ID)) .unwrap_or(diag.span); let location = self.span_to_location(span); diff --git a/typstdriver/typstdriver_logger.h b/typstdriver/typstdriver_logger.h index 57b7acf..032aea0 100644 --- a/typstdriver/typstdriver_logger.h +++ b/typstdriver/typstdriver_logger.h @@ -77,7 +77,7 @@ class TYPSTDRIVER_EXPORT Logger : public QObject void logDiagnostic(Diagnostic diagnostic); signals: - void diagnosticLogged(Diagnostic diagnostic); + void diagnosticLogged(katvan::typstdriver::Diagnostic diagnostic); }; }