File indexing completed on 2024-05-05 04:39:53

0001 /*
0002     SPDX-FileCopyrightText: 2003 John Birch <jbb@kdevelop.org>
0003     SPDX-FileCopyrightText: 2006 Vladimir Prus <ghost@cs.msu.su>
0004     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "gdboutputwidget.h"
0010 
0011 #include "dbgglobal.h"
0012 #include "debuggerplugin.h"
0013 #include "debuglog.h"
0014 #include "debugsession.h"
0015 
0016 #include <interfaces/icore.h>
0017 #include <interfaces/idebugcontroller.h>
0018 
0019 #include <KColorScheme>
0020 #include <KHistoryComboBox>
0021 #include <KLocalizedString>
0022 
0023 #include <QApplication>
0024 #include <QClipboard>
0025 #include <QFocusEvent>
0026 #include <QMenu>
0027 #include <QLabel>
0028 #include <QVBoxLayout>
0029 #include <QToolButton>
0030 #include <QToolTip>
0031 #include <QScrollBar>
0032 #include <QScopedPointer>
0033 
0034 using namespace KDevMI::GDB;
0035 
0036 /***************************************************************************/
0037 
0038 GDBOutputWidget::GDBOutputWidget(CppDebuggerPlugin* plugin, QWidget *parent) :
0039     QWidget(parent),
0040     m_userGDBCmdEditor(nullptr),
0041     m_Interrupt(nullptr),
0042     m_gdbView(nullptr),
0043     m_showInternalCommands(false),
0044     m_maxLines(5000)
0045 {
0046     setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-scripts"), windowIcon()));
0047     setWindowTitle(i18nc("@title:window", "GDB Output"));
0048     setWhatsThis(i18nc("@info:whatsthis", "<b>GDB output</b><p>"
0049                     "Shows all GDB commands being executed. "
0050                     "You can also issue any other GDB command while debugging.</p>"));
0051 
0052     m_gdbView = new OutputTextEdit(this);
0053     m_gdbView->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0054     m_gdbView->setReadOnly(true);
0055 
0056     m_userGDBCmdEditor = new KHistoryComboBox (this);
0057 
0058     auto* label = new QLabel(i18nc("@label:listbox", "&GDB command:"), this);
0059     label->setBuddy(m_userGDBCmdEditor);
0060 
0061     m_Interrupt = new QToolButton( this );
0062     m_Interrupt->setIcon ( QIcon::fromTheme( QStringLiteral("media-playback-pause") ) );
0063     m_Interrupt->setToolTip(i18nc("@info:tooltip", "Pause execution of the app to enter GDB commands"));
0064 
0065     auto *topLayout = new QVBoxLayout(this);
0066     topLayout->addWidget(m_gdbView);
0067     topLayout->setStretchFactor(m_gdbView, 1);
0068     topLayout->setContentsMargins(0, 0, 0, 0);
0069 
0070     QBoxLayout *userGDBCmdEntry = new QHBoxLayout();
0071     userGDBCmdEntry->addWidget(label);
0072     userGDBCmdEntry->addWidget(m_userGDBCmdEditor);
0073     userGDBCmdEntry->setStretchFactor(m_userGDBCmdEditor, 1);
0074     userGDBCmdEntry->addWidget(m_Interrupt);
0075     topLayout->addLayout(userGDBCmdEntry);
0076 
0077     setLayout(topLayout);
0078 
0079     slotStateChanged(s_none, s_dbgNotStarted);
0080 
0081     connect(m_userGDBCmdEditor, QOverload<const QString&>::of(&KHistoryComboBox::returnPressed),
0082             this, &GDBOutputWidget::slotGDBCmd);
0083     connect(m_Interrupt, &QToolButton::clicked, this, &GDBOutputWidget::breakInto);
0084 
0085     m_updateTimer.setSingleShot(true);
0086     m_updateTimer.setInterval(100);
0087     connect(&m_updateTimer, &QTimer::timeout,
0088              this, &GDBOutputWidget::flushPending);
0089 
0090     connect(KDevelop::ICore::self()->debugController(), &KDevelop::IDebugController::currentSessionChanged,
0091             this, &GDBOutputWidget::currentSessionChanged);
0092 
0093     connect(plugin, &CppDebuggerPlugin::reset, this, &GDBOutputWidget::clear);
0094     connect(plugin, &CppDebuggerPlugin::raiseDebuggerConsoleViews, this, &GDBOutputWidget::requestRaise);
0095 
0096     currentSessionChanged(KDevelop::ICore::self()->debugController()->currentSession());
0097 
0098 //     TODO Port to KF5
0099 //     connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()),
0100 //             this, SLOT(updateColors()));
0101     updateColors();
0102 
0103 }
0104 
0105 void GDBOutputWidget::updateColors()
0106 {
0107     KColorScheme scheme(QPalette::Active);
0108     m_gdbColor = scheme.foreground(KColorScheme::LinkText).color();
0109     m_errorColor = scheme.foreground(KColorScheme::NegativeText).color();
0110 }
0111 
0112 void GDBOutputWidget::currentSessionChanged(KDevelop::IDebugSession* s)
0113 {
0114     if (!s)
0115         return;
0116 
0117     auto *session = qobject_cast<DebugSession*>(s);
0118     if (!session)
0119         return;
0120 
0121      connect(this, &GDBOutputWidget::userGDBCmd,
0122              session, &DebugSession::addUserCommand);
0123      connect(this, &GDBOutputWidget::breakInto,
0124              session, &DebugSession::interruptDebugger);
0125 
0126      connect(session, &DebugSession::debuggerInternalCommandOutput,
0127              this, &GDBOutputWidget::slotInternalCommandStdout);
0128      connect(session, &DebugSession::debuggerUserCommandOutput,
0129              this, &GDBOutputWidget::slotUserCommandStdout);
0130      // debugger internal output, treat it as an internal command output
0131      connect(session, &DebugSession::debuggerInternalOutput,
0132              this, &GDBOutputWidget::slotInternalCommandStdout);
0133 
0134      connect(session, &DebugSession::debuggerStateChanged,
0135              this, &GDBOutputWidget::slotStateChanged);
0136 
0137      slotStateChanged(s_none, session->debuggerState());
0138 }
0139 
0140 
0141 /***************************************************************************/
0142 
0143 GDBOutputWidget::~GDBOutputWidget()
0144 {
0145     delete m_gdbView;
0146     delete m_userGDBCmdEditor;
0147 }
0148 
0149 /***************************************************************************/
0150 
0151 void GDBOutputWidget::clear()
0152 {
0153     if (m_gdbView)
0154         m_gdbView->clear();
0155 
0156     m_userCommands_.clear();
0157     m_allCommands.clear();
0158 }
0159 
0160 /***************************************************************************/
0161 
0162 void GDBOutputWidget::slotInternalCommandStdout(const QString& line)
0163 {
0164     newStdoutLine(line, true);
0165 }
0166 
0167 void GDBOutputWidget::slotUserCommandStdout(const QString& line)
0168 {
0169     qCDebug(DEBUGGERGDB) << "User command stdout: " << line;
0170     newStdoutLine(line, false);
0171 }
0172 
0173 namespace {
0174     QString colorify(QString text, const QColor& color)
0175     {
0176         if (text.endsWith(QLatin1Char('\n'))) {
0177             text.chop(1);
0178         }
0179         text = QLatin1String("<font color=\"") + color.name() + QLatin1String("\">") + text + QLatin1String("</font><br>");
0180         return text;
0181     }
0182 }
0183 
0184 
0185 void GDBOutputWidget::newStdoutLine(const QString& line,
0186                                     bool internal)
0187 {
0188     QString s = line.toHtmlEscaped();
0189     if (s.startsWith(QLatin1String("(gdb)")))
0190     {
0191         s = colorify(s, m_gdbColor);
0192     }
0193     else
0194         s.replace(QLatin1Char('\n'), QLatin1String("<br>"));
0195 
0196     m_allCommands.append(s);
0197     m_allCommandsRaw.append(line);
0198     trimList(m_allCommands, m_maxLines);
0199     trimList(m_allCommandsRaw, m_maxLines);
0200 
0201     if (!internal)
0202     {
0203         m_userCommands_.append(s);
0204         m_userCommandsRaw.append(line);
0205         trimList(m_userCommands_, m_maxLines);
0206         trimList(m_userCommandsRaw, m_maxLines);
0207     }
0208 
0209     if (!internal || m_showInternalCommands)
0210         showLine(s);
0211 }
0212 
0213 
0214 void GDBOutputWidget::showLine(const QString& line)
0215 {
0216     m_pendingOutput += line;
0217 
0218     // To improve performance, we update the view after some delay.
0219     if (!m_updateTimer.isActive())
0220     {
0221         m_updateTimer.start();
0222     }
0223 }
0224 
0225 void GDBOutputWidget::trimList(QStringList& l, int max_size)
0226 {
0227     int length = l.count();
0228     if (length > max_size)
0229     {
0230         for(int to_delete = length - max_size; to_delete; --to_delete)
0231         {
0232             l.erase(l.begin());
0233         }
0234     }
0235 }
0236 
0237 void GDBOutputWidget::setShowInternalCommands(bool show)
0238 {
0239     if (show != m_showInternalCommands)
0240     {
0241         m_showInternalCommands = show;
0242 
0243         // Set of strings to show changes, text edit still has old
0244         // set. Refresh.
0245         m_gdbView->clear();
0246         const QStringList& newList =
0247             m_showInternalCommands ? m_allCommands : m_userCommands_;
0248 
0249         for (auto& line : newList) {
0250             // Note that color formatting is already applied to 'line'.
0251             showLine(line);
0252         }
0253     }
0254 }
0255 
0256 /***************************************************************************/
0257 
0258 void GDBOutputWidget::slotReceivedStderr(const char* line)
0259 {
0260     const auto lineEncoded = QString::fromUtf8(line);
0261     const auto colored = colorify(lineEncoded.toHtmlEscaped(), m_errorColor);
0262     // Errors are shown inside user commands too.
0263     m_allCommands.append(colored);
0264     trimList(m_allCommands, m_maxLines);
0265     m_userCommands_.append(colored);
0266     trimList(m_userCommands_, m_maxLines);
0267 
0268     m_allCommandsRaw.append(lineEncoded);
0269     trimList(m_allCommandsRaw, m_maxLines);
0270     m_userCommandsRaw.append(lineEncoded);
0271     trimList(m_userCommandsRaw, m_maxLines);
0272 
0273     showLine(colored);
0274 }
0275 
0276 /***************************************************************************/
0277 
0278 void GDBOutputWidget::slotGDBCmd()
0279 {
0280     QString GDBCmd(m_userGDBCmdEditor->currentText());
0281     if (!GDBCmd.isEmpty())
0282     {
0283         m_userGDBCmdEditor->addToHistory(GDBCmd);
0284         m_userGDBCmdEditor->clearEditText();
0285         emit userGDBCmd(GDBCmd);
0286     }
0287 }
0288 
0289 void GDBOutputWidget::flushPending()
0290 {
0291     m_gdbView->setUpdatesEnabled(false);
0292 
0293     // QTextEdit adds newline after paragraph automatically.
0294     // So, remove trailing newline to avoid double newlines.
0295     if (m_pendingOutput.endsWith(QLatin1Char('\n')))
0296         m_pendingOutput.chop(1);
0297     Q_ASSERT(!m_pendingOutput.endsWith(QLatin1Char('\n')));
0298 
0299     QTextDocument *document = m_gdbView->document();
0300     QTextCursor cursor(document);
0301     cursor.movePosition(QTextCursor::End);
0302     cursor.insertHtml(m_pendingOutput);
0303     m_pendingOutput.clear();
0304 
0305     m_gdbView->verticalScrollBar()->setValue(m_gdbView->verticalScrollBar()->maximum());
0306     m_gdbView->setUpdatesEnabled(true);
0307     m_gdbView->update();
0308     if (m_cmdEditorHadFocus) {
0309         m_userGDBCmdEditor->setFocus();
0310     }
0311 }
0312 
0313 /***************************************************************************/
0314 
0315 void GDBOutputWidget::slotStateChanged(KDevMI::DBGStateFlags oldStatus, KDevMI::DBGStateFlags newStatus)
0316 {
0317     Q_UNUSED(oldStatus)
0318     if (newStatus & s_dbgNotStarted)
0319     {
0320         m_Interrupt->setEnabled(false);
0321         m_userGDBCmdEditor->setEnabled(false);
0322         return;
0323     }
0324     else
0325     {
0326         m_Interrupt->setEnabled(true);
0327     }
0328 
0329     if (newStatus & s_dbgBusy)
0330     {
0331         if (m_userGDBCmdEditor->isEnabled()) {
0332             m_cmdEditorHadFocus = m_userGDBCmdEditor->hasFocus();
0333         }
0334         m_userGDBCmdEditor->setEnabled(false);
0335     }
0336     else
0337     {
0338         m_userGDBCmdEditor->setEnabled(true);
0339     }
0340 }
0341 
0342 /***************************************************************************/
0343 
0344 void GDBOutputWidget::focusInEvent(QFocusEvent* /*e*/)
0345 {
0346     m_gdbView->verticalScrollBar()->setValue(m_gdbView->verticalScrollBar()->maximum());
0347     m_userGDBCmdEditor->setFocus();
0348 }
0349 
0350 void GDBOutputWidget::savePartialProjectSession()
0351 {
0352     KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger");
0353 
0354     config.writeEntry("showInternalCommands", m_showInternalCommands);
0355 }
0356 
0357 void GDBOutputWidget::restorePartialProjectSession()
0358 {
0359     KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger");
0360 
0361     m_showInternalCommands = config.readEntry("showInternalCommands", false);
0362 }
0363 
0364 
0365 void GDBOutputWidget::contextMenuEvent(QContextMenuEvent * e)
0366 {
0367     QScopedPointer<QMenu> popup(new QMenu(this));
0368 
0369     QAction* action = popup->addAction(i18nc("@action:inmenu", "Show Internal Commands"),
0370                                this,
0371                                SLOT(toggleShowInternalCommands()));
0372 
0373     action->setCheckable(true);
0374     action->setChecked(m_showInternalCommands);
0375     action->setWhatsThis(i18nc("@info:tooltip",
0376             "Controls if commands issued internally by KDevelop "
0377             "will be shown or not.<br>"
0378             "This option will affect only future commands, it will not "
0379             "add or remove already issued commands from the view."));
0380 
0381     popup->addAction(i18nc("@action:inmenu", "Copy All"),
0382                       this,
0383                       SLOT(copyAll()));
0384 
0385     popup->exec(e->globalPos());
0386 }
0387 
0388 void GDBOutputWidget::copyAll()
0389 {
0390     /* See comments for allCommandRaw_ for explanations of
0391        this complex logic, as opposed to calling text(). */
0392     const QStringList& raw = m_showInternalCommands ?
0393         m_allCommandsRaw : m_userCommandsRaw;
0394     const QString text = raw.join(QString());
0395 
0396     // Make sure the text is pastable both with Ctrl-C and with
0397     // middle click.
0398     QApplication::clipboard()->setText(text, QClipboard::Clipboard);
0399     QApplication::clipboard()->setText(text, QClipboard::Selection);
0400 }
0401 
0402 void GDBOutputWidget::toggleShowInternalCommands()
0403 {
0404     setShowInternalCommands(!m_showInternalCommands);
0405 }
0406 
0407 
0408 OutputTextEdit::OutputTextEdit(GDBOutputWidget * parent)
0409     : QPlainTextEdit(parent)
0410 {
0411 }
0412 
0413 void OutputTextEdit::contextMenuEvent(QContextMenuEvent * event)
0414 {
0415     QScopedPointer<QMenu> popup(createStandardContextMenu());
0416 
0417     QAction* action = popup->addAction(i18nc("@action:inmenu", "Show Internal Commands"),
0418                                parent(),
0419                                SLOT(toggleShowInternalCommands()));
0420 
0421     action->setCheckable(true);
0422     action->setChecked(static_cast<GDBOutputWidget*>(parent())->showInternalCommands());
0423     action->setWhatsThis(i18nc("@info:tooltip",
0424             "Controls if commands issued internally by KDevelop "
0425             "will be shown or not.<br>"
0426             "This option will affect only future commands, it will not "
0427             "add or remove already issued commands from the view."));
0428 
0429     popup->exec(event->globalPos());
0430 }
0431 
0432 bool GDBOutputWidget::showInternalCommands() const
0433 {
0434     return m_showInternalCommands;
0435 }
0436 
0437 #include "moc_gdboutputwidget.cpp"