File indexing completed on 2024-05-12 04:39:46
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 SPDX-FileCopyrightText: 2016 Aetf <aetf@unlimitedcodeworks.xyz> 0006 0007 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0008 */ 0009 0010 #include "debuggerconsoleview.h" 0011 0012 #include "debuglog.h" 0013 #include "midebuggerplugin.h" 0014 #include "midebugsession.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 <QAction> 0024 #include <QEvent> 0025 #include <QHBoxLayout> 0026 #include <QIcon> 0027 #include <QLabel> 0028 #include <QMenu> 0029 #include <QScopedPointer> 0030 #include <QScrollBar> 0031 #include <QStyle> 0032 #include <QTextEdit> 0033 #include <QToolBar> 0034 #include <QVBoxLayout> 0035 #include <QPoint> 0036 0037 using namespace KDevMI; 0038 0039 DebuggerConsoleView::DebuggerConsoleView(MIDebuggerPlugin *plugin, QWidget *parent) 0040 : QWidget(parent) 0041 , m_repeatLastCommand(false) 0042 , m_showInternalCommands(false) 0043 , m_cmdEditorHadFocus(false) 0044 , m_maxLines(5000) 0045 { 0046 setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-scripts"))); 0047 setWindowTitle(i18nc("@title:window", "Debugger Console")); 0048 setWhatsThis(i18nc("@info:whatsthis", 0049 "<b>Debugger Console</b><p>" 0050 "Shows all debugger commands being executed. " 0051 "You can also issue any other debugger command while debugging.</p>")); 0052 0053 setupUi(); 0054 0055 m_actRepeat = new QAction(QIcon::fromTheme(QStringLiteral("edit-redo")), 0056 QString(), 0057 this); 0058 m_actRepeat->setToolTip(i18nc("@info:tooltip", "Repeat last command when hit Return")); 0059 m_actRepeat->setCheckable(true); 0060 m_actRepeat->setChecked(m_repeatLastCommand); 0061 connect(m_actRepeat, &QAction::toggled, this, &DebuggerConsoleView::toggleRepeat); 0062 m_toolBar->insertAction(m_actCmdEditor, m_actRepeat); 0063 0064 m_actInterrupt = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-pause")), 0065 QString(), 0066 this); 0067 m_actInterrupt->setToolTip(i18nc("@info:tooltip", "Pause execution of the app to enter debugger commands")); 0068 connect(m_actInterrupt, &QAction::triggered, this, &DebuggerConsoleView::interruptDebugger); 0069 m_toolBar->insertAction(m_actCmdEditor, m_actInterrupt); 0070 setShowInterrupt(true); 0071 0072 m_actShowInternal = new QAction(i18nc("@action", "Show Internal Commands"), this); 0073 m_actShowInternal->setCheckable(true); 0074 m_actShowInternal->setChecked(m_showInternalCommands); 0075 m_actShowInternal->setWhatsThis(i18nc("@info:whatsthis", 0076 "Controls if commands issued internally by KDevelop " 0077 "will be shown or not.<br>" 0078 "This option will affect only future commands, it will not " 0079 "add or remove already issued commands from the view.")); 0080 connect(m_actShowInternal, &QAction::toggled, 0081 this, &DebuggerConsoleView::toggleShowInternalCommands); 0082 0083 handleDebuggerStateChange(s_none, s_dbgNotStarted); 0084 0085 m_updateTimer.setSingleShot(true); 0086 m_updateTimer.setInterval(100); 0087 connect(&m_updateTimer, &QTimer::timeout, this, &DebuggerConsoleView::flushPending); 0088 0089 connect(plugin->core()->debugController(), &KDevelop::IDebugController::currentSessionChanged, 0090 this, &DebuggerConsoleView::handleSessionChanged); 0091 0092 connect(plugin, &MIDebuggerPlugin::reset, this, &DebuggerConsoleView::clear); 0093 connect(plugin, &MIDebuggerPlugin::raiseDebuggerConsoleViews, 0094 this, &DebuggerConsoleView::requestRaise); 0095 0096 handleSessionChanged(plugin->core()->debugController()->currentSession()); 0097 0098 updateColors(); 0099 } 0100 0101 void DebuggerConsoleView::changeEvent(QEvent *event) 0102 { 0103 if (event->type() == QEvent::PaletteChange) { 0104 updateColors(); 0105 } 0106 } 0107 0108 void DebuggerConsoleView::updateColors() 0109 { 0110 KColorScheme scheme(QPalette::Active); 0111 m_stdColor = scheme.foreground(KColorScheme::LinkText).color(); 0112 m_errorColor = scheme.foreground(KColorScheme::NegativeText).color(); 0113 } 0114 0115 void DebuggerConsoleView::setupUi() 0116 { 0117 setupToolBar(); 0118 0119 m_textView = new QTextEdit; 0120 m_textView->setReadOnly(true); 0121 m_textView->setContextMenuPolicy(Qt::CustomContextMenu); 0122 connect(m_textView, &QTextEdit::customContextMenuRequested, 0123 this, &DebuggerConsoleView::showContextMenu); 0124 0125 auto vbox = new QVBoxLayout; 0126 vbox->setContentsMargins(0, 0, 0, 0); 0127 vbox->addWidget(m_textView); 0128 vbox->addWidget(m_toolBar); 0129 0130 setLayout(vbox); 0131 0132 m_cmdEditor = new KHistoryComboBox(this); 0133 m_cmdEditor->setDuplicatesEnabled(false); 0134 connect(m_cmdEditor, QOverload<const QString&>::of(&KHistoryComboBox::returnPressed), 0135 this, &DebuggerConsoleView::trySendCommand); 0136 0137 auto label = new QLabel(i18nc("@label:listbox", "&Command:"), this); 0138 label->setBuddy(m_cmdEditor); 0139 0140 auto hbox = new QHBoxLayout; 0141 hbox->addWidget(label); 0142 hbox->addWidget(m_cmdEditor); 0143 hbox->setStretchFactor(m_cmdEditor, 1); 0144 hbox->setContentsMargins(0, 0, 0, 0); 0145 0146 auto cmdEditor = new QWidget(this); 0147 cmdEditor->setLayout(hbox); 0148 m_actCmdEditor = m_toolBar->addWidget(cmdEditor); 0149 } 0150 0151 void DebuggerConsoleView::setupToolBar() 0152 { 0153 m_toolBar = new QToolBar(this); 0154 int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize); 0155 m_toolBar->setIconSize(QSize(iconSize, iconSize)); 0156 m_toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); 0157 m_toolBar->setFloatable(false); 0158 m_toolBar->setMovable(false); 0159 m_toolBar->setWindowTitle(i18nc("@title:window", "%1 Command Bar", windowTitle())); 0160 m_toolBar->setContextMenuPolicy(Qt::PreventContextMenu); 0161 0162 // remove margins, to make command editor nicely aligned with the output 0163 m_toolBar->layout()->setContentsMargins(0, 0, 0, 0); 0164 } 0165 0166 void DebuggerConsoleView::focusInEvent(QFocusEvent*) 0167 { 0168 m_textView->verticalScrollBar()->setValue(m_textView->verticalScrollBar()->maximum()); 0169 m_cmdEditor->setFocus(); 0170 } 0171 0172 DebuggerConsoleView::~DebuggerConsoleView() 0173 { 0174 } 0175 0176 void DebuggerConsoleView::setShowInterrupt(bool enable) 0177 { 0178 m_actInterrupt->setVisible(enable); 0179 } 0180 0181 void DebuggerConsoleView::setReplacePrompt(const QString& prompt) 0182 { 0183 m_alterPrompt = prompt; 0184 } 0185 0186 void DebuggerConsoleView::setShowInternalCommands(bool enable) 0187 { 0188 if (enable != m_showInternalCommands) 0189 { 0190 m_showInternalCommands = enable; 0191 0192 // Set of strings to show changes, text edit still has old 0193 // set. Refresh. 0194 m_textView->clear(); 0195 QStringList& newList = m_showInternalCommands ? m_allOutput : m_userOutput; 0196 0197 for (const auto &line : newList) { 0198 // Note that color formatting is already applied to 'line'. 0199 appendLine(line); 0200 } 0201 } 0202 } 0203 0204 void DebuggerConsoleView::showContextMenu(const QPoint &pos) 0205 { 0206 // FIXME: QTextEdit::createStandardContextMenu takes position in document coordinates 0207 // while pos is in QTextEdit::viewport coordinates. 0208 // Seems not a big issue currently as menu content seems position independent, but still better fix 0209 QScopedPointer<QMenu> popup(m_textView->createStandardContextMenu(pos)); 0210 0211 popup->addSeparator(); 0212 popup->addAction(m_actShowInternal); 0213 0214 popup->exec(m_textView->viewport()->mapToGlobal(pos)); 0215 } 0216 0217 void DebuggerConsoleView::toggleRepeat(bool checked) 0218 { 0219 m_repeatLastCommand = checked; 0220 } 0221 0222 void DebuggerConsoleView::toggleShowInternalCommands(bool checked) 0223 { 0224 setShowInternalCommands(checked); 0225 } 0226 0227 void DebuggerConsoleView::appendLine(const QString& line) 0228 { 0229 m_pendingOutput += line; 0230 0231 // To improve performance, we update the view after some delay. 0232 if (!m_updateTimer.isActive()) 0233 { 0234 m_updateTimer.start(); 0235 } 0236 } 0237 0238 void DebuggerConsoleView::flushPending() 0239 { 0240 m_textView->setUpdatesEnabled(false); 0241 0242 QTextDocument *document = m_textView->document(); 0243 QTextCursor cursor(document); 0244 cursor.movePosition(QTextCursor::End); 0245 cursor.insertHtml(m_pendingOutput); 0246 m_pendingOutput.clear(); 0247 0248 m_textView->verticalScrollBar()->setValue(m_textView->verticalScrollBar()->maximum()); 0249 m_textView->setUpdatesEnabled(true); 0250 m_textView->update(); 0251 if (m_cmdEditorHadFocus) { 0252 m_cmdEditor->setFocus(); 0253 } 0254 } 0255 0256 void DebuggerConsoleView::clear() 0257 { 0258 if (m_textView) 0259 m_textView->clear(); 0260 0261 if (m_cmdEditor) 0262 m_cmdEditor->clear(); 0263 0264 m_userOutput.clear(); 0265 m_allOutput.clear(); 0266 } 0267 0268 void DebuggerConsoleView::handleDebuggerStateChange(DBGStateFlags, DBGStateFlags newStatus) 0269 { 0270 if (newStatus & s_dbgNotStarted) { 0271 m_actInterrupt->setEnabled(false); 0272 m_cmdEditor->setEnabled(false); 0273 return; 0274 } else { 0275 m_actInterrupt->setEnabled(true); 0276 } 0277 0278 if (newStatus & s_dbgBusy) { 0279 if (m_cmdEditor->isEnabled()) { 0280 m_cmdEditorHadFocus = m_cmdEditor->hasFocus(); 0281 } 0282 m_cmdEditor->setEnabled(false); 0283 } else { 0284 m_cmdEditor->setEnabled(true); 0285 } 0286 } 0287 0288 QString DebuggerConsoleView::toHtmlEscaped(QString text) 0289 { 0290 text = text.toHtmlEscaped(); 0291 0292 text.replace(QLatin1Char('\n'), QLatin1String("<br>")); 0293 return text; 0294 } 0295 0296 0297 QString DebuggerConsoleView::colorify(QString text, const QColor& color) 0298 { 0299 text = QLatin1String("<font color=\"") + color.name() + QLatin1String("\">") + text + QLatin1String("</font>"); 0300 return text; 0301 } 0302 0303 void DebuggerConsoleView::receivedInternalCommandStdout(const QString& line) 0304 { 0305 receivedStdout(line, true); 0306 } 0307 0308 void DebuggerConsoleView::receivedUserCommandStdout(const QString& line) 0309 { 0310 receivedStdout(line, false); 0311 } 0312 0313 void DebuggerConsoleView::receivedStdout(const QString& line, bool internal) 0314 { 0315 QString colored = toHtmlEscaped(line); 0316 if (colored.startsWith(QLatin1String("(gdb)"))) { 0317 if (!m_alterPrompt.isEmpty()) { 0318 colored.replace(0, 5, m_alterPrompt); 0319 } 0320 colored = colorify(colored, m_stdColor); 0321 } 0322 0323 m_allOutput.append(colored); 0324 trimList(m_allOutput, m_maxLines); 0325 0326 if (!internal) { 0327 m_userOutput.append(colored); 0328 trimList(m_userOutput, m_maxLines); 0329 } 0330 0331 if (!internal || m_showInternalCommands) 0332 appendLine(colored); 0333 } 0334 0335 void DebuggerConsoleView::receivedStderr(const QString& line) 0336 { 0337 QString colored = toHtmlEscaped(line); 0338 colored = colorify(colored, m_errorColor); 0339 0340 // Errors are shown inside user commands too. 0341 m_allOutput.append(colored); 0342 trimList(m_allOutput, m_maxLines); 0343 0344 m_userOutput.append(colored); 0345 trimList(m_userOutput, m_maxLines); 0346 0347 appendLine(colored); 0348 } 0349 0350 void DebuggerConsoleView::trimList(QStringList& l, int max_size) 0351 { 0352 int length = l.count(); 0353 if (length > max_size) 0354 { 0355 for(int to_delete = length - max_size; to_delete; --to_delete) 0356 { 0357 l.erase(l.begin()); 0358 } 0359 } 0360 } 0361 0362 void DebuggerConsoleView::trySendCommand(QString cmd) 0363 { 0364 if (m_repeatLastCommand && cmd.isEmpty()) { 0365 cmd = m_cmdEditor->historyItems().last(); 0366 } 0367 if (!cmd.isEmpty()) 0368 { 0369 m_cmdEditor->addToHistory(cmd); 0370 m_cmdEditor->clearEditText(); 0371 0372 emit sendCommand(cmd); 0373 } 0374 } 0375 0376 void DebuggerConsoleView::handleSessionChanged(KDevelop::IDebugSession* s) 0377 { 0378 auto *session = qobject_cast<MIDebugSession*>(s); 0379 if (!session) return; 0380 0381 connect(this, &DebuggerConsoleView::sendCommand, 0382 session, &MIDebugSession::addUserCommand); 0383 connect(this, &DebuggerConsoleView::interruptDebugger, 0384 session, &MIDebugSession::interruptDebugger); 0385 0386 connect(session, &MIDebugSession::debuggerInternalCommandOutput, 0387 this, &DebuggerConsoleView::receivedInternalCommandStdout); 0388 connect(session, &MIDebugSession::debuggerUserCommandOutput, 0389 this, &DebuggerConsoleView::receivedUserCommandStdout); 0390 connect(session, &MIDebugSession::debuggerInternalOutput, 0391 this, &DebuggerConsoleView::receivedStderr); 0392 0393 connect(session, &MIDebugSession::debuggerStateChanged, 0394 this, &DebuggerConsoleView::handleDebuggerStateChange); 0395 0396 handleDebuggerStateChange(s_none, session->debuggerState()); 0397 } 0398 0399 #include "moc_debuggerconsoleview.cpp"