Commit 8e25f4ee authored by Peifeng Yu's avatar Peifeng Yu
Browse files

Reworked LLDB Debugger Console

parent b90eb8c2
......@@ -18,6 +18,8 @@ set(debuggercommon_SRCS
mivariable.cpp
stringhelpers.cpp
stty.cpp
# tool views
widgets/debuggerconsoleview.cpp
)
if(KF5SysGuard_FOUND)
list(APPEND debuggercommon_SRCS
......@@ -27,6 +29,7 @@ endif()
ki18n_wrap_ui(debuggercommon_SRCS
dialogs/selectcoredialog.ui
widgets/debuggerconsoleview.ui
)
# Use old behavior (ignore the visibility properties for static libraries, object
......@@ -47,6 +50,7 @@ target_link_libraries(kdevdebuggercommon
PRIVATE
Qt5::Core
Qt5::Gui
Qt5::Widgets
KDev::Util
KDev::Language
)
......
......@@ -141,11 +141,13 @@ public:
/* At present, some debugger widgets (e.g. breakpoint) contain actions so that shortcuts
work, but they don't need any toolbar. So, suppress toolbar action. */
/*
QList<QAction*> toolBarActions(QWidget* viewWidget) const override
{
Q_UNUSED(viewWidget);
return QList<QAction*>();
}
*/
private:
Plugin * m_plugin;
......
/*
* Debugger Console View
* Copyright 2016 Aetf <aetf@unlimitedcodeworks.xyz>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "debuggerconsoleview.h"
#include "debuglog.h"
#include "midebuggerplugin.h"
#include "midebugsession.h"
#include <interfaces/icore.h>
#include <interfaces/idebugcontroller.h>
#include <KColorScheme>
#include <KHistoryComboBox>
#include <KLocalizedString>
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QMenu>
#include <QScopedPointer>
#include <QScrollBar>
#include <QStyle>
#include <QTextEdit>
#include <QToolBar>
#include <QVBoxLayout>
#include <QWidget>
using namespace KDevMI;
DebuggerConsoleView::DebuggerConsoleView(MIDebuggerPlugin *plugin, QWidget *parent)
: QWidget(parent)
, m_repeatLastCommand(false)
, m_showInternalCommands(false)
, m_cmdEditorHadFocus(false)
, m_maxLines(5000)
{
setWindowIcon(QIcon::fromTheme("dialog-scripts"));
setWindowTitle(i18n("Debugger Console"));
setWhatsThis(i18n("<b>Debugger Console</b><p>"
"Shows all debugger commands being executed. "
"You can also issue any other debugger command while debugging.</p>"));
setupUi();
m_actRepeat = new QAction(QIcon::fromTheme("edit-redo"),
i18n("Repeat last command when hit Return"),
this);
m_actRepeat->setCheckable(true);
m_actRepeat->setChecked(m_repeatLastCommand);
connect(m_actRepeat, &QAction::toggled, this, &DebuggerConsoleView::toggleRepeat);
m_toolBar->insertAction(m_actCmdEditor, m_actRepeat);
m_actInterrupt = new QAction(QIcon::fromTheme("media-playback-pause"),
i18n("Pause execution of the app to enter gdb commands"),
this);
connect(m_actInterrupt, &QAction::triggered, this, &DebuggerConsoleView::interruptDebugger);
m_toolBar->insertAction(m_actCmdEditor, m_actInterrupt);
setShowInterrupt(true);
m_actShowInternal = new QAction(i18n("Show Internal Commands"));
m_actShowInternal->setCheckable(true);
m_actShowInternal->setChecked(m_showInternalCommands);
m_actShowInternal->setWhatsThis(i18n(
"Controls if commands issued internally by KDevelop "
"will be shown or not.<br>"
"This option will affect only future commands, it will not "
"add or remove already issued commands from the view."));
connect(m_actShowInternal, &QAction::toggled,
this, &DebuggerConsoleView::toggleShowInternalCommands);
handleDebuggerStateChange(s_none, s_dbgNotStarted);
m_updateTimer.setSingleShot(true);
connect(&m_updateTimer, &QTimer::timeout, this, &DebuggerConsoleView::flushPending);
connect(plugin->core()->debugController(), &KDevelop::IDebugController::currentSessionChanged,
this, &DebuggerConsoleView::handleSessionChanged);
connect(plugin, &MIDebuggerPlugin::reset, this, &DebuggerConsoleView::clear);
connect(plugin, &MIDebuggerPlugin::raiseDebuggerConsoleViews,
this, &DebuggerConsoleView::requestRaise);
handleSessionChanged(plugin->core()->debugController()->currentSession());
// tap to QApplication object for color palette changes
//QCoreApplication::instance()->installEventFilter(this);
}
bool DebuggerConsoleView::eventFilter(QObject *, QEvent *event)
{
if (event->type() == QEvent::ApplicationPaletteChange) {
updateColors();
}
return false;
}
void DebuggerConsoleView::updateColors()
{
KColorScheme scheme(QPalette::Active);
m_stdColor = scheme.foreground(KColorScheme::LinkText).color();
m_errorColor = scheme.foreground(KColorScheme::NegativeText).color();
}
void DebuggerConsoleView::setupUi()
{
setupToolBar();
m_textView = new QTextEdit;
m_textView->setReadOnly(true);
m_textView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_textView, &QTextEdit::customContextMenuRequested,
this, &DebuggerConsoleView::showContextMenu);
auto vbox = new QVBoxLayout;
vbox->addWidget(m_textView);
vbox->addWidget(m_toolBar);
setLayout(vbox);
m_cmdEditor = new KHistoryComboBox(this);
m_cmdEditor->setDuplicatesEnabled(false);
connect(m_cmdEditor,
static_cast<void(KHistoryComboBox::*)(const QString&)>(&KHistoryComboBox::returnPressed),
this, &DebuggerConsoleView::trySendCommand);
auto label = new QLabel(i18n("&Command:"), this);
label->setBuddy(m_cmdEditor);
auto hbox = new QHBoxLayout;
hbox->addWidget(label);
hbox->addWidget(m_cmdEditor);
hbox->setStretchFactor(m_cmdEditor, 1);
hbox->setContentsMargins(0, 0, 0, 0);
auto cmdEditor = new QWidget(this);
cmdEditor->setLayout(hbox);
m_actCmdEditor = m_toolBar->addWidget(cmdEditor);
}
void DebuggerConsoleView::setupToolBar()
{
m_toolBar = new QToolBar(this);
int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
m_toolBar->setIconSize(QSize(iconSize, iconSize));
m_toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_toolBar->setFloatable(false);
m_toolBar->setMovable(false);
m_toolBar->setWindowTitle(i18n("%1 Command Bar", windowTitle()));
m_toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
// remove margins, to make command editor nicely aligned with the output
m_toolBar->layout()->setContentsMargins(0, 0, 0, 0);
}
void DebuggerConsoleView::focusInEvent(QFocusEvent*)
{
m_textView->verticalScrollBar()->setValue(m_textView->verticalScrollBar()->maximum());
m_cmdEditor->setFocus();
}
DebuggerConsoleView::~DebuggerConsoleView()
{
}
void DebuggerConsoleView::setShowInterrupt(bool enable)
{
m_actInterrupt->setVisible(enable);
}
void DebuggerConsoleView::setShowInternalCommands(bool enable)
{
if (enable != m_showInternalCommands)
{
m_showInternalCommands = enable;
// Set of strings to show changes, text edit still has old
// set. Refresh.
m_textView->clear();
QStringList& newList = m_showInternalCommands ? m_allOutput : m_userOutput;
for (const auto &line : newList) {
// Note that color formatting is already applied to 'line'.
appendLine(line);
}
}
}
void DebuggerConsoleView::showContextMenu(const QPoint &pos)
{
QScopedPointer<QMenu> popup(m_textView->createStandardContextMenu(pos));
popup->addSeparator();
popup->addAction(m_actShowInternal);
popup->exec(m_textView->mapToGlobal(pos));
}
void DebuggerConsoleView::toggleRepeat(bool checked)
{
m_repeatLastCommand = checked;
}
void DebuggerConsoleView::toggleShowInternalCommands(bool checked)
{
setShowInternalCommands(checked);
}
void DebuggerConsoleView::appendLine(const QString& line)
{
m_pendingOutput += line;
// To improve performance, we update the view after some delay.
if (!m_updateTimer.isActive())
{
m_updateTimer.start(100);
}
}
void DebuggerConsoleView::flushPending()
{
m_textView->setUpdatesEnabled(false);
// QTextEdit adds newline after paragraph automatically.
// So, remove trailing newline to avoid double newlines.
if (m_pendingOutput.endsWith('\n'))
m_pendingOutput.remove(m_pendingOutput.length()-1, 1);
Q_ASSERT(!m_pendingOutput.endsWith('\n'));
QTextDocument *document = m_textView->document();
QTextCursor cursor(document);
cursor.movePosition(QTextCursor::End);
cursor.insertHtml(m_pendingOutput);
m_pendingOutput.clear();
m_textView->verticalScrollBar()->setValue(m_textView->verticalScrollBar()->maximum());
m_textView->setUpdatesEnabled(true);
m_textView->update();
if (m_cmdEditorHadFocus) {
m_cmdEditor->setFocus();
}
}
void DebuggerConsoleView::clear()
{
if (m_textView)
m_textView->clear();
if (m_cmdEditor)
m_cmdEditor->clear();
m_userOutput.clear();
m_allOutput.clear();
}
void DebuggerConsoleView::handleDebuggerStateChange(DBGStateFlags, DBGStateFlags newStatus)
{
if (newStatus & s_dbgNotStarted) {
m_actInterrupt->setEnabled(false);
m_cmdEditor->setEnabled(false);
return;
} else {
m_actInterrupt->setEnabled(true);
}
if (newStatus & s_dbgBusy) {
if (m_cmdEditor->isEnabled()) {
m_cmdEditorHadFocus = m_cmdEditor->hasFocus();
}
m_cmdEditor->setEnabled(false);
} else {
m_cmdEditor->setEnabled(true);
}
}
QString DebuggerConsoleView::colorify(QString text, const QColor& color)
{
// Make sure the newline is at the end of the newly-added
// string. This is so that we can always correctly remove
// newline inside 'flushPending'.
if (!text.endsWith('\n'))
text.append('\n');
if (text.endsWith('\n')) {
text.remove(text.length()-1, 1);
}
text = "<font color=\"" + color.name() + "\">" + text + "</font><br>";
return text;
}
void DebuggerConsoleView::receivedInternalCommandStdout(const QString& line)
{
receivedStdout(line, true);
}
void DebuggerConsoleView::receivedUserCommandStdout(const QString& line)
{
receivedStdout(line, false);
}
void DebuggerConsoleView::receivedStdout(const QString& line, bool internal)
{
QString colored = line.toHtmlEscaped();
if (colored.startsWith("(gdb)")) {
colored = colorify(colored, m_stdColor);
} else {
colored.replace('\n', "<br>");
}
m_allOutput.append(colored);
trimList(m_allOutput, m_maxLines);
if (!internal) {
m_userOutput.append(colored);
trimList(m_userOutput, m_maxLines);
}
if (!internal || m_showInternalCommands)
appendLine(colored);
}
void DebuggerConsoleView::receivedStderr(const QString& line)
{
QString colored = colorify(line.toHtmlEscaped(), m_errorColor);
// Errors are shown inside user commands too.
m_allOutput.append(colored);
trimList(m_allOutput, m_maxLines);
m_userOutput.append(colored);
trimList(m_userOutput, m_maxLines);
appendLine(colored);
}
void DebuggerConsoleView::trimList(QStringList& l, int max_size)
{
int length = l.count();
if (length > max_size)
{
for(int to_delete = length - max_size; to_delete; --to_delete)
{
l.erase(l.begin());
}
}
}
void DebuggerConsoleView::trySendCommand(QString cmd)
{
if (m_repeatLastCommand && cmd.isEmpty()) {
cmd = m_cmdEditor->historyItems().last();
}
if (!cmd.isEmpty())
{
m_cmdEditor->addToHistory(cmd);
m_cmdEditor->clearEditText();
emit sendCommand(cmd);
}
}
void DebuggerConsoleView::handleSessionChanged(KDevelop::IDebugSession* s)
{
MIDebugSession *session = qobject_cast<MIDebugSession*>(s);
if (!session) return;
connect(this, &DebuggerConsoleView::sendCommand,
session, &MIDebugSession::addUserCommand);
connect(this, &DebuggerConsoleView::interruptDebugger,
session, &MIDebugSession::interruptDebugger);
connect(session, &MIDebugSession::debuggerInternalCommandOutput,
this, &DebuggerConsoleView::receivedInternalCommandStdout);
connect(session, &MIDebugSession::debuggerUserCommandOutput,
this, &DebuggerConsoleView::receivedUserCommandStdout);
connect(session, &MIDebugSession::debuggerInternalOutput,
this, &DebuggerConsoleView::receivedStderr);
connect(session, &MIDebugSession::debuggerStateChanged,
this, &DebuggerConsoleView::handleDebuggerStateChange);
handleDebuggerStateChange(s_none, session->debuggerState());
}
/*
* Debugger Console View
* Copyright 2016 Aetf <aetf@unlimitedcodeworks.xyz>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef DEBUGGERCONSOLEVIEW_H
#define DEBUGGERCONSOLEVIEW_H
#include <QWidget>
#include <QPoint>
#include <QStringList>
#include <QTimer>
#include "dbgglobal.h"
class QMenu;
class QTextEdit;
class QToolBar;
class KHistoryComboBox;
namespace KDevelop {
class IDebugSession;
}
namespace KDevMI {
class MIDebuggerPlugin;
/**
* @brief A debugger console gives the user direct access to the debugger command line interface.
*/
class DebuggerConsoleView : public QWidget
{
Q_OBJECT
public:
DebuggerConsoleView(MIDebuggerPlugin *plugin, QWidget *parent = nullptr);
~DebuggerConsoleView();
/**
* Whether show a button allowing user to interrput debugger execution.
*/
void setShowInterrupt(bool enable);
void setShowInternalCommands(bool enable);
Q_SIGNALS:
void requestRaise();
/**
* Proxy signals for DebugSession
*/
void interruptDebugger();
void sendCommand(const QString &cmd);
protected:
void setupUi();
void setupToolBar();
/**
* Arranges for 'line' to be shown to the user.
* Adds 'line' to m_pendingOutput and makes sure
* m_updateTimer is running.
*/
void appendLine(const QString &line);
void updateColors();
QString colorify(QString text, const QColor &color);
/**
* Makes 'l' no longer than 'max_size' by
* removing excessive elements from the top.
*/
void trimList(QStringList& l, int max_size);
bool eventFilter(QObject *, QEvent *) override;
void focusInEvent(QFocusEvent *e) override;
protected Q_SLOTS:
void showContextMenu(const QPoint &pos);
void toggleRepeat(bool checked);
void toggleShowInternalCommands(bool checked);
void flushPending();
void clear();
void handleSessionChanged(KDevelop::IDebugSession *session);
void handleDebuggerStateChange(DBGStateFlags oldStatus, DBGStateFlags newStatus);
void receivedInternalCommandStdout(const QString &line);
void receivedUserCommandStdout(const QString &line);
void receivedStdout(const QString &line, bool internal);
void receivedStderr(const QString &line);
void trySendCommand(QString cmd);
private:
QAction *m_actRepeat;
QAction *m_actInterrupt;
QAction *m_actShowInternal;
QAction *m_actCmdEditor;
QTextEdit *m_textView;
QMenu *m_contextMenu;
QToolBar *m_toolBar;
KHistoryComboBox *m_cmdEditor;
bool m_repeatLastCommand;
bool m_showInternalCommands;
bool m_cmdEditorHadFocus;
/**
* The output from user commands only and from all
* commands. We keep it here so that if we switch
* "Show internal commands" on, we can show previous
* internal commands.
*/
QStringList m_allOutput;
QStringList m_userOutput;
/**
* For performance reasons, we don't immediately add new text
* to QTExtEdit. Instead we add it to m_pendingOutput and
* flush it on timer.
*/
QString m_pendingOutput;
QTimer m_updateTimer;
QColor m_stdColor;
QColor m_errorColor;
int m_maxLines;
};
} // end of namespace KDevMI
#endif // DEBUGGERCONSOLEVIEW_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">