summaryrefslogtreecommitdiff
path: root/Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp
diff options
context:
space:
mode:
authorTilka <tilkax@gmail.com>2023-12-16 21:15:31 +0000
committerGitHub <noreply@github.com>2023-12-16 21:15:31 +0000
commit773ffd04b8ce3695e2676db66ac2a2bf2ba73494 (patch)
tree64d79a5c4e238cee05128e0c61b7e2e4e9ca71bb /Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp
parent190c4e8cda7a78dd7b1ae184425b49d363734c7e (diff)
parentc8c9928eb11cea81808127bae9894fc6cfbac770 (diff)
Merge pull request #11497 from vyuuui/debugger_assembler_ui
Built-in assembler for debugger interface
Diffstat (limited to 'Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp')
-rw-r--r--Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp369
1 files changed, 369 insertions, 0 deletions
diff --git a/Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp b/Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp
new file mode 100644
index 0000000000..113c59fa07
--- /dev/null
+++ b/Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp
@@ -0,0 +1,369 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "DolphinQt/Debugger/AssemblyEditor.h"
+
+#include <QFile>
+#include <QPainter>
+#include <QTextBlock>
+#include <QToolTip>
+
+#include <filesystem>
+
+#include "Common/Assembler/GekkoParser.h"
+#include "Common/StringUtil.h"
+#include "DolphinQt/Debugger/GekkoSyntaxHighlight.h"
+
+QSize AsmEditor::LineNumberArea::sizeHint() const
+{
+ return QSize(asm_editor->LineNumberAreaWidth(), 0);
+}
+
+void AsmEditor::LineNumberArea::paintEvent(QPaintEvent* event)
+{
+ asm_editor->LineNumberAreaPaintEvent(event);
+}
+
+AsmEditor::AsmEditor(const QString& path, int editor_num, bool dark_scheme, QWidget* parent)
+ : QPlainTextEdit(parent), m_path(path), m_base_address(QStringLiteral("0")),
+ m_editor_num(editor_num), m_dirty(false), m_dark_scheme(dark_scheme)
+{
+ if (!m_path.isEmpty())
+ {
+ m_filename =
+ QString::fromStdString(std::filesystem::path(m_path.toStdString()).filename().string());
+ }
+
+ m_line_number_area = new LineNumberArea(this);
+ m_highlighter = new GekkoSyntaxHighlight(document(), currentCharFormat(), dark_scheme);
+ m_last_block = textCursor().block();
+
+ QFont mono_font(QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
+ mono_font.setPointSize(12);
+ setFont(mono_font);
+ m_line_number_area->setFont(mono_font);
+
+ UpdateLineNumberAreaWidth(0);
+ HighlightCurrentLine();
+ setMouseTracking(true);
+
+ connect(this, &AsmEditor::blockCountChanged, this, &AsmEditor::UpdateLineNumberAreaWidth);
+ connect(this, &AsmEditor::updateRequest, this, &AsmEditor::UpdateLineNumberArea);
+ connect(this, &AsmEditor::cursorPositionChanged, this, &AsmEditor::HighlightCurrentLine);
+ connect(this, &AsmEditor::textChanged, this, [this] {
+ m_dirty = true;
+ emit DirtyChanged();
+ });
+}
+
+int AsmEditor::LineNumberAreaWidth()
+{
+ int num_digits = 1;
+ for (int max = qMax(1, blockCount()); max >= 10; max /= 10, ++num_digits)
+ {
+ }
+
+ return 3 + CharWidth() * qMax(2, num_digits);
+}
+
+void AsmEditor::SetBaseAddress(const QString& ba)
+{
+ if (ba != m_base_address)
+ {
+ m_base_address = ba;
+ m_dirty = true;
+ emit DirtyChanged();
+ }
+}
+
+bool AsmEditor::LoadFromPath()
+{
+ QFile file(m_path);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ return false;
+ }
+
+ const std::string base_addr_line = file.readLine().toStdString();
+ std::string base_address = "";
+ for (size_t i = 0; i < base_addr_line.length(); i++)
+ {
+ if (std::isspace(base_addr_line[i]))
+ {
+ continue;
+ }
+ else if (base_addr_line[i] == '#')
+ {
+ base_address = base_addr_line.substr(i + 1);
+ break;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (base_address.empty())
+ {
+ file.seek(0);
+ }
+ else
+ {
+ StringPopBackIf(&base_address, '\n');
+ if (base_address.empty())
+ {
+ base_address = "0";
+ }
+ m_base_address = QString::fromStdString(base_address);
+ }
+
+ const bool old_block = blockSignals(true);
+ setPlainText(QString::fromStdString(file.readAll().toStdString()));
+ blockSignals(old_block);
+ return true;
+}
+
+bool AsmEditor::PathsMatch(const QString& path) const
+{
+ if (m_path.isEmpty() || path.isEmpty())
+ {
+ return false;
+ }
+ return std::filesystem::path(m_path.toStdString()) == std::filesystem::path(path.toStdString());
+}
+
+void AsmEditor::Zoom(int amount)
+{
+ if (amount > 0)
+ {
+ zoomIn(amount);
+ }
+ else
+ {
+ zoomOut(-amount);
+ }
+ m_line_number_area->setFont(font());
+}
+
+bool AsmEditor::SaveFile(const QString& save_path)
+{
+ QFile file(save_path);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
+ {
+ return false;
+ }
+
+ if (m_path != save_path)
+ {
+ m_path = save_path;
+ m_filename =
+ QString::fromStdString(std::filesystem::path(m_path.toStdString()).filename().string());
+ emit PathChanged();
+ }
+
+ if (file.write(QStringLiteral("#%1\n").arg(m_base_address).toUtf8()) == -1)
+ {
+ return false;
+ }
+
+ if (file.write(toPlainText().toUtf8()) == -1)
+ {
+ return false;
+ }
+
+ m_dirty = false;
+ emit DirtyChanged();
+ return true;
+}
+
+void AsmEditor::UpdateLineNumberAreaWidth(int)
+{
+ setViewportMargins(LineNumberAreaWidth(), 0, 0, 0);
+}
+
+void AsmEditor::UpdateLineNumberArea(const QRect& rect, int dy)
+{
+ if (dy != 0)
+ {
+ m_line_number_area->scroll(0, dy);
+ }
+ else
+ {
+ m_line_number_area->update(0, rect.y(), m_line_number_area->width(), rect.height());
+ }
+
+ if (rect.contains(viewport()->rect()))
+ {
+ UpdateLineNumberAreaWidth(0);
+ }
+}
+
+int AsmEditor::CharWidth() const
+{
+ return fontMetrics().horizontalAdvance(QLatin1Char(' '));
+}
+
+void AsmEditor::resizeEvent(QResizeEvent* e)
+{
+ QPlainTextEdit::resizeEvent(e);
+
+ const QRect cr = contentsRect();
+ m_line_number_area->setGeometry(QRect(cr.left(), cr.top(), LineNumberAreaWidth(), cr.height()));
+}
+
+void AsmEditor::paintEvent(QPaintEvent* event)
+{
+ QPlainTextEdit::paintEvent(event);
+
+ QPainter painter(viewport());
+ QTextCursor tc(document());
+
+ QPen p = QPen(Qt::red);
+ p.setStyle(Qt::PenStyle::SolidLine);
+ p.setWidth(1);
+ painter.setPen(p);
+ const int width = CharWidth();
+
+ for (QTextBlock blk = firstVisibleBlock(); blk.isVisible() && blk.isValid(); blk = blk.next())
+ {
+ if (blk.userData() == nullptr)
+ {
+ continue;
+ }
+
+ BlockInfo* info = static_cast<BlockInfo*>(blk.userData());
+ if (info->error_at_eol)
+ {
+ tc.setPosition(blk.position() + blk.length() - 1);
+ tc.clearSelection();
+ const QRect qr = cursorRect(tc);
+ painter.drawLine(qr.x(), qr.y() + qr.height(), qr.x() + width, qr.y() + qr.height());
+ }
+ }
+}
+
+bool AsmEditor::event(QEvent* e)
+{
+ if (e->type() == QEvent::ToolTip)
+ {
+ QHelpEvent* he = static_cast<QHelpEvent*>(e);
+ QTextCursor hover_cursor = cursorForPosition(he->pos());
+ QTextBlock hover_block = hover_cursor.block();
+
+ BlockInfo* info = static_cast<BlockInfo*>(hover_block.userData());
+ if (info == nullptr || !info->error)
+ {
+ QToolTip::hideText();
+ return true;
+ }
+
+ QRect check_rect;
+ if (info->error_at_eol)
+ {
+ hover_cursor.setPosition(hover_block.position() +
+ static_cast<int>(info->error->col + info->error->len));
+ const QRect cursor_left = cursorRect(hover_cursor);
+ const int area_width = CharWidth();
+ check_rect = QRect(cursor_left.x() + LineNumberAreaWidth(), cursor_left.y(),
+ cursor_left.x() + area_width, cursor_left.height());
+ }
+ else
+ {
+ hover_cursor.setPosition(hover_block.position() + static_cast<int>(info->error->col));
+ const QRect cursor_left = cursorRect(hover_cursor);
+ hover_cursor.setPosition(hover_block.position() +
+ static_cast<int>(info->error->col + info->error->len));
+ const QRect cursor_right = cursorRect(hover_cursor);
+ check_rect = QRect(cursor_left.x() + LineNumberAreaWidth(), cursor_left.y(),
+ cursor_right.x() - cursor_left.x(), cursor_left.height());
+ }
+ if (check_rect.contains(he->pos()))
+ {
+ QToolTip::showText(he->globalPos(), QString::fromStdString(info->error->message));
+ }
+ else
+ {
+ QToolTip::hideText();
+ }
+ return true;
+ }
+ return QPlainTextEdit::event(e);
+}
+
+void AsmEditor::keyPressEvent(QKeyEvent* event)
+{
+ // HACK: Change shift+enter to enter to keep lines as blocks
+ if (event->modifiers() & Qt::ShiftModifier &&
+ (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return))
+ {
+ event->setModifiers(event->modifiers() & ~Qt::ShiftModifier);
+ }
+ QPlainTextEdit::keyPressEvent(event);
+}
+
+void AsmEditor::wheelEvent(QWheelEvent* event)
+{
+ QPlainTextEdit::wheelEvent(event);
+
+ if (event->modifiers() & Qt::ControlModifier)
+ {
+ auto delta = static_cast<int>(std::round((event->angleDelta().y() / 120.0)));
+ if (delta != 0)
+ {
+ emit ZoomRequested(delta);
+ }
+ }
+}
+
+void AsmEditor::HighlightCurrentLine()
+{
+ const bool old_state = blockSignals(true);
+
+ if (m_last_block.blockNumber() != textCursor().blockNumber())
+ {
+ m_highlighter->SetMode(2);
+ m_highlighter->rehighlightBlock(m_last_block);
+
+ m_last_block = textCursor().block();
+ }
+
+ m_highlighter->SetCursorLoc(textCursor().positionInBlock());
+ m_highlighter->SetMode(1);
+ m_highlighter->rehighlightBlock(textCursor().block());
+ m_highlighter->SetMode(0);
+
+ blockSignals(old_state);
+}
+
+void AsmEditor::LineNumberAreaPaintEvent(QPaintEvent* event)
+{
+ QPainter painter(m_line_number_area);
+ if (m_dark_scheme)
+ {
+ painter.fillRect(event->rect(), QColor::fromRgb(76, 76, 76));
+ }
+ else
+ {
+ painter.fillRect(event->rect(), QColor::fromRgb(180, 180, 180));
+ }
+
+ QTextBlock block = firstVisibleBlock();
+ int block_num = block.blockNumber();
+ int top = qRound(blockBoundingGeometry(block).translated(contentOffset()).top());
+ int bottom = top + qRound(blockBoundingRect(block).height());
+
+ while (block.isValid() && top <= event->rect().bottom())
+ {
+ if (block.isVisible() && bottom >= event->rect().top())
+ {
+ const QString num = QString::number(block_num + 1);
+ painter.drawText(0, top, m_line_number_area->width(), fontMetrics().height(), Qt::AlignRight,
+ num);
+ }
+
+ block = block.next();
+ top = bottom;
+ bottom = top + qRound(blockBoundingRect(block).height());
+ ++block_num;
+ }
+}