File indexing completed on 2024-04-28 05:48:34

0001 //
0002 // Description: Kate Plugin for GDB integration
0003 //
0004 //
0005 // SPDX-FileCopyrightText: 2010 Ian Wakeling <ian.wakeling@ntlworld.com>
0006 // SPDX-FileCopyrightText: 2010-2014 Kåre Särs <kare.sars@iki.fi>
0007 //
0008 //  SPDX-License-Identifier: LGPL-2.0-only
0009 
0010 #include "plugin_kategdb.h"
0011 
0012 #include <QBoxLayout>
0013 #include <QFile>
0014 #include <QFontDatabase>
0015 #include <QKeyEvent>
0016 #include <QScrollBar>
0017 #include <QSplitter>
0018 #include <QTabWidget>
0019 #include <QTextEdit>
0020 #include <QTreeWidget>
0021 
0022 #include <KActionCollection>
0023 #include <KConfigGroup>
0024 #include <KXMLGUIFactory>
0025 #include <QAction>
0026 #include <QMenu>
0027 
0028 #include <KColorScheme>
0029 #include <KHistoryComboBox>
0030 #include <KLocalizedString>
0031 #include <KPluginFactory>
0032 
0033 #include "debugconfigpage.h"
0034 #include <QDir>
0035 #include <ktexteditor/document.h>
0036 #include <ktexteditor/editor.h>
0037 #include <ktexteditor/view.h>
0038 
0039 K_PLUGIN_FACTORY_WITH_JSON(KatePluginGDBFactory, "kategdbplugin.json", registerPlugin<KatePluginGDB>();)
0040 
0041 static const QString CONFIG_DEBUGPLUGIN{QStringLiteral("debugplugin")};
0042 static const QString CONFIG_DAP_CONFIG{QStringLiteral("DAPConfiguration")};
0043 
0044 KatePluginGDB::KatePluginGDB(QObject *parent, const VariantList &)
0045     : KTextEditor::Plugin(parent)
0046     , m_settingsPath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + QStringLiteral("/debugger"))
0047     , m_defaultConfigPath(QUrl::fromLocalFile(m_settingsPath + QStringLiteral("/dap.json")))
0048 {
0049     // FIXME KF5 KGlobal::locale()->insertCatalog("kategdbplugin");
0050     QDir().mkpath(m_settingsPath);
0051     readConfig();
0052 }
0053 
0054 int KatePluginGDB::configPages() const
0055 {
0056     return 1;
0057 }
0058 KTextEditor::ConfigPage *KatePluginGDB::configPage(int number, QWidget *parent)
0059 {
0060     if (number != 0) {
0061         return nullptr;
0062     }
0063 
0064     return new DebugConfigPage(parent, this);
0065 }
0066 
0067 KatePluginGDB::~KatePluginGDB()
0068 {
0069 }
0070 
0071 void KatePluginGDB::readConfig()
0072 {
0073     KConfigGroup config(KSharedConfig::openConfig(), CONFIG_DEBUGPLUGIN);
0074     m_configPath = config.readEntry(CONFIG_DAP_CONFIG, QUrl());
0075 
0076     Q_EMIT update();
0077 }
0078 
0079 void KatePluginGDB::writeConfig() const
0080 {
0081     KConfigGroup config(KSharedConfig::openConfig(), CONFIG_DEBUGPLUGIN);
0082     config.writeEntry(CONFIG_DAP_CONFIG, m_configPath);
0083 
0084     Q_EMIT update();
0085 }
0086 
0087 QObject *KatePluginGDB::createView(KTextEditor::MainWindow *mainWindow)
0088 {
0089     return new KatePluginGDBView(this, mainWindow);
0090 }
0091 
0092 KatePluginGDBView::KatePluginGDBView(KatePluginGDB *plugin, KTextEditor::MainWindow *mainWin)
0093     : QObject(mainWin)
0094     , m_mainWin(mainWin)
0095 {
0096     m_lastExecUrl = QUrl();
0097     m_lastExecLine = -1;
0098     m_lastExecFrame = 0;
0099     m_kateApplication = KTextEditor::Editor::instance()->application();
0100     m_focusOnInput = true;
0101     m_activeThread = -1;
0102 
0103     KXMLGUIClient::setComponentName(QStringLiteral("kategdb"), i18n("Kate Debug"));
0104     setXMLFile(QStringLiteral("ui.rc"));
0105 
0106     m_toolView.reset(
0107         m_mainWin->createToolView(plugin, i18n("Debug View"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("debug-run")), i18n("Debug")));
0108 
0109     m_localsStackToolView.reset(m_mainWin->createToolView(plugin,
0110                                                           i18n("Locals and Stack"),
0111                                                           KTextEditor::MainWindow::Right,
0112                                                           QIcon::fromTheme(QStringLiteral("debug-run")),
0113                                                           i18n("Locals and Stack")));
0114 
0115     m_tabWidget = new QTabWidget(m_toolView.get());
0116 
0117     // buttons widget, buttons are initialized at action connections
0118     m_buttonWidget = new QWidget(m_tabWidget);
0119     auto buttonsLayout = new QHBoxLayout(m_buttonWidget);
0120     buttonsLayout->setContentsMargins(0, 1, 0, 3);
0121     m_tabWidget->setCornerWidget(m_buttonWidget);
0122 
0123     // Output
0124     m_outputArea = new QTextEdit();
0125     m_outputArea->setAcceptRichText(false);
0126     m_outputArea->setReadOnly(true);
0127     m_outputArea->setUndoRedoEnabled(false);
0128     // fixed wide font, like konsole
0129     m_outputArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0130     // Use complementary color scheme for dark "terminal"
0131     KColorScheme schemeView(QPalette::Active, KColorScheme::Complementary);
0132     m_outputArea->setTextBackgroundColor(schemeView.background().color());
0133     m_outputArea->setTextColor(schemeView.foreground().color());
0134     QPalette p = m_outputArea->palette();
0135     p.setColor(QPalette::Base, schemeView.background().color());
0136     m_outputArea->setPalette(p);
0137 
0138     // input
0139     m_inputArea = new KHistoryComboBox(true);
0140     connect(m_inputArea, static_cast<void (KHistoryComboBox::*)(const QString &)>(&KHistoryComboBox::returnPressed), this, &KatePluginGDBView::slotSendCommand);
0141     QHBoxLayout *inputLayout = new QHBoxLayout();
0142     inputLayout->addWidget(m_inputArea, 10);
0143     inputLayout->setContentsMargins(0, 0, 0, 0);
0144     m_outputArea->setFocusProxy(m_inputArea); // take the focus from the outputArea
0145 
0146     m_gdbPage = new QWidget();
0147     QVBoxLayout *layout = new QVBoxLayout(m_gdbPage);
0148     layout->addWidget(m_outputArea);
0149     layout->addLayout(inputLayout);
0150     layout->setStretch(0, 10);
0151     layout->setContentsMargins(0, 0, 0, 0);
0152     layout->setSpacing(0);
0153 
0154     // stack page
0155     QWidget *stackContainer = new QWidget();
0156     QVBoxLayout *stackLayout = new QVBoxLayout(stackContainer);
0157     m_threadCombo = new QComboBox();
0158     m_stackTree = new QTreeWidget();
0159     stackLayout->addWidget(m_threadCombo);
0160     stackLayout->addWidget(m_stackTree);
0161     stackLayout->setStretch(0, 10);
0162     stackLayout->setContentsMargins(0, 0, 0, 0);
0163     stackLayout->setSpacing(0);
0164     QStringList headers;
0165     headers << QStringLiteral("  ") << i18nc("Column label (frame number)", "Nr") << i18nc("Column label", "Frame");
0166     m_stackTree->setHeaderLabels(headers);
0167     m_stackTree->setRootIsDecorated(false);
0168     m_stackTree->resizeColumnToContents(0);
0169     m_stackTree->resizeColumnToContents(1);
0170     m_stackTree->setAutoScroll(false);
0171     connect(m_stackTree, &QTreeWidget::itemActivated, this, &KatePluginGDBView::stackFrameSelected);
0172 
0173     connect(m_threadCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KatePluginGDBView::threadSelected);
0174 
0175     QWidget *variableContainer = new QWidget();
0176     QVBoxLayout *variableLayout = new QVBoxLayout(variableContainer);
0177     m_scopeCombo = new QComboBox();
0178     connect(m_scopeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KatePluginGDBView::scopeSelected);
0179     m_localsView = new LocalsView();
0180     variableLayout->addWidget(m_scopeCombo);
0181     variableLayout->addWidget(m_localsView);
0182     variableLayout->setStretch(0, 10);
0183     variableLayout->setContentsMargins(0, 0, 0, 0);
0184     variableLayout->setSpacing(0);
0185 
0186     QSplitter *locStackSplitter = new QSplitter(m_localsStackToolView.get());
0187     locStackSplitter->addWidget(variableContainer);
0188     locStackSplitter->addWidget(stackContainer);
0189     locStackSplitter->setOrientation(Qt::Vertical);
0190 
0191     // config page
0192     m_configView = new ConfigView(nullptr, mainWin, plugin);
0193 
0194     m_ioView = std::make_unique<IOView>();
0195     connect(m_configView, &ConfigView::showIO, this, &KatePluginGDBView::showIO);
0196 
0197     m_tabWidget->addTab(m_gdbPage, i18nc("Tab label", "Debug Output"));
0198     m_tabWidget->addTab(m_configView, i18nc("Tab label", "Settings"));
0199 
0200     m_debugView = new Backend(this);
0201     connect(m_debugView, &BackendInterface::readyForInput, this, &KatePluginGDBView::enableDebugActions);
0202     connect(m_debugView, &BackendInterface::debuggerCapabilitiesChanged, [this] {
0203         enableDebugActions(true);
0204     });
0205 
0206     connect(m_debugView, &BackendInterface::outputText, this, &KatePluginGDBView::addOutputText);
0207 
0208     connect(m_debugView, &BackendInterface::outputError, this, &KatePluginGDBView::addErrorText);
0209 
0210     connect(m_debugView, &BackendInterface::debugLocationChanged, this, &KatePluginGDBView::slotGoTo);
0211 
0212     connect(m_debugView, &BackendInterface::breakPointSet, this, &KatePluginGDBView::slotBreakpointSet);
0213 
0214     connect(m_debugView, &BackendInterface::breakPointCleared, this, &KatePluginGDBView::slotBreakpointCleared);
0215 
0216     connect(m_debugView, &BackendInterface::clearBreakpointMarks, this, &KatePluginGDBView::clearMarks);
0217 
0218     connect(m_debugView, &BackendInterface::programEnded, this, &KatePluginGDBView::programEnded);
0219 
0220     connect(m_debugView, &BackendInterface::gdbEnded, this, &KatePluginGDBView::programEnded);
0221 
0222     connect(m_debugView, &BackendInterface::gdbEnded, this, &KatePluginGDBView::gdbEnded);
0223 
0224     connect(m_debugView, &BackendInterface::stackFrameInfo, this, &KatePluginGDBView::insertStackFrame);
0225 
0226     connect(m_debugView, &BackendInterface::stackFrameChanged, this, &KatePluginGDBView::stackFrameChanged);
0227 
0228     connect(m_debugView, &BackendInterface::scopesInfo, this, &KatePluginGDBView::insertScopes);
0229 
0230     connect(m_debugView, &BackendInterface::variableScopeOpened, m_localsView, &LocalsView::openVariableScope);
0231     connect(m_debugView, &BackendInterface::variableScopeClosed, m_localsView, &LocalsView::closeVariableScope);
0232     connect(m_debugView, &BackendInterface::variableInfo, m_localsView, &LocalsView::addVariableLevel);
0233 
0234     connect(m_debugView, &BackendInterface::threadInfo, this, &KatePluginGDBView::insertThread);
0235 
0236     connect(m_debugView, &BackendInterface::debuggeeOutput, this, &KatePluginGDBView::addOutput);
0237 
0238     connect(m_debugView, &BackendInterface::sourceFileNotFound, this, [this](const QString &fileName) {
0239         displayMessage(xi18nc("@info",
0240                               "<title>Could not open file:</title><nl/>%1<br/>Try adding a search path to Advanced Settings -> Source file search paths",
0241                               fileName),
0242                        KTextEditor::Message::Error);
0243     });
0244 
0245     connect(m_debugView, &BackendInterface::backendError, this, [this](const QString &message, KTextEditor::Message::MessageType level) {
0246         displayMessage(message, level);
0247     });
0248 
0249     connect(m_localsView, &LocalsView::localsVisible, m_debugView, &BackendInterface::slotQueryLocals);
0250 
0251     connect(m_configView, &ConfigView::configChanged, this, [this]() {
0252         if (!m_configView->debuggerIsGDB())
0253             return;
0254 
0255         GDBTargetConf config = m_configView->currentGDBTarget();
0256         if (m_debugView->targetName() == config.targetName) {
0257             m_debugView->setFileSearchPaths(config.srcPaths);
0258         }
0259     });
0260 
0261     // Actions
0262     m_configView->registerActions(actionCollection());
0263 
0264     QAction *a = actionCollection()->addAction(QStringLiteral("debug"));
0265     a->setText(i18n("Start Debugging"));
0266     a->setIcon(QIcon::fromTheme(QStringLiteral("debug-run")));
0267     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::CTRL | Qt::Key_F7)));
0268     connect(a, &QAction::triggered, this, &KatePluginGDBView::slotDebug);
0269     // This button is handled differently due to it having multiple actions
0270     m_continueButton = new QToolButton();
0271     m_continueButton->setAutoRaise(true);
0272     buttonsLayout->addWidget(m_continueButton);
0273 
0274     a = actionCollection()->addAction(QStringLiteral("kill"));
0275     a->setText(i18n("Kill / Stop Debugging"));
0276     a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop")));
0277     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::ALT | Qt::Key_F7)));
0278     connect(a, &QAction::triggered, m_debugView, &BackendInterface::slotKill);
0279     buttonsLayout->addWidget(createDebugButton(a));
0280 
0281     a = actionCollection()->addAction(QStringLiteral("continue"));
0282     a->setText(i18n("Continue"));
0283     a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
0284     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::CTRL | Qt::Key_F7)));
0285     connect(a, &QAction::triggered, m_debugView, &BackendInterface::slotContinue);
0286 
0287     a = actionCollection()->addAction(QStringLiteral("toggle_breakpoint"));
0288     a->setText(i18n("Toggle Breakpoint / Break"));
0289     a->setIcon(QIcon::fromTheme(QStringLiteral("media-record")));
0290     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::SHIFT | Qt::Key_F11)));
0291     connect(a, &QAction::triggered, this, &KatePluginGDBView::slotToggleBreakpoint);
0292 
0293     a = actionCollection()->addAction(QStringLiteral("step_in"));
0294     a->setText(i18n("Step In"));
0295     a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-into")));
0296     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::Key_F10)));
0297     connect(a, &QAction::triggered, m_debugView, &BackendInterface::slotStepInto);
0298     buttonsLayout->addWidget(createDebugButton(a));
0299 
0300     a = actionCollection()->addAction(QStringLiteral("step_over"));
0301     a->setText(i18n("Step Over"));
0302     a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-over")));
0303     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::Key_F9)));
0304     connect(a, &QAction::triggered, m_debugView, &BackendInterface::slotStepOver);
0305     buttonsLayout->addWidget(createDebugButton(a));
0306 
0307     a = actionCollection()->addAction(QStringLiteral("step_out"));
0308     a->setText(i18n("Step Out"));
0309     a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-out")));
0310     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::SHIFT | Qt::Key_F10)));
0311     connect(a, &QAction::triggered, m_debugView, &BackendInterface::slotStepOut);
0312     buttonsLayout->addWidget(createDebugButton(a));
0313 
0314     a = actionCollection()->addAction(QStringLiteral("run_to_cursor"));
0315     a->setText(i18n("Run To Cursor"));
0316     a->setIcon(QIcon::fromTheme(QStringLiteral("debug-run-cursor")));
0317     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::SHIFT | Qt::Key_F9)));
0318     connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRunToCursor);
0319     buttonsLayout->addWidget(createDebugButton(a));
0320 
0321     a = actionCollection()->addAction(QStringLiteral("rerun"));
0322     a->setText(i18n("Restart Debugging"));
0323     a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0324     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::CTRL | Qt::SHIFT | Qt::Key_F7)));
0325     connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRestart);
0326     buttonsLayout->addWidget(createDebugButton(a));
0327     buttonsLayout->addStretch();
0328 
0329     a = actionCollection()->addAction(QStringLiteral("move_pc"));
0330     a->setText(i18nc("Move Program Counter (next execution)", "Move PC"));
0331     connect(a, &QAction::triggered, this, &KatePluginGDBView::slotMovePC);
0332     actionCollection()->setDefaultShortcut(a, QKeySequence((Qt::ALT | Qt::Key_F9)));
0333 
0334     a = actionCollection()->addAction(QStringLiteral("print_value"));
0335     a->setText(i18n("Print Value"));
0336     a->setIcon(QIcon::fromTheme(QStringLiteral("document-preview")));
0337     connect(a, &QAction::triggered, this, &KatePluginGDBView::slotValue);
0338 
0339     // popup context m_menu
0340     m_menu = new KActionMenu(i18n("Debug"), this);
0341     actionCollection()->addAction(QStringLiteral("popup_gdb"), m_menu);
0342     connect(m_menu->menu(), &QMenu::aboutToShow, this, &KatePluginGDBView::aboutToShowMenu);
0343 
0344     m_breakpoint = m_menu->menu()->addAction(i18n("popup_breakpoint"), this, &KatePluginGDBView::slotToggleBreakpoint);
0345 
0346     QAction *popupAction = m_menu->menu()->addAction(i18n("popup_run_to_cursor"), this, &KatePluginGDBView::slotRunToCursor);
0347     popupAction->setText(i18n("Run To Cursor"));
0348     popupAction = m_menu->menu()->addAction(QStringLiteral("move_pc"), this, &KatePluginGDBView::slotMovePC);
0349     popupAction->setText(i18nc("Move Program Counter (next execution)", "Move PC"));
0350 
0351     enableDebugActions(false);
0352 
0353     connect(m_mainWin, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KatePluginGDBView::handleEsc);
0354 
0355     const auto documents = KTextEditor::Editor::instance()->application()->documents();
0356     for (auto doc : documents) {
0357         enableBreakpointMarks(doc);
0358     }
0359 
0360     connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentCreated, this, &KatePluginGDBView::enableBreakpointMarks);
0361 
0362     m_toolView->installEventFilter(this);
0363 
0364     m_mainWin->guiFactory()->addClient(this);
0365 }
0366 
0367 KatePluginGDBView::~KatePluginGDBView()
0368 {
0369     m_mainWin->guiFactory()->removeClient(this);
0370 }
0371 
0372 void KatePluginGDBView::readSessionConfig(const KConfigGroup &config)
0373 {
0374     m_configView->readConfig(config);
0375 }
0376 
0377 void KatePluginGDBView::writeSessionConfig(KConfigGroup &config)
0378 {
0379     m_configView->writeConfig(config);
0380 }
0381 
0382 void KatePluginGDBView::slotDebug()
0383 {
0384     disconnect(m_ioView.get(), &IOView::stdOutText, nullptr, nullptr);
0385     disconnect(m_ioView.get(), &IOView::stdErrText, nullptr, nullptr);
0386     if (m_configView->showIOTab()) {
0387         connect(m_ioView.get(), &IOView::stdOutText, m_ioView.get(), &IOView::addStdOutText);
0388         connect(m_ioView.get(), &IOView::stdErrText, m_ioView.get(), &IOView::addStdErrText);
0389     } else {
0390         connect(m_ioView.get(), &IOView::stdOutText, this, &KatePluginGDBView::addOutputText);
0391         connect(m_ioView.get(), &IOView::stdErrText, this, &KatePluginGDBView::addErrorText);
0392     }
0393     QStringList ioFifos;
0394     ioFifos << m_ioView->stdinFifo();
0395     ioFifos << m_ioView->stdoutFifo();
0396     ioFifos << m_ioView->stderrFifo();
0397 
0398     enableDebugActions(true);
0399     m_mainWin->showToolView(m_toolView.get());
0400     m_tabWidget->setCurrentWidget(m_gdbPage);
0401     QScrollBar *sb = m_outputArea->verticalScrollBar();
0402     sb->setValue(sb->maximum());
0403     m_scopeCombo->clear();
0404     m_localsView->clear();
0405 
0406     if (m_configView->debuggerIsGDB()) {
0407         m_debugView->runDebugger(m_configView->currentGDBTarget(), ioFifos);
0408     } else {
0409         m_debugView->runDebugger(m_configView->currentDAPTarget(true));
0410     }
0411 }
0412 
0413 void KatePluginGDBView::slotRestart()
0414 {
0415     m_mainWin->showToolView(m_toolView.get());
0416     m_tabWidget->setCurrentWidget(m_gdbPage);
0417     QScrollBar *sb = m_outputArea->verticalScrollBar();
0418     sb->setValue(sb->maximum());
0419     m_scopeCombo->clear();
0420     m_localsView->clear();
0421 
0422     m_debugView->slotReRun();
0423 }
0424 
0425 void KatePluginGDBView::aboutToShowMenu()
0426 {
0427     if (!m_debugView->debuggerRunning() || m_debugView->debuggerBusy()) {
0428         m_breakpoint->setText(i18n("Insert breakpoint"));
0429         m_breakpoint->setDisabled(true);
0430         return;
0431     }
0432 
0433     m_breakpoint->setDisabled(false);
0434 
0435     KTextEditor::View *editView = m_mainWin->activeView();
0436     QUrl url = editView->document()->url();
0437     int line = editView->cursorPosition().line();
0438 
0439     line++; // GDB uses 1 based line numbers, kate uses 0 based...
0440 
0441     if (m_debugView->hasBreakpoint(url, line)) {
0442         m_breakpoint->setText(i18n("Remove breakpoint"));
0443     } else {
0444         m_breakpoint->setText(i18n("Insert breakpoint"));
0445     }
0446 }
0447 
0448 void KatePluginGDBView::slotToggleBreakpoint()
0449 {
0450     if (!actionCollection()->action(QStringLiteral("continue"))->isEnabled()) {
0451         m_debugView->slotInterrupt();
0452     } else {
0453         KTextEditor::View *editView = m_mainWin->activeView();
0454         QUrl currURL = editView->document()->url();
0455         int line = editView->cursorPosition().line();
0456 
0457         m_debugView->toggleBreakpoint(currURL, line + 1);
0458     }
0459 }
0460 
0461 void KatePluginGDBView::slotBreakpointSet(const QUrl &file, int line)
0462 {
0463     if (auto doc = m_kateApplication->findUrl(file)) {
0464         doc->addMark(line, KTextEditor::Document::BreakpointActive);
0465     }
0466 }
0467 
0468 void KatePluginGDBView::slotBreakpointCleared(const QUrl &file, int line)
0469 {
0470     if (auto doc = m_kateApplication->findUrl(file)) {
0471         doc->removeMark(line, KTextEditor::Document::BreakpointActive);
0472     }
0473 }
0474 
0475 void KatePluginGDBView::slotMovePC()
0476 {
0477     KTextEditor::View *editView = m_mainWin->activeView();
0478     QUrl currURL = editView->document()->url();
0479     KTextEditor::Cursor cursor = editView->cursorPosition();
0480 
0481     m_debugView->movePC(currURL, cursor.line() + 1);
0482 }
0483 
0484 void KatePluginGDBView::slotRunToCursor()
0485 {
0486     KTextEditor::View *editView = m_mainWin->activeView();
0487     QUrl currURL = editView->document()->url();
0488     KTextEditor::Cursor cursor = editView->cursorPosition();
0489 
0490     // GDB starts lines from 1, kate returns lines starting from 0 (displaying 1)
0491     m_debugView->runToCursor(currURL, cursor.line() + 1);
0492 }
0493 
0494 void KatePluginGDBView::slotGoTo(const QUrl &url, int lineNum)
0495 {
0496     // remove last location
0497     if ((url == m_lastExecUrl) && (lineNum == m_lastExecLine)) {
0498         return;
0499     } else {
0500         if (auto doc = m_kateApplication->findUrl(m_lastExecUrl)) {
0501             doc->removeMark(m_lastExecLine, KTextEditor::Document::Execution);
0502         }
0503     }
0504 
0505     // skip not existing files
0506     if (!QFile::exists(url.toLocalFile())) {
0507         m_lastExecLine = -1;
0508         return;
0509     }
0510 
0511     m_lastExecUrl = url;
0512     m_lastExecLine = lineNum;
0513 
0514     KTextEditor::View *editView = m_mainWin->openUrl(m_lastExecUrl);
0515     editView->setCursorPosition(KTextEditor::Cursor(m_lastExecLine, 0));
0516     m_mainWin->window()->raise();
0517     m_mainWin->window()->setFocus();
0518 }
0519 
0520 void KatePluginGDBView::enableDebugActions(bool enable)
0521 {
0522     const bool canMove = m_debugView->canMove();
0523     actionCollection()->action(QStringLiteral("step_in"))->setEnabled(enable && canMove);
0524     actionCollection()->action(QStringLiteral("step_over"))->setEnabled(enable && canMove);
0525     actionCollection()->action(QStringLiteral("step_out"))->setEnabled(enable && canMove);
0526     actionCollection()->action(QStringLiteral("move_pc"))->setEnabled(enable && m_debugView->supportsMovePC());
0527     actionCollection()->action(QStringLiteral("run_to_cursor"))->setEnabled(enable && m_debugView->supportsRunToCursor());
0528     actionCollection()->action(QStringLiteral("popup_gdb"))->setEnabled(enable);
0529     actionCollection()->action(QStringLiteral("continue"))->setEnabled(enable && m_debugView->canContinue());
0530     actionCollection()->action(QStringLiteral("print_value"))->setEnabled(enable);
0531 
0532     // "toggle breakpoint" doubles as interrupt while the program is running
0533     actionCollection()->action(QStringLiteral("toggle_breakpoint"))->setEnabled(m_debugView->canSetBreakpoints());
0534     actionCollection()->action(QStringLiteral("kill"))->setEnabled(m_debugView->debuggerRunning());
0535     actionCollection()->action(QStringLiteral("rerun"))->setEnabled(m_debugView->debuggerRunning());
0536 
0537     // Combine start and continue button to same button
0538     m_continueButton->removeAction(actionCollection()->action(!enable ? QStringLiteral("continue") : QStringLiteral("debug")));
0539     m_continueButton->setDefaultAction(actionCollection()->action(enable ? QStringLiteral("continue") : QStringLiteral("debug")));
0540     m_continueButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
0541 
0542     m_inputArea->setEnabled(enable && !m_debugView->debuggerBusy());
0543     m_threadCombo->setEnabled(enable);
0544     m_stackTree->setEnabled(enable);
0545     m_scopeCombo->setEnabled(enable);
0546     m_localsView->setEnabled(enable);
0547 
0548     if (enable) {
0549         m_inputArea->setFocusPolicy(Qt::WheelFocus);
0550 
0551         if (m_focusOnInput || m_configView->takeFocusAlways()) {
0552             m_inputArea->setFocus();
0553             m_focusOnInput = false;
0554         } else {
0555             if (m_mainWin->activeView()) {
0556                 m_mainWin->activeView()->setFocus();
0557             }
0558         }
0559     } else {
0560         m_inputArea->setFocusPolicy(Qt::NoFocus);
0561         if (m_mainWin->activeView()) {
0562             m_mainWin->activeView()->setFocus();
0563         }
0564     }
0565 
0566     m_ioView->enableInput(!enable && m_debugView->debuggerRunning());
0567 
0568     if ((m_lastExecLine > -1)) {
0569         if (auto doc = m_kateApplication->findUrl(m_lastExecUrl)) {
0570             if (enable) {
0571                 doc->setMarkDescription(KTextEditor::Document::Execution, i18n("Execution point"));
0572                 doc->setMarkIcon(KTextEditor::Document::Execution, QIcon::fromTheme(QStringLiteral("arrow-right")));
0573                 doc->addMark(m_lastExecLine, KTextEditor::Document::Execution);
0574             } else {
0575                 doc->removeMark(m_lastExecLine, KTextEditor::Document::Execution);
0576             }
0577         }
0578     }
0579 }
0580 
0581 void KatePluginGDBView::programEnded()
0582 {
0583     // don't set the execution mark on exit
0584     m_lastExecLine = -1;
0585     m_stackTree->clear();
0586     m_scopeCombo->clear();
0587     m_localsView->clear();
0588     m_threadCombo->clear();
0589 
0590     // Indicate the state change by showing the debug outputArea
0591     m_mainWin->showToolView(m_toolView.get());
0592     m_tabWidget->setCurrentWidget(m_gdbPage);
0593 }
0594 
0595 void KatePluginGDBView::gdbEnded()
0596 {
0597     m_outputArea->clear();
0598     m_localsView->clear();
0599     m_ioView->clearOutput();
0600     clearMarks();
0601 }
0602 
0603 void KatePluginGDBView::clearMarks()
0604 {
0605     const auto documents = m_kateApplication->documents();
0606     for (KTextEditor::Document *doc : documents) {
0607         const QHash<int, KTextEditor::Mark *> marks = doc->marks();
0608         QHashIterator<int, KTextEditor::Mark *> i(marks);
0609         while (i.hasNext()) {
0610             i.next();
0611             if ((i.value()->type == KTextEditor::Document::Execution) || (i.value()->type == KTextEditor::Document::BreakpointActive)) {
0612                 doc->removeMark(i.value()->line, i.value()->type);
0613             }
0614         }
0615     }
0616 }
0617 
0618 void KatePluginGDBView::slotSendCommand()
0619 {
0620     QString cmd = m_inputArea->currentText();
0621 
0622     if (cmd.isEmpty()) {
0623         cmd = m_lastCommand;
0624     }
0625 
0626     m_inputArea->addToHistory(cmd);
0627     m_inputArea->setCurrentItem(QString());
0628     m_focusOnInput = true;
0629     m_lastCommand = cmd;
0630     m_debugView->issueCommand(cmd);
0631 
0632     QScrollBar *sb = m_outputArea->verticalScrollBar();
0633     sb->setValue(sb->maximum());
0634 }
0635 
0636 void KatePluginGDBView::insertStackFrame(int level, const QString &info)
0637 {
0638     if (level < 0) {
0639         m_stackTree->resizeColumnToContents(2);
0640         return;
0641     }
0642 
0643     if (level == 0) {
0644         m_stackTree->clear();
0645     }
0646     QStringList columns;
0647     columns << QStringLiteral("  "); // icon place holder
0648     columns << QString::number(level);
0649     int lastSpace = info.lastIndexOf(QLatin1Char(' '));
0650     QString shortInfo = info.mid(lastSpace);
0651     columns << shortInfo;
0652 
0653     QTreeWidgetItem *item = new QTreeWidgetItem(columns);
0654     item->setToolTip(2, QStringLiteral("<qt>%1<qt>").arg(info));
0655     m_stackTree->insertTopLevelItem(level, item);
0656 }
0657 
0658 void KatePluginGDBView::stackFrameSelected()
0659 {
0660     m_debugView->changeStackFrame(m_stackTree->currentIndex().row());
0661 }
0662 
0663 void KatePluginGDBView::stackFrameChanged(int level)
0664 {
0665     QTreeWidgetItem *current = m_stackTree->topLevelItem(m_lastExecFrame);
0666     QTreeWidgetItem *next = m_stackTree->topLevelItem(level);
0667 
0668     if (current) {
0669         current->setIcon(0, QIcon());
0670     }
0671     if (next) {
0672         next->setIcon(0, QIcon::fromTheme(QStringLiteral("arrow-right")));
0673     }
0674     m_lastExecFrame = level;
0675 }
0676 
0677 void KatePluginGDBView::insertScopes(const QList<dap::Scope> &scopes, std::optional<int> activeId)
0678 {
0679     m_scopeCombo->clear();
0680 
0681     int selected = -1;
0682     int index = 0;
0683     for (const auto &scope : scopes) {
0684         QString name = scope.expensive.value_or(false) ? QStringLiteral("%1!").arg(scope.name) : scope.name;
0685         if (activeId && (activeId == scope.variablesReference)) {
0686             selected = index;
0687         }
0688         m_scopeCombo->addItem(QIcon::fromTheme(QStringLiteral("")).pixmap(10, 10), scope.name, scope.variablesReference);
0689         ++index;
0690     }
0691     if (selected >= 0) {
0692         m_scopeCombo->setCurrentIndex(selected);
0693     }
0694 }
0695 
0696 void KatePluginGDBView::scopeSelected(int scope)
0697 {
0698     if (scope < 0)
0699         return;
0700     m_debugView->changeScope(m_scopeCombo->itemData(scope).toInt());
0701 }
0702 
0703 void KatePluginGDBView::insertThread(const dap::Thread &thread, bool active)
0704 {
0705     if (thread.id < 0) {
0706         m_threadCombo->clear();
0707         m_activeThread = -1;
0708         return;
0709     }
0710     QString text = i18n("Thread %1", thread.id);
0711     if (!thread.name.isEmpty()) {
0712         text += QStringLiteral(": %1").arg(thread.name);
0713     }
0714     if (!active) {
0715         m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("")).pixmap(10, 10), text, thread.id);
0716     } else {
0717         m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10), text, thread.id);
0718         m_activeThread = m_threadCombo->count() - 1;
0719     }
0720     m_threadCombo->setCurrentIndex(m_activeThread);
0721 }
0722 
0723 void KatePluginGDBView::threadSelected(int thread)
0724 {
0725     if (thread < 0)
0726         return;
0727     m_debugView->changeThread(m_threadCombo->itemData(thread).toInt());
0728 }
0729 
0730 QString KatePluginGDBView::currentWord()
0731 {
0732     KTextEditor::View *kv = m_mainWin->activeView();
0733     if (!kv) {
0734         qDebug() << "no KTextEditor::View";
0735         return QString();
0736     }
0737 
0738     if (!kv->cursorPosition().isValid()) {
0739         qDebug() << "cursor not valid!";
0740         return QString();
0741     }
0742 
0743     int line = kv->cursorPosition().line();
0744     int col = kv->cursorPosition().column();
0745 
0746     QString linestr = kv->document()->line(line);
0747 
0748     int startPos = qMax(qMin(col, linestr.length() - 1), 0);
0749     int lindex = linestr.length() - 1;
0750     int endPos = startPos;
0751     while (startPos >= 0
0752            && (linestr[startPos].isLetterOrNumber() || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~')
0753                || ((startPos > 1) && (linestr[startPos] == QLatin1Char('.')) && !linestr[startPos - 1].isSpace())
0754                || ((startPos > 2) && (linestr[startPos] == QLatin1Char('>')) && (linestr[startPos - 1] == QLatin1Char('-'))
0755                    && !linestr[startPos - 2].isSpace()))) {
0756         if (linestr[startPos] == QLatin1Char('>')) {
0757             startPos--;
0758         }
0759         startPos--;
0760     }
0761     while (
0762         endPos < linestr.length()
0763         && (linestr[endPos].isLetterOrNumber() || linestr[endPos] == QLatin1Char('_')
0764             || ((endPos < lindex - 1) && (linestr[endPos] == QLatin1Char('.')) && !linestr[endPos + 1].isSpace())
0765             || ((endPos < lindex - 2) && (linestr[endPos] == QLatin1Char('-')) && (linestr[endPos + 1] == QLatin1Char('>')) && !linestr[endPos + 2].isSpace())
0766             || ((endPos > 1) && (linestr[endPos - 1] == QLatin1Char('-')) && (linestr[endPos] == QLatin1Char('>'))))) {
0767         if (linestr[endPos] == QLatin1Char('-')) {
0768             endPos++;
0769         }
0770         endPos++;
0771     }
0772     if (startPos == endPos) {
0773         qDebug() << "no word found!";
0774         return QString();
0775     }
0776 
0777     // qDebug() << linestr.mid(startPos+1, endPos-startPos-1);
0778     return linestr.mid(startPos + 1, endPos - startPos - 1);
0779 }
0780 
0781 void KatePluginGDBView::slotValue()
0782 {
0783     QString variable;
0784     KTextEditor::View *editView = m_mainWin->activeView();
0785     if (editView && editView->selection() && editView->selectionRange().onSingleLine()) {
0786         variable = editView->selectionText();
0787     }
0788 
0789     if (variable.isEmpty()) {
0790         variable = currentWord();
0791     }
0792 
0793     if (variable.isEmpty()) {
0794         return;
0795     }
0796 
0797     m_inputArea->addToHistory(m_debugView->slotPrintVariable(variable));
0798     m_inputArea->setCurrentItem(QString());
0799 
0800     m_mainWin->showToolView(m_toolView.get());
0801     m_tabWidget->setCurrentWidget(m_gdbPage);
0802 
0803     QScrollBar *sb = m_outputArea->verticalScrollBar();
0804     sb->setValue(sb->maximum());
0805 }
0806 
0807 void KatePluginGDBView::showIO(bool show)
0808 {
0809     if (show) {
0810         m_tabWidget->addTab(m_ioView.get(), i18n("IO"));
0811     } else {
0812         m_tabWidget->removeTab(m_tabWidget->indexOf(m_ioView.get()));
0813     }
0814 }
0815 
0816 void KatePluginGDBView::addOutput(const dap::Output &output)
0817 {
0818     if (output.isSpecialOutput()) {
0819         addErrorText(output.output);
0820         return;
0821     }
0822     if (m_configView->showIOTab()) {
0823         if (output.category == dap::Output::Category::Stdout) {
0824             m_ioView->addStdOutText(output.output);
0825         } else {
0826             m_ioView->addStdErrText(output.output);
0827         }
0828     } else {
0829         if (output.category == dap::Output::Category::Stdout) {
0830             addOutputText(output.output);
0831         } else {
0832             addErrorText(output.output);
0833         }
0834     }
0835 }
0836 
0837 void KatePluginGDBView::addOutputText(QString const &text)
0838 {
0839     QScrollBar *scrollb = m_outputArea->verticalScrollBar();
0840     if (!scrollb) {
0841         return;
0842     }
0843     bool atEnd = (scrollb->value() == scrollb->maximum());
0844 
0845     QTextCursor cursor = m_outputArea->textCursor();
0846     if (!cursor.atEnd()) {
0847         cursor.movePosition(QTextCursor::End);
0848     }
0849     cursor.insertText(text);
0850 
0851     if (atEnd) {
0852         scrollb->setValue(scrollb->maximum());
0853     }
0854 }
0855 
0856 void KatePluginGDBView::addErrorText(QString const &text)
0857 {
0858     m_outputArea->setFontItalic(true);
0859     addOutputText(text);
0860     m_outputArea->setFontItalic(false);
0861 }
0862 
0863 bool KatePluginGDBView::eventFilter(QObject *obj, QEvent *event)
0864 {
0865     if (event->type() == QEvent::KeyPress) {
0866         QKeyEvent *ke = static_cast<QKeyEvent *>(event);
0867         if ((obj == m_toolView.get()) && (ke->key() == Qt::Key_Escape)) {
0868             m_mainWin->hideToolView(m_toolView.get());
0869             event->accept();
0870             return true;
0871         }
0872     }
0873     return QObject::eventFilter(obj, event);
0874 }
0875 
0876 void KatePluginGDBView::handleEsc(QEvent *e)
0877 {
0878     if (!m_mainWin || !m_toolView) {
0879         return;
0880     }
0881 
0882     QKeyEvent *k = static_cast<QKeyEvent *>(e);
0883     if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) {
0884         if (m_toolView->isVisible()) {
0885             m_mainWin->hideToolView(m_toolView.get());
0886         }
0887     }
0888 }
0889 
0890 void KatePluginGDBView::enableBreakpointMarks(KTextEditor::Document *document)
0891 {
0892     if (document) {
0893         document->setEditableMarks(document->editableMarks() | KTextEditor::Document::BreakpointActive);
0894         document->setMarkDescription(KTextEditor::Document::BreakpointActive, i18n("Breakpoint"));
0895         document->setMarkIcon(KTextEditor::Document::BreakpointActive, QIcon::fromTheme(QStringLiteral("media-record")));
0896     }
0897 }
0898 
0899 void KatePluginGDBView::displayMessage(const QString &msg, KTextEditor::Message::MessageType level)
0900 {
0901     KTextEditor::View *kv = m_mainWin->activeView();
0902     if (!kv) {
0903         return;
0904     }
0905 
0906     delete m_infoMessage;
0907     m_infoMessage = new KTextEditor::Message(msg, level);
0908     m_infoMessage->setWordWrap(true);
0909     m_infoMessage->setPosition(KTextEditor::Message::BottomInView);
0910     m_infoMessage->setAutoHide(8000);
0911     m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate);
0912     m_infoMessage->setView(kv);
0913     kv->document()->postMessage(m_infoMessage);
0914 }
0915 
0916 QToolButton *KatePluginGDBView::createDebugButton(QAction *action)
0917 {
0918     QToolButton *button = new QToolButton();
0919     button->setDefaultAction(action);
0920     button->setAutoRaise(true);
0921     return button;
0922 }
0923 
0924 #include "moc_plugin_kategdb.cpp"
0925 #include "plugin_kategdb.moc"