kde-extraapps/kdevplatform/shell/debugcontroller.cpp
2015-07-26 14:23:17 +03:00

536 lines
19 KiB
C++

/* This file is part of KDevelop
*
* Copyright 1999-2001 John Birch <jbb@kdevelop.org>
* Copyright 2001 by Bernd Gehrmann <bernd@kdevelop.org>
* Copyright 2006 Vladimir Prus <ghost@cs.msu.su>
* Copyright 2007 Hamish Rodda <rodda@kde.org>
* Copyright 2009 Niko Sams <niko.sams@gmail.com>
*
* 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) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "debugcontroller.h"
#include <QtCore/QMetaEnum>
#include <KDE/KLocale>
#include <KDE/KDebug>
#include <KDE/KActionCollection>
#include <KDE/KAction>
#include <KDE/KParts/Part>
#include <KDE/KParts/PartManager>
#include <KDE/KTextEditor/Document>
#include <KDE/KTextEditor/MarkInterface>
#include "../interfaces/idocument.h"
#include "../interfaces/icore.h"
#include "../interfaces/idocumentcontroller.h"
#include "../interfaces/ipartcontroller.h"
#include "../interfaces/contextmenuextension.h"
#include "../interfaces/context.h"
#include "../language/interfaces/editorcontext.h"
#include "../sublime/view.h"
#include "../sublime/mainwindow.h"
#include "../sublime/area.h"
#include "core.h"
#include "uicontroller.h"
#include "../debugger/breakpoint/breakpointmodel.h"
#include "../debugger/breakpoint/breakpointwidget.h"
#include "../debugger/variable/variablewidget.h"
#include "../debugger/framestack/framestackmodel.h"
#include "../debugger/framestack/framestackwidget.h"
#include <KXMLGUIFactory>
namespace KDevelop {
template<class T>
class DebuggerToolFactory : public KDevelop::IToolViewFactory
{
public:
DebuggerToolFactory(DebugController* controller, const QString &id, Qt::DockWidgetArea defaultArea)
: m_controller(controller), m_id(id), m_defaultArea(defaultArea) {}
virtual QWidget* create(QWidget *parent = 0)
{
return new T(m_controller, parent);
}
virtual QString id() const
{
return m_id;
}
virtual Qt::DockWidgetArea defaultPosition()
{
return m_defaultArea;
}
virtual void viewCreated(Sublime::View* view)
{
if (view->widget()->metaObject()->indexOfSignal("requestRaise()") != -1)
QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise()));
}
/* 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. */
virtual QList<QAction*> toolBarActions( QWidget* viewWidget ) const
{
Q_UNUSED(viewWidget);
return QList<QAction*>();
}
private:
DebugController* m_controller;
QString m_id;
Qt::DockWidgetArea m_defaultArea;
};
DebugController::DebugController(QObject *parent)
: IDebugController(parent), KXMLGUIClient(),
m_continueDebugger(0), m_stopDebugger(0),
m_interruptDebugger(0), m_runToCursor(0),
m_jumpToCursor(0), m_stepOver(0),
m_stepIntoInstruction(0), m_stepInto(0),
m_stepOverInstruction(0), m_stepOut(0),
m_toggleBreakpoint(0),
m_breakpointModel(new BreakpointModel(this)),
m_variableCollection(new VariableCollection(this)),
m_uiInitialized(false)
{
setComponentData(KComponentData("kdevdebugger"));
setXMLFile("kdevdebuggershellui.rc");
}
void DebugController::initialize()
{
}
void DebugController::initializeUi()
{
if (m_uiInitialized) return;
m_uiInitialized = true;
if((Core::self()->setupFlags() & Core::NoUi)) return;
setupActions();
ICore::self()->uiController()->addToolView(
i18n("Frame Stack"),
new DebuggerToolFactory<FramestackWidget>(
this, "org.kdevelop.debugger.StackView",
Qt::BottomDockWidgetArea));
ICore::self()->uiController()->addToolView(
i18n("Breakpoints"),
new DebuggerToolFactory<BreakpointWidget>(
this, "org.kdevelop.debugger.BreakpointsView",
Qt::BottomDockWidgetArea));
ICore::self()->uiController()->addToolView(
i18n("Variables"),
new DebuggerToolFactory<VariableWidget>(
this, "org.kdevelop.debugger.VariablesView",
Qt::LeftDockWidgetArea));
foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts())
partAdded(p);
connect(KDevelop::ICore::self()->partController(),
SIGNAL(partAdded(KParts::Part*)),
this,
SLOT(partAdded(KParts::Part*)));
ICore::self()->uiController()->activeMainWindow()->guiFactory()->addClient(this);
stateChanged("ended");
}
void DebugController::cleanup()
{
if (m_currentSession) m_currentSession.data()->stopDebugger();
}
DebugController::~DebugController()
{
}
BreakpointModel* DebugController::breakpointModel()
{
return m_breakpointModel;
}
VariableCollection* DebugController::variableCollection()
{
return m_variableCollection;
}
void DebugController::partAdded(KParts::Part* part)
{
if (KTextEditor::Document* doc = dynamic_cast<KTextEditor::Document*>(part)) {
KTextEditor::MarkInterface *iface = dynamic_cast<KTextEditor::MarkInterface*>(doc);
if( !iface )
return;
iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, *executionPointPixmap());
}
}
IDebugSession* DebugController::currentSession()
{
return m_currentSession.data();
}
void DebugController::setupActions()
{
KActionCollection* ac = actionCollection();
KAction* action = m_continueDebugger = new KAction(KIcon("media-playback-start"), i18n("&Continue"), this);
action->setToolTip( i18n("Continue application execution") );
action->setWhatsThis( i18n("Continues the execution of your application in the "
"debugger. This only takes effect when the application "
"has been halted by the debugger (i.e. a breakpoint has "
"been activated or the interrupt was pressed).") );
ac->addAction("debug_continue", action);
connect(action, SIGNAL(triggered(bool)), this, SLOT(run()));
#if 0
m_restartDebugger = action = new KAction(KIcon("media-seek-backward"), i18n("&Restart"), this);
action->setToolTip( i18n("Restart program") );
action->setWhatsThis( i18n("Restarts applications from the beginning.") );
action->setEnabled(false);
connect(action, SIGNAL(triggered(bool)), this, SLOT(restartDebugger()));
ac->addAction("debug_restart", action);
#endif
m_interruptDebugger = action = new KAction(KIcon("media-playback-pause"), i18n("Interrupt"), this);
action->setToolTip( i18n("Interrupt application") );
action->setWhatsThis(i18n("Interrupts the debugged process or current debugger command."));
connect(action, SIGNAL(triggered(bool)), this, SLOT(interruptDebugger()));
ac->addAction("debug_pause", action);
m_runToCursor = action = new KAction(KIcon("debug-run-cursor"), i18n("Run to &Cursor"), this);
action->setToolTip( i18n("Run to cursor") );
action->setWhatsThis(i18n("Continues execution until the cursor position is reached."));
connect(action, SIGNAL(triggered(bool)), this, SLOT(runToCursor()));
ac->addAction("debug_runtocursor", action);
m_jumpToCursor = action = new KAction(KIcon("debug-execute-to-cursor"), i18n("Set E&xecution Position to Cursor"), this);
action->setToolTip( i18n("Jump to cursor") );
action->setWhatsThis(i18n("Continue execution from the current cursor position."));
connect(action, SIGNAL(triggered(bool)), this, SLOT(jumpToCursor()));
ac->addAction("debug_jumptocursor", action);
m_stepOver = action = new KAction(KIcon("debug-step-over"), i18n("Step &Over"), this);
action->setShortcut(Qt::Key_F10);
action->setToolTip( i18n("Step over the next line") );
action->setWhatsThis( i18n("Executes one line of source in the current source file. "
"If the source line is a call to a function the whole "
"function is executed and the app will stop at the line "
"following the function call.") );
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepOver()));
ac->addAction("debug_stepover", action);
m_stepOverInstruction = action = new KAction(KIcon("debug-step-instruction"), i18n("Step over Ins&truction"), this);
action->setToolTip( i18n("Step over instruction") );
action->setWhatsThis(i18n("Steps over the next assembly instruction."));
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepOverInstruction()));
ac->addAction("debug_stepoverinst", action);
m_stepInto = action = new KAction(KIcon("debug-step-into"), i18n("Step &Into"), this);
action->setShortcut(Qt::Key_F11);
action->setToolTip( i18n("Step into the next statement") );
action->setWhatsThis( i18n("Executes exactly one line of source. If the source line "
"is a call to a function then execution will stop after "
"the function has been entered.") );
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepInto()));
ac->addAction("debug_stepinto", action);
m_stepIntoInstruction = action = new KAction(KIcon("debug-step-into-instruction"), i18n("Step into I&nstruction"), this);
action->setToolTip( i18n("Step into instruction") );
action->setWhatsThis(i18n("Steps into the next assembly instruction."));
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepIntoInstruction()));
ac->addAction("debug_stepintoinst", action);
m_stepOut = action = new KAction(KIcon("debug-step-out"), i18n("Step O&ut"), this);
action->setShortcut(Qt::Key_F12);
action->setToolTip( i18n("Step out of the current function") );
action->setWhatsThis( i18n("Executes the application until the currently executing "
"function is completed. The debugger will then display "
"the line after the original call to that function. If "
"program execution is in the outermost frame (i.e. in "
"main()) then this operation has no effect.") );
connect(action, SIGNAL(triggered(bool)), this, SLOT(stepOut()));
ac->addAction("debug_stepout", action);
m_toggleBreakpoint = action = new KAction(KIcon("script-error"), i18n("Toggle Breakpoint"), this);
action->setShortcut( i18n("Ctrl+Alt+B") );
action->setToolTip(i18n("Toggle breakpoint"));
action->setWhatsThis(i18n("Toggles the breakpoint at the current line in editor."));
connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleBreakpoint()));
ac->addAction("debug_toggle_breakpoint", action);
}
void DebugController::addSession(IDebugSession* session)
{
kDebug() << session;
Q_ASSERT(session->variableController());
Q_ASSERT(session->breakpointController());
Q_ASSERT(session->frameStackModel());
//TODO support multiple sessions
if (m_currentSession) {
m_currentSession.data()->stopDebugger();
}
m_currentSession = session;
connect(session, SIGNAL(stateChanged(KDevelop::IDebugSession::DebuggerState)), SLOT(debuggerStateChanged(KDevelop::IDebugSession::DebuggerState)));
connect(session, SIGNAL(showStepInSource(KUrl,int,QString)), SLOT(showStepInSource(KUrl,int)));
connect(session, SIGNAL(clearExecutionPoint()), SLOT(clearExecutionPoint()));
connect(session, SIGNAL(raiseFramestackViews()), SIGNAL(raiseFramestackViews()));
updateDebuggerState(session->state(), session);
emit currentSessionChanged(session);
if((Core::self()->setupFlags() & Core::NoUi)) return;
Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow();
if (mainWindow->area()->objectName() != "debug") {
QString workingSet = mainWindow->area()->workingSet();
ICore::self()->uiController()->switchToArea("debug", IUiController::ThisWindow);
mainWindow->area()->setWorkingSet(workingSet);
connect(mainWindow, SIGNAL(areaChanged(Sublime::Area*)), SLOT(areaChanged(Sublime::Area*)));
}
}
void DebugController::clearExecutionPoint()
{
kDebug();
foreach (KDevelop::IDocument* document, KDevelop::ICore::self()->documentController()->openDocuments()) {
KTextEditor::MarkInterface *iface = dynamic_cast<KTextEditor::MarkInterface*>(document->textDocument());
if (!iface)
continue;
QHashIterator<int, KTextEditor::Mark*> it = iface->marks();
while (it.hasNext())
{
KTextEditor::Mark* mark = it.next().value();
if( mark->type & KTextEditor::MarkInterface::Execution )
iface->removeMark( mark->line, KTextEditor::MarkInterface::Execution );
}
}
}
void DebugController::showStepInSource(const KUrl &url, int lineNum)
{
if((Core::self()->setupFlags() & Core::NoUi)) return;
clearExecutionPoint();
kDebug() << url << lineNum;
Q_ASSERT(dynamic_cast<IDebugSession*>(sender()));
QPair<KUrl,int> openUrl = static_cast<IDebugSession*>(sender())->convertToLocalUrl(qMakePair<KUrl,int>( url, lineNum ));
KDevelop::IDocument* document = KDevelop::ICore::self()
->documentController()
->openDocument(openUrl.first, KTextEditor::Cursor(openUrl.second, 0), IDocumentController::DoNotFocus);
if( !document )
return;
KTextEditor::MarkInterface *iface = dynamic_cast<KTextEditor::MarkInterface*>(document->textDocument());
if( !iface )
return;
document->textDocument()->blockSignals(true);
iface->addMark( lineNum, KTextEditor::MarkInterface::Execution );
document->textDocument()->blockSignals(false);
}
void DebugController::debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state)
{
Q_ASSERT(dynamic_cast<IDebugSession*>(sender()));
IDebugSession* session = static_cast<IDebugSession*>(sender());
kDebug() << session << state << "current" << m_currentSession.data();
if (session == m_currentSession.data()) {
updateDebuggerState(state, session);
}
if (state == IDebugSession::EndedState) {
if (session == m_currentSession.data()) {
m_currentSession.clear();
emit currentSessionChanged(0);
if (!Core::self()->shuttingDown()) {
Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow();
if (mainWindow && mainWindow->area()->objectName() != "code") {
QString workingSet = mainWindow->area()->workingSet();
ICore::self()->uiController()->switchToArea("code", IUiController::ThisWindow);
mainWindow->area()->setWorkingSet(workingSet);
}
ICore::self()->uiController()->findToolView(i18n("Debug"), 0, IUiController::Raise);
}
}
session->deleteLater();
}
}
void DebugController::updateDebuggerState(IDebugSession::DebuggerState state, IDebugSession *session)
{
Q_UNUSED(session);
if((Core::self()->setupFlags() & Core::NoUi)) return;
kDebug() << state;
switch (state) {
case IDebugSession::StoppedState:
case IDebugSession::NotStartedState:
case IDebugSession::StoppingState:
kDebug() << "new state: stopped";
stateChanged("stopped");
//m_restartDebugger->setEnabled(session->restartAvailable());
break;
case IDebugSession::StartingState:
case IDebugSession::PausedState:
kDebug() << "new state: paused";
stateChanged("paused");
//m_restartDebugger->setEnabled(session->restartAvailable());
break;
case IDebugSession::ActiveState:
kDebug() << "new state: active";
stateChanged("active");
//m_restartDebugger->setEnabled(false);
break;
case IDebugSession::EndedState:
kDebug() << "new state: ended";
stateChanged("ended");
//m_restartDebugger->setEnabled(false);
break;
}
if (state == IDebugSession::PausedState && ICore::self()->uiController()->activeMainWindow()) {
ICore::self()->uiController()->activeMainWindow()->activateWindow();
}
}
ContextMenuExtension DebugController::contextMenuExtension( Context* context )
{
ContextMenuExtension menuExt;
if( context->type() != Context::EditorContext )
return menuExt;
KDevelop::EditorContext *econtext = dynamic_cast<KDevelop::EditorContext*>(context);
if (!econtext)
return menuExt;
if (m_currentSession && m_currentSession.data()->isRunning()) {
menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_runToCursor);
}
if (econtext->url().isLocalFile()) {
menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_toggleBreakpoint);
}
return menuExt;
}
#if 0
void DebugController::restartDebugger() {
if (m_currentSession) {
m_currentSession.data()->restartDebugger();
}
}
#endif
void DebugController::stopDebugger() {
if (m_currentSession) {
m_currentSession.data()->stopDebugger();
}
}
void DebugController::interruptDebugger() {
if (m_currentSession) {
m_currentSession.data()->interruptDebugger();
}
}
void DebugController::run() {
if (m_currentSession) {
m_currentSession.data()->run();
}
}
void DebugController::runToCursor() {
if (m_currentSession) {
m_currentSession.data()->runToCursor();
}
}
void DebugController::jumpToCursor() {
if (m_currentSession) {
m_currentSession.data()->jumpToCursor();
}
}
void DebugController::stepOver() {
if (m_currentSession) {
m_currentSession.data()->stepOver();
}
}
void DebugController::stepIntoInstruction() {
if (m_currentSession) {
m_currentSession.data()->stepIntoInstruction();
}
}
void DebugController::stepInto() {
if (m_currentSession) {
m_currentSession.data()->stepInto();
}
}
void DebugController::stepOverInstruction() {
if (m_currentSession) {
m_currentSession.data()->stepOverInstruction();
}
}
void DebugController::stepOut() {
if (m_currentSession) {
m_currentSession.data()->stepOut();
}
}
void DebugController::areaChanged(Sublime::Area* newArea)
{
if (newArea->objectName()!="debug") {
stopDebugger();
}
}
void DebugController::toggleBreakpoint()
{
if (KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument()) {
KTextEditor::Cursor cursor = document->cursorPosition();
if (!cursor.isValid()) return;
breakpointModel()->toggleBreakpoint(document->url(), cursor);
}
}
const QPixmap* DebugController::executionPointPixmap()
{
static QPixmap pixmap=KIcon("go-next").pixmap(QSize(22,22), QIcon::Normal, QIcon::Off);
return &pixmap;
}
}