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"