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"