1 //
2 // Description: Kate Plugin for GDB integration
3 //
4 //
5 // SPDX-FileCopyrightText: 2010 Ian Wakeling <ian.wakeling@ntlworld.com>
6 // SPDX-FileCopyrightText: 2010-2014 Kåre Särs <kare.sars@iki.fi>
7 //
8 // SPDX-License-Identifier: LGPL-2.0-only
9
10 #include "plugin_kategdb.h"
11
12 #include <QFile>
13 #include <QFileInfo>
14 #include <QFontDatabase>
15 #include <QKeyEvent>
16 #include <QLayout>
17 #include <QScrollBar>
18 #include <QSplitter>
19 #include <QTabWidget>
20 #include <QTextEdit>
21 #include <QToolBar>
22 #include <QTreeWidget>
23
24 #include <KActionCollection>
25 #include <KConfigGroup>
26 #include <KXMLGUIFactory>
27 #include <QAction>
28 #include <QMenu>
29
30 #include <KAboutData>
31 #include <KColorScheme>
32 #include <KHistoryComboBox>
33 #include <KLocalizedString>
34 #include <KPluginFactory>
35
36 #include <ktexteditor/document.h>
37 #include <ktexteditor/editor.h>
38 #include <ktexteditor/markinterface.h>
39 #include <ktexteditor/view.h>
40
41 K_PLUGIN_FACTORY_WITH_JSON(KatePluginGDBFactory, "kategdbplugin.json", registerPlugin<KatePluginGDB>();)
42
KatePluginGDB(QObject * parent,const VariantList &)43 KatePluginGDB::KatePluginGDB(QObject *parent, const VariantList &)
44 : KTextEditor::Plugin(parent)
45 {
46 // FIXME KF5 KGlobal::locale()->insertCatalog("kategdbplugin");
47 }
48
~KatePluginGDB()49 KatePluginGDB::~KatePluginGDB()
50 {
51 }
52
createView(KTextEditor::MainWindow * mainWindow)53 QObject *KatePluginGDB::createView(KTextEditor::MainWindow *mainWindow)
54 {
55 return new KatePluginGDBView(this, mainWindow);
56 }
57
KatePluginGDBView(KTextEditor::Plugin * plugin,KTextEditor::MainWindow * mainWin)58 KatePluginGDBView::KatePluginGDBView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin)
59 : QObject(mainWin)
60 , m_mainWin(mainWin)
61 {
62 m_lastExecUrl = QUrl();
63 m_lastExecLine = -1;
64 m_lastExecFrame = 0;
65 m_kateApplication = KTextEditor::Editor::instance()->application();
66 m_focusOnInput = true;
67 m_activeThread = -1;
68
69 KXMLGUIClient::setComponentName(QStringLiteral("kategdb"), i18n("Kate GDB"));
70 setXMLFile(QStringLiteral("ui.rc"));
71
72 m_toolView.reset(m_mainWin->createToolView(plugin,
73 i18n("Debug View"),
74 KTextEditor::MainWindow::Bottom,
75 QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")),
76 i18n("Debug View")));
77
78 m_localsStackToolView.reset(m_mainWin->createToolView(plugin,
79 i18n("Locals and Stack"),
80 KTextEditor::MainWindow::Right,
81 QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")),
82 i18n("Locals and Stack")));
83
84 m_tabWidget = new QTabWidget(m_toolView.get());
85 // Output
86 m_outputArea = new QTextEdit();
87 m_outputArea->setAcceptRichText(false);
88 m_outputArea->setReadOnly(true);
89 m_outputArea->setUndoRedoEnabled(false);
90 // fixed wide font, like konsole
91 m_outputArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
92 // alternate color scheme, like konsole
93 KColorScheme schemeView(QPalette::Active, KColorScheme::View);
94 m_outputArea->setTextBackgroundColor(schemeView.foreground().color());
95 m_outputArea->setTextColor(schemeView.background().color());
96 QPalette p = m_outputArea->palette();
97 p.setColor(QPalette::Base, schemeView.foreground().color());
98 m_outputArea->setPalette(p);
99
100 // input
101 m_inputArea = new KHistoryComboBox(true);
102 connect(m_inputArea, static_cast<void (KHistoryComboBox::*)(const QString &)>(&KHistoryComboBox::returnPressed), this, &KatePluginGDBView::slotSendCommand);
103 QHBoxLayout *inputLayout = new QHBoxLayout();
104 inputLayout->addWidget(m_inputArea, 10);
105 inputLayout->setContentsMargins(0, 0, 0, 0);
106 m_outputArea->setFocusProxy(m_inputArea); // take the focus from the outputArea
107
108 m_gdbPage = new QWidget();
109 QVBoxLayout *layout = new QVBoxLayout(m_gdbPage);
110 layout->addWidget(m_outputArea);
111 layout->addLayout(inputLayout);
112 layout->setStretch(0, 10);
113 layout->setContentsMargins(0, 0, 0, 0);
114 layout->setSpacing(0);
115
116 // stack page
117 QWidget *stackContainer = new QWidget();
118 QVBoxLayout *stackLayout = new QVBoxLayout(stackContainer);
119 m_threadCombo = new QComboBox();
120 m_stackTree = new QTreeWidget();
121 stackLayout->addWidget(m_threadCombo);
122 stackLayout->addWidget(m_stackTree);
123 stackLayout->setStretch(0, 10);
124 stackLayout->setContentsMargins(0, 0, 0, 0);
125 stackLayout->setSpacing(0);
126 QStringList headers;
127 headers << QStringLiteral(" ") << i18nc("Column label (frame number)", "Nr") << i18nc("Column label", "Frame");
128 m_stackTree->setHeaderLabels(headers);
129 m_stackTree->setRootIsDecorated(false);
130 m_stackTree->resizeColumnToContents(0);
131 m_stackTree->resizeColumnToContents(1);
132 m_stackTree->setAutoScroll(false);
133 connect(m_stackTree, &QTreeWidget::itemActivated, this, &KatePluginGDBView::stackFrameSelected);
134
135 connect(m_threadCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KatePluginGDBView::threadSelected);
136
137 m_localsView = new LocalsView();
138
139 QSplitter *locStackSplitter = new QSplitter(m_localsStackToolView.get());
140 locStackSplitter->addWidget(m_localsView);
141 locStackSplitter->addWidget(stackContainer);
142 locStackSplitter->setOrientation(Qt::Vertical);
143
144 // config page
145 m_configView = new ConfigView(nullptr, mainWin);
146
147 m_ioView = std::make_unique<IOView>();
148 connect(m_configView, &ConfigView::showIO, this, &KatePluginGDBView::showIO);
149
150 m_tabWidget->addTab(m_gdbPage, i18nc("Tab label", "GDB Output"));
151 m_tabWidget->addTab(m_configView, i18nc("Tab label", "Settings"));
152
153 m_debugView = new DebugView(this);
154 connect(m_debugView, &DebugView::readyForInput, this, &KatePluginGDBView::enableDebugActions);
155
156 connect(m_debugView, &DebugView::outputText, this, &KatePluginGDBView::addOutputText);
157
158 connect(m_debugView, &DebugView::outputError, this, &KatePluginGDBView::addErrorText);
159
160 connect(m_debugView, &DebugView::debugLocationChanged, this, &KatePluginGDBView::slotGoTo);
161
162 connect(m_debugView, &DebugView::breakPointSet, this, &KatePluginGDBView::slotBreakpointSet);
163
164 connect(m_debugView, &DebugView::breakPointCleared, this, &KatePluginGDBView::slotBreakpointCleared);
165
166 connect(m_debugView, &DebugView::clearBreakpointMarks, this, &KatePluginGDBView::clearMarks);
167
168 connect(m_debugView, &DebugView::programEnded, this, &KatePluginGDBView::programEnded);
169
170 connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::programEnded);
171
172 connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::gdbEnded);
173
174 connect(m_debugView, &DebugView::stackFrameInfo, this, &KatePluginGDBView::insertStackFrame);
175
176 connect(m_debugView, &DebugView::stackFrameChanged, this, &KatePluginGDBView::stackFrameChanged);
177
178 connect(m_debugView, &DebugView::infoLocal, m_localsView, &LocalsView::addLocal);
179
180 connect(m_debugView, &DebugView::threadInfo, this, &KatePluginGDBView::insertThread);
181
182 connect(m_debugView, &DebugView::sourceFileNotFound, this, [this](const QString &fileName) {
183 displayMessage(xi18nc("@info",
184 "<title>Could not open file:</title><nl/>%1<br/>Try adding a search path to Advanced Settings -> Source file search paths",
185 fileName),
186 KTextEditor::Message::Error);
187 });
188
189 connect(m_localsView, &LocalsView::localsVisible, m_debugView, &DebugView::slotQueryLocals);
190
191 connect(m_configView, &ConfigView::configChanged, this, [this]() {
192 GDBTargetConf config = m_configView->currentTarget();
193 if (m_debugView->targetName() == config.targetName) {
194 m_debugView->setFileSearchPaths(config.srcPaths);
195 }
196 });
197
198 // Actions
199 m_configView->registerActions(actionCollection());
200
201 QAction *a = actionCollection()->addAction(QStringLiteral("debug"));
202 a->setText(i18n("Start Debugging"));
203 a->setIcon(QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")));
204 connect(a, &QAction::triggered, this, &KatePluginGDBView::slotDebug);
205
206 a = actionCollection()->addAction(QStringLiteral("kill"));
207 a->setText(i18n("Kill / Stop Debugging"));
208 a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop")));
209 connect(a, &QAction::triggered, m_debugView, &DebugView::slotKill);
210
211 a = actionCollection()->addAction(QStringLiteral("rerun"));
212 a->setText(i18n("Restart Debugging"));
213 a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
214 connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRestart);
215
216 a = actionCollection()->addAction(QStringLiteral("toggle_breakpoint"));
217 a->setText(i18n("Toggle Breakpoint / Break"));
218 a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
219 connect(a, &QAction::triggered, this, &KatePluginGDBView::slotToggleBreakpoint);
220
221 a = actionCollection()->addAction(QStringLiteral("step_in"));
222 a->setText(i18n("Step In"));
223 a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-into")));
224 connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepInto);
225
226 a = actionCollection()->addAction(QStringLiteral("step_over"));
227 a->setText(i18n("Step Over"));
228 a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-over")));
229 connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOver);
230
231 a = actionCollection()->addAction(QStringLiteral("step_out"));
232 a->setText(i18n("Step Out"));
233 a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-out")));
234 connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOut);
235
236 a = actionCollection()->addAction(QStringLiteral("move_pc"));
237 a->setText(i18nc("Move Program Counter (next execution)", "Move PC"));
238 connect(a, &QAction::triggered, this, &KatePluginGDBView::slotMovePC);
239
240 a = actionCollection()->addAction(QStringLiteral("run_to_cursor"));
241 a->setText(i18n("Run To Cursor"));
242 a->setIcon(QIcon::fromTheme(QStringLiteral("debug-run-cursor")));
243 connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRunToCursor);
244
245 a = actionCollection()->addAction(QStringLiteral("continue"));
246 a->setText(i18n("Continue"));
247 a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
248 connect(a, &QAction::triggered, m_debugView, &DebugView::slotContinue);
249
250 a = actionCollection()->addAction(QStringLiteral("print_value"));
251 a->setText(i18n("Print Value"));
252 a->setIcon(QIcon::fromTheme(QStringLiteral("document-preview")));
253 connect(a, &QAction::triggered, this, &KatePluginGDBView::slotValue);
254
255 // popup context m_menu
256 m_menu = new KActionMenu(i18n("Debug"), this);
257 actionCollection()->addAction(QStringLiteral("popup_gdb"), m_menu);
258 connect(m_menu->menu(), &QMenu::aboutToShow, this, &KatePluginGDBView::aboutToShowMenu);
259
260 m_breakpoint = m_menu->menu()->addAction(i18n("popup_breakpoint"), this, &KatePluginGDBView::slotToggleBreakpoint);
261
262 QAction *popupAction = m_menu->menu()->addAction(i18n("popup_run_to_cursor"), this, &KatePluginGDBView::slotRunToCursor);
263 popupAction->setText(i18n("Run To Cursor"));
264 popupAction = m_menu->menu()->addAction(QStringLiteral("move_pc"), this, &KatePluginGDBView::slotMovePC);
265 popupAction->setText(i18nc("Move Program Counter (next execution)", "Move PC"));
266
267 enableDebugActions(false);
268
269 connect(m_mainWin, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KatePluginGDBView::handleEsc);
270
271 m_toolView->installEventFilter(this);
272
273 m_mainWin->guiFactory()->addClient(this);
274 }
275
~KatePluginGDBView()276 KatePluginGDBView::~KatePluginGDBView()
277 {
278 m_mainWin->guiFactory()->removeClient(this);
279 }
280
readSessionConfig(const KConfigGroup & config)281 void KatePluginGDBView::readSessionConfig(const KConfigGroup &config)
282 {
283 m_configView->readConfig(config);
284 }
285
writeSessionConfig(KConfigGroup & config)286 void KatePluginGDBView::writeSessionConfig(KConfigGroup &config)
287 {
288 m_configView->writeConfig(config);
289 }
290
slotDebug()291 void KatePluginGDBView::slotDebug()
292 {
293 disconnect(m_ioView.get(), &IOView::stdOutText, nullptr, nullptr);
294 disconnect(m_ioView.get(), &IOView::stdErrText, nullptr, nullptr);
295 if (m_configView->showIOTab()) {
296 connect(m_ioView.get(), &IOView::stdOutText, m_ioView.get(), &IOView::addStdOutText);
297 connect(m_ioView.get(), &IOView::stdErrText, m_ioView.get(), &IOView::addStdErrText);
298 } else {
299 connect(m_ioView.get(), &IOView::stdOutText, this, &KatePluginGDBView::addOutputText);
300 connect(m_ioView.get(), &IOView::stdErrText, this, &KatePluginGDBView::addErrorText);
301 }
302 QStringList ioFifos;
303 ioFifos << m_ioView->stdinFifo();
304 ioFifos << m_ioView->stdoutFifo();
305 ioFifos << m_ioView->stderrFifo();
306
307 enableDebugActions(true);
308 m_mainWin->showToolView(m_toolView.get());
309 m_tabWidget->setCurrentWidget(m_gdbPage);
310 QScrollBar *sb = m_outputArea->verticalScrollBar();
311 sb->setValue(sb->maximum());
312 m_localsView->clear();
313
314 m_debugView->runDebugger(m_configView->currentTarget(), ioFifos);
315 }
316
slotRestart()317 void KatePluginGDBView::slotRestart()
318 {
319 m_mainWin->showToolView(m_toolView.get());
320 m_tabWidget->setCurrentWidget(m_gdbPage);
321 QScrollBar *sb = m_outputArea->verticalScrollBar();
322 sb->setValue(sb->maximum());
323 m_localsView->clear();
324
325 m_debugView->slotReRun();
326 }
327
aboutToShowMenu()328 void KatePluginGDBView::aboutToShowMenu()
329 {
330 if (!m_debugView->debuggerRunning() || m_debugView->debuggerBusy()) {
331 m_breakpoint->setText(i18n("Insert breakpoint"));
332 m_breakpoint->setDisabled(true);
333 return;
334 }
335
336 m_breakpoint->setDisabled(false);
337
338 KTextEditor::View *editView = m_mainWin->activeView();
339 QUrl url = editView->document()->url();
340 int line = editView->cursorPosition().line();
341
342 line++; // GDB uses 1 based line numbers, kate uses 0 based...
343
344 if (m_debugView->hasBreakpoint(url, line)) {
345 m_breakpoint->setText(i18n("Remove breakpoint"));
346 } else {
347 m_breakpoint->setText(i18n("Insert breakpoint"));
348 }
349 }
350
slotToggleBreakpoint()351 void KatePluginGDBView::slotToggleBreakpoint()
352 {
353 if (!actionCollection()->action(QStringLiteral("continue"))->isEnabled()) {
354 m_debugView->slotInterrupt();
355 } else {
356 KTextEditor::View *editView = m_mainWin->activeView();
357 QUrl currURL = editView->document()->url();
358 int line = editView->cursorPosition().line();
359
360 m_debugView->toggleBreakpoint(currURL, line + 1);
361 }
362 }
363
slotBreakpointSet(const QUrl & file,int line)364 void KatePluginGDBView::slotBreakpointSet(const QUrl &file, int line)
365 {
366 KTextEditor::MarkInterfaceV2 *iface = qobject_cast<KTextEditor::MarkInterfaceV2 *>(m_kateApplication->findUrl(file));
367
368 if (iface) {
369 iface->setMarkDescription(KTextEditor::MarkInterface::BreakpointActive, i18n("Breakpoint"));
370 iface->setMarkIcon(KTextEditor::MarkInterface::BreakpointActive, QIcon::fromTheme(QStringLiteral("media-playback-pause")));
371 iface->addMark(line, KTextEditor::MarkInterface::BreakpointActive);
372 }
373 }
374
slotBreakpointCleared(const QUrl & file,int line)375 void KatePluginGDBView::slotBreakpointCleared(const QUrl &file, int line)
376 {
377 KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface *>(m_kateApplication->findUrl(file));
378
379 if (iface) {
380 iface->removeMark(line, KTextEditor::MarkInterface::BreakpointActive);
381 }
382 }
383
slotMovePC()384 void KatePluginGDBView::slotMovePC()
385 {
386 KTextEditor::View *editView = m_mainWin->activeView();
387 QUrl currURL = editView->document()->url();
388 KTextEditor::Cursor cursor = editView->cursorPosition();
389
390 m_debugView->movePC(currURL, cursor.line() + 1);
391 }
392
slotRunToCursor()393 void KatePluginGDBView::slotRunToCursor()
394 {
395 KTextEditor::View *editView = m_mainWin->activeView();
396 QUrl currURL = editView->document()->url();
397 KTextEditor::Cursor cursor = editView->cursorPosition();
398
399 // GDB starts lines from 1, kate returns lines starting from 0 (displaying 1)
400 m_debugView->runToCursor(currURL, cursor.line() + 1);
401 }
402
slotGoTo(const QUrl & url,int lineNum)403 void KatePluginGDBView::slotGoTo(const QUrl &url, int lineNum)
404 {
405 // skip not existing files
406 if (!QFile::exists(url.toLocalFile())) {
407 m_lastExecLine = -1;
408 return;
409 }
410
411 m_lastExecUrl = url;
412 m_lastExecLine = lineNum;
413
414 KTextEditor::View *editView = m_mainWin->openUrl(m_lastExecUrl);
415 editView->setCursorPosition(KTextEditor::Cursor(m_lastExecLine, 0));
416 m_mainWin->window()->raise();
417 m_mainWin->window()->setFocus();
418 }
419
enableDebugActions(bool enable)420 void KatePluginGDBView::enableDebugActions(bool enable)
421 {
422 actionCollection()->action(QStringLiteral("step_in"))->setEnabled(enable);
423 actionCollection()->action(QStringLiteral("step_over"))->setEnabled(enable);
424 actionCollection()->action(QStringLiteral("step_out"))->setEnabled(enable);
425 actionCollection()->action(QStringLiteral("move_pc"))->setEnabled(enable);
426 actionCollection()->action(QStringLiteral("run_to_cursor"))->setEnabled(enable);
427 actionCollection()->action(QStringLiteral("popup_gdb"))->setEnabled(enable);
428 actionCollection()->action(QStringLiteral("continue"))->setEnabled(enable);
429 actionCollection()->action(QStringLiteral("print_value"))->setEnabled(enable);
430
431 // "toggle breakpoint" doubles as interrupt while the program is running
432 actionCollection()->action(QStringLiteral("toggle_breakpoint"))->setEnabled(m_debugView->debuggerRunning());
433 actionCollection()->action(QStringLiteral("kill"))->setEnabled(m_debugView->debuggerRunning());
434 actionCollection()->action(QStringLiteral("rerun"))->setEnabled(m_debugView->debuggerRunning());
435
436 m_inputArea->setEnabled(enable);
437 m_threadCombo->setEnabled(enable);
438 m_stackTree->setEnabled(enable);
439 m_localsView->setEnabled(enable);
440
441 if (enable) {
442 m_inputArea->setFocusPolicy(Qt::WheelFocus);
443
444 if (m_focusOnInput || m_configView->takeFocusAlways()) {
445 m_inputArea->setFocus();
446 m_focusOnInput = false;
447 } else {
448 m_mainWin->activeView()->setFocus();
449 }
450 } else {
451 m_inputArea->setFocusPolicy(Qt::NoFocus);
452 if (m_mainWin->activeView()) {
453 m_mainWin->activeView()->setFocus();
454 }
455 }
456
457 m_ioView->enableInput(!enable && m_debugView->debuggerRunning());
458
459 if ((m_lastExecLine > -1)) {
460 KTextEditor::MarkInterfaceV2 *iface = qobject_cast<KTextEditor::MarkInterfaceV2 *>(m_kateApplication->findUrl(m_lastExecUrl));
461
462 if (iface) {
463 if (enable) {
464 iface->setMarkDescription(KTextEditor::MarkInterface::Execution, i18n("Execution point"));
465 iface->setMarkIcon(KTextEditor::MarkInterface::Execution, QIcon::fromTheme(QStringLiteral("arrow-right")));
466 iface->addMark(m_lastExecLine, KTextEditor::MarkInterface::Execution);
467 } else {
468 iface->removeMark(m_lastExecLine, KTextEditor::MarkInterface::Execution);
469 }
470 }
471 }
472 }
473
programEnded()474 void KatePluginGDBView::programEnded()
475 {
476 // don't set the execution mark on exit
477 m_lastExecLine = -1;
478 m_stackTree->clear();
479 m_localsView->clear();
480 m_threadCombo->clear();
481
482 // Indicate the state change by showing the debug outputArea
483 m_mainWin->showToolView(m_toolView.get());
484 m_tabWidget->setCurrentWidget(m_gdbPage);
485 }
486
gdbEnded()487 void KatePluginGDBView::gdbEnded()
488 {
489 m_outputArea->clear();
490 m_localsView->clear();
491 m_ioView->clearOutput();
492 clearMarks();
493 }
494
clearMarks()495 void KatePluginGDBView::clearMarks()
496 {
497 KTextEditor::MarkInterface *iface;
498 const auto documents = m_kateApplication->documents();
499 for (KTextEditor::Document *doc : documents) {
500 iface = qobject_cast<KTextEditor::MarkInterface *>(doc);
501 if (iface) {
502 const QHash<int, KTextEditor::Mark *> marks = iface->marks();
503 QHashIterator<int, KTextEditor::Mark *> i(marks);
504 while (i.hasNext()) {
505 i.next();
506 if ((i.value()->type == KTextEditor::MarkInterface::Execution) || (i.value()->type == KTextEditor::MarkInterface::BreakpointActive)) {
507 iface->removeMark(i.value()->line, i.value()->type);
508 }
509 }
510 }
511 }
512 }
513
slotSendCommand()514 void KatePluginGDBView::slotSendCommand()
515 {
516 QString cmd = m_inputArea->currentText();
517
518 if (cmd.isEmpty()) {
519 cmd = m_lastCommand;
520 }
521
522 m_inputArea->addToHistory(cmd);
523 m_inputArea->setCurrentItem(QString());
524 m_focusOnInput = true;
525 m_lastCommand = cmd;
526 m_debugView->issueCommand(cmd);
527
528 QScrollBar *sb = m_outputArea->verticalScrollBar();
529 sb->setValue(sb->maximum());
530 }
531
insertStackFrame(QString const & level,QString const & info)532 void KatePluginGDBView::insertStackFrame(QString const &level, QString const &info)
533 {
534 if (level.isEmpty() && info.isEmpty()) {
535 m_stackTree->resizeColumnToContents(2);
536 return;
537 }
538
539 if (level == QLatin1Char('0')) {
540 m_stackTree->clear();
541 }
542 QStringList columns;
543 columns << QStringLiteral(" "); // icon place holder
544 columns << level;
545 int lastSpace = info.lastIndexOf(QLatin1Char(' '));
546 QString shortInfo = info.mid(lastSpace);
547 columns << shortInfo;
548
549 QTreeWidgetItem *item = new QTreeWidgetItem(columns);
550 item->setToolTip(2, QStringLiteral("<qt>%1<qt>").arg(info));
551 m_stackTree->insertTopLevelItem(level.toInt(), item);
552 }
553
stackFrameSelected()554 void KatePluginGDBView::stackFrameSelected()
555 {
556 m_debugView->issueCommand(QStringLiteral("(Q)f %1").arg(m_stackTree->currentIndex().row()));
557 }
558
stackFrameChanged(int level)559 void KatePluginGDBView::stackFrameChanged(int level)
560 {
561 QTreeWidgetItem *current = m_stackTree->topLevelItem(m_lastExecFrame);
562 QTreeWidgetItem *next = m_stackTree->topLevelItem(level);
563
564 if (current) {
565 current->setIcon(0, QIcon());
566 }
567 if (next) {
568 next->setIcon(0, QIcon::fromTheme(QStringLiteral("arrow-right")));
569 }
570 m_lastExecFrame = level;
571 }
572
insertThread(int number,bool active)573 void KatePluginGDBView::insertThread(int number, bool active)
574 {
575 if (number < 0) {
576 m_threadCombo->clear();
577 m_activeThread = -1;
578 return;
579 }
580 if (!active) {
581 m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("")).pixmap(10, 10), i18n("Thread %1", number), number);
582 } else {
583 m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10), i18n("Thread %1", number), number);
584 m_activeThread = m_threadCombo->count() - 1;
585 }
586 m_threadCombo->setCurrentIndex(m_activeThread);
587 }
588
threadSelected(int thread)589 void KatePluginGDBView::threadSelected(int thread)
590 {
591 m_debugView->issueCommand(QStringLiteral("thread %1").arg(m_threadCombo->itemData(thread).toInt()));
592 }
593
currentWord()594 QString KatePluginGDBView::currentWord()
595 {
596 KTextEditor::View *kv = m_mainWin->activeView();
597 if (!kv) {
598 qDebug() << "no KTextEditor::View";
599 return QString();
600 }
601
602 if (!kv->cursorPosition().isValid()) {
603 qDebug() << "cursor not valid!";
604 return QString();
605 }
606
607 int line = kv->cursorPosition().line();
608 int col = kv->cursorPosition().column();
609
610 QString linestr = kv->document()->line(line);
611
612 int startPos = qMax(qMin(col, linestr.length() - 1), 0);
613 int lindex = linestr.length() - 1;
614 int endPos = startPos;
615 while (startPos >= 0
616 && (linestr[startPos].isLetterOrNumber() || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~')
617 || ((startPos > 1) && (linestr[startPos] == QLatin1Char('.')) && !linestr[startPos - 1].isSpace())
618 || ((startPos > 2) && (linestr[startPos] == QLatin1Char('>')) && (linestr[startPos - 1] == QLatin1Char('-'))
619 && !linestr[startPos - 2].isSpace()))) {
620 if (linestr[startPos] == QLatin1Char('>')) {
621 startPos--;
622 }
623 startPos--;
624 }
625 while (
626 endPos < linestr.length()
627 && (linestr[endPos].isLetterOrNumber() || linestr[endPos] == QLatin1Char('_')
628 || ((endPos < lindex - 1) && (linestr[endPos] == QLatin1Char('.')) && !linestr[endPos + 1].isSpace())
629 || ((endPos < lindex - 2) && (linestr[endPos] == QLatin1Char('-')) && (linestr[endPos + 1] == QLatin1Char('>')) && !linestr[endPos + 2].isSpace())
630 || ((endPos > 1) && (linestr[endPos - 1] == QLatin1Char('-')) && (linestr[endPos] == QLatin1Char('>'))))) {
631 if (linestr[endPos] == QLatin1Char('-')) {
632 endPos++;
633 }
634 endPos++;
635 }
636 if (startPos == endPos) {
637 qDebug() << "no word found!";
638 return QString();
639 }
640
641 // qDebug() << linestr.mid(startPos+1, endPos-startPos-1);
642 return linestr.mid(startPos + 1, endPos - startPos - 1);
643 }
644
slotValue()645 void KatePluginGDBView::slotValue()
646 {
647 QString variable;
648 KTextEditor::View *editView = m_mainWin->activeView();
649 if (editView && editView->selection() && editView->selectionRange().onSingleLine()) {
650 variable = editView->selectionText();
651 }
652
653 if (variable.isEmpty()) {
654 variable = currentWord();
655 }
656
657 if (variable.isEmpty()) {
658 return;
659 }
660
661 QString cmd = QStringLiteral("print %1").arg(variable);
662 m_debugView->issueCommand(cmd);
663 m_inputArea->addToHistory(cmd);
664 m_inputArea->setCurrentItem(QString());
665
666 m_mainWin->showToolView(m_toolView.get());
667 m_tabWidget->setCurrentWidget(m_gdbPage);
668
669 QScrollBar *sb = m_outputArea->verticalScrollBar();
670 sb->setValue(sb->maximum());
671 }
672
showIO(bool show)673 void KatePluginGDBView::showIO(bool show)
674 {
675 if (show) {
676 m_tabWidget->addTab(m_ioView.get(), i18n("IO"));
677 } else {
678 m_tabWidget->removeTab(m_tabWidget->indexOf(m_ioView.get()));
679 }
680 }
681
addOutputText(QString const & text)682 void KatePluginGDBView::addOutputText(QString const &text)
683 {
684 QScrollBar *scrollb = m_outputArea->verticalScrollBar();
685 if (!scrollb) {
686 return;
687 }
688 bool atEnd = (scrollb->value() == scrollb->maximum());
689
690 QTextCursor cursor = m_outputArea->textCursor();
691 if (!cursor.atEnd()) {
692 cursor.movePosition(QTextCursor::End);
693 }
694 cursor.insertText(text);
695
696 if (atEnd) {
697 scrollb->setValue(scrollb->maximum());
698 }
699 }
700
addErrorText(QString const & text)701 void KatePluginGDBView::addErrorText(QString const &text)
702 {
703 m_outputArea->setFontItalic(true);
704 addOutputText(text);
705 m_outputArea->setFontItalic(false);
706 }
707
eventFilter(QObject * obj,QEvent * event)708 bool KatePluginGDBView::eventFilter(QObject *obj, QEvent *event)
709 {
710 if (event->type() == QEvent::KeyPress) {
711 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
712 if ((obj == m_toolView.get()) && (ke->key() == Qt::Key_Escape)) {
713 m_mainWin->hideToolView(m_toolView.get());
714 event->accept();
715 return true;
716 }
717 }
718 return QObject::eventFilter(obj, event);
719 }
720
handleEsc(QEvent * e)721 void KatePluginGDBView::handleEsc(QEvent *e)
722 {
723 if (!m_mainWin || !m_toolView) {
724 return;
725 }
726
727 QKeyEvent *k = static_cast<QKeyEvent *>(e);
728 if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) {
729 if (m_toolView->isVisible()) {
730 m_mainWin->hideToolView(m_toolView.get());
731 }
732 }
733 }
734
displayMessage(const QString & msg,KTextEditor::Message::MessageType level)735 void KatePluginGDBView::displayMessage(const QString &msg, KTextEditor::Message::MessageType level)
736 {
737 KTextEditor::View *kv = m_mainWin->activeView();
738 if (!kv) {
739 return;
740 }
741
742 delete m_infoMessage;
743 m_infoMessage = new KTextEditor::Message(msg, level);
744 m_infoMessage->setWordWrap(true);
745 m_infoMessage->setPosition(KTextEditor::Message::BottomInView);
746 m_infoMessage->setAutoHide(8000);
747 m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate);
748 m_infoMessage->setView(kv);
749 kv->document()->postMessage(m_infoMessage);
750 }
751
752 #include "plugin_kategdb.moc"
753