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