diff --git a/shell/debugcontroller.cpp b/shell/debugcontroller.cpp index 8725e5ac27..06dabad9ee 100644 --- a/shell/debugcontroller.cpp +++ b/shell/debugcontroller.cpp @@ -1,535 +1,562 @@ /* This file is part of KDevelop * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * 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 #include #include #include #include #include #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 "../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 "core.h" #include "debug.h" #include "uicontroller.h" +#include "iruncontroller.h" +#include "launchconfigurationdialog.h" namespace KDevelop { template class DebuggerToolFactory : public KDevelop::IToolViewFactory { public: DebuggerToolFactory(DebugController* controller, const QString &id, Qt::DockWidgetArea defaultArea) : m_controller(controller), m_id(id), m_defaultArea(defaultArea) {} QWidget* create(QWidget *parent = nullptr) override { return new T(m_controller, parent); } QString id() const override { return m_id; } Qt::DockWidgetArea defaultPosition() override { return m_defaultArea; } void viewCreated(Sublime::View* view) override { 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. */ QList toolBarActions( QWidget* viewWidget ) const override { Q_UNUSED(viewWidget); return QList(); } private: DebugController* m_controller; QString m_id; Qt::DockWidgetArea m_defaultArea; }; DebugController::DebugController(QObject *parent) : IDebugController(parent), KXMLGUIClient(), m_continueDebugger(nullptr), m_stopDebugger(nullptr), m_interruptDebugger(nullptr), m_runToCursor(nullptr), m_jumpToCursor(nullptr), m_stepOver(nullptr), m_stepIntoInstruction(nullptr), m_stepInto(nullptr), m_stepOverInstruction(nullptr), m_stepOut(nullptr), m_toggleBreakpoint(nullptr), m_breakpointModel(new BreakpointModel(this)), m_variableCollection(new VariableCollection(this)), m_uiInitialized(false) { setComponentName(QStringLiteral("kdevdebugger"), QStringLiteral("kdevdebugger")); setXMLFile(QStringLiteral("kdevdebuggershellui.rc")); } void DebugController::initialize() { m_breakpointModel->load(); } 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( this, QStringLiteral("org.kdevelop.debugger.StackView"), Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Breakpoints"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.BreakpointsView"), Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Variables"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.VariablesView"), Qt::LeftDockWidgetArea)); foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts()) partAdded(p); connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &DebugController::partAdded); ICore::self()->uiController()->activeMainWindow()->guiFactory()->addClient(this); stateChanged(QStringLiteral("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(part)) { KTextEditor::MarkInterface *iface = dynamic_cast(doc); if( !iface ) return; iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, *executionPointPixmap()); } } IDebugSession* DebugController::currentSession() { return m_currentSession.data(); } void DebugController::setupActions() { KActionCollection* ac = actionCollection(); - QAction* action = m_continueDebugger = new QAction(QIcon::fromTheme(QStringLiteral("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).") ); + QAction* action = m_continueDebugger = new QAction(this); + setContinueStartsDebug(true); ac->addAction(QStringLiteral("debug_continue"), action); connect(action, &QAction::triggered, this, &DebugController::run); #if 0 m_restartDebugger = action = new QAction(QIcon::fromTheme("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 QAction(QIcon::fromTheme(QStringLiteral("media-playback-pause")), i18n("Interrupt"), this); action->setToolTip( i18n("Interrupt application") ); action->setWhatsThis(i18n("Interrupts the debugged process or current debugger command.")); connect(action, &QAction::triggered, this, &DebugController::interruptDebugger); ac->addAction(QStringLiteral("debug_pause"), action); m_runToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("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, &QAction::triggered, this, &DebugController::runToCursor); ac->addAction(QStringLiteral("debug_runtocursor"), action); m_jumpToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("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, &QAction::triggered, this, &DebugController::jumpToCursor); ac->addAction(QStringLiteral("debug_jumptocursor"), action); m_stepOver = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-over")), i18n("Step &Over"), this); ac->setDefaultShortcut( action, 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, &QAction::triggered, this, &DebugController::stepOver); ac->addAction(QStringLiteral("debug_stepover"), action); m_stepOverInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("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, &QAction::triggered, this, &DebugController::stepOverInstruction); ac->addAction(QStringLiteral("debug_stepoverinst"), action); m_stepInto = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-into")), i18n("Step &Into"), this); ac->setDefaultShortcut( action, 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, &QAction::triggered, this, &DebugController::stepInto); ac->addAction(QStringLiteral("debug_stepinto"), action); m_stepIntoInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("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, &QAction::triggered, this, &DebugController::stepIntoInstruction); ac->addAction(QStringLiteral("debug_stepintoinst"), action); m_stepOut = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-out")), i18n("Step O&ut"), this); ac->setDefaultShortcut( action, 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, &QAction::triggered, this, &DebugController::stepOut); ac->addAction(QStringLiteral("debug_stepout"), action); m_toggleBreakpoint = action = new QAction(QIcon::fromTheme(QStringLiteral("breakpoint")), i18n("Toggle Breakpoint"), this); ac->setDefaultShortcut( action, i18n("Ctrl+Alt+B") ); action->setToolTip(i18n("Toggle breakpoint")); action->setWhatsThis(i18n("Toggles the breakpoint at the current line in editor.")); connect(action, &QAction::triggered, this, &DebugController::toggleBreakpoint); ac->addAction(QStringLiteral("debug_toggle_breakpoint"), action); } void DebugController::addSession(IDebugSession* session) { qCDebug(SHELL) << 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, &IDebugSession::stateChanged, this, &DebugController::debuggerStateChanged); connect(session, &IDebugSession::showStepInSource, this, &DebugController::showStepInSource); connect(session, &IDebugSession::clearExecutionPoint, this, &DebugController::clearExecutionPoint); connect(session, &IDebugSession::raiseFramestackViews, this, &DebugController::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() != QLatin1String("debug")) { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea(QStringLiteral("debug"), IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); connect(mainWindow, &Sublime::MainWindow::areaChanged, this, &DebugController::areaChanged); } } void DebugController::clearExecutionPoint() { qCDebug(SHELL); foreach (KDevelop::IDocument* document, KDevelop::ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *iface = dynamic_cast(document->textDocument()); if (!iface) continue; QHashIterator 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 QUrl &url, int lineNum) { if((Core::self()->setupFlags() & Core::NoUi)) return; clearExecutionPoint(); qCDebug(SHELL) << url << lineNum; Q_ASSERT(dynamic_cast(sender())); QPair openUrl = static_cast(sender())->convertToLocalUrl(qMakePair( 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(document->textDocument()); if( !iface ) return; { QSignalBlocker blocker(document->textDocument()); iface->addMark( lineNum, KTextEditor::MarkInterface::Execution ); } } void DebugController::debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state) { Q_ASSERT(dynamic_cast(sender())); IDebugSession* session = static_cast(sender()); qCDebug(SHELL) << 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(nullptr); if (!Core::self()->shuttingDown()) { Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow && mainWindow->area()->objectName() != QLatin1String("code")) { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea(QStringLiteral("code"), IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); } ICore::self()->uiController()->findToolView(i18n("Debug"), nullptr, IUiController::Raise); } } session->deleteLater(); } } void DebugController::updateDebuggerState(IDebugSession::DebuggerState state, IDebugSession *session) { Q_UNUSED(session); if((Core::self()->setupFlags() & Core::NoUi)) return; qCDebug(SHELL) << state; switch (state) { case IDebugSession::StoppedState: case IDebugSession::NotStartedState: case IDebugSession::StoppingState: qCDebug(SHELL) << "new state: stopped"; stateChanged(QStringLiteral("stopped")); + setContinueStartsDebug(true); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::StartingState: case IDebugSession::PausedState: qCDebug(SHELL) << "new state: paused"; stateChanged(QStringLiteral("paused")); + setContinueStartsDebug(false); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::ActiveState: qCDebug(SHELL) << "new state: active"; stateChanged(QStringLiteral("active")); + setContinueStartsDebug(false); //m_restartDebugger->setEnabled(false); break; case IDebugSession::EndedState: qCDebug(SHELL) << "new state: ended"; stateChanged(QStringLiteral("ended")); + setContinueStartsDebug(true); //m_restartDebugger->setEnabled(false); break; } if (state == IDebugSession::PausedState && ICore::self()->uiController()->activeMainWindow()) { ICore::self()->uiController()->activeMainWindow()->activateWindow(); } } +void DebugController::setContinueStartsDebug(bool startsDebug) +{ + if (startsDebug) { + m_continueDebugger->setText(i18n("Debug Launch")); + m_continueDebugger->setIcon(QIcon::fromTheme(QStringLiteral("debug-run"))); + m_continueDebugger->setToolTip(i18n("Debug current launch")); + m_continueDebugger->setWhatsThis(i18n("Executes the target or the program specified in " + "currently active launch configuration inside a Debugger.")); + } else { + m_continueDebugger->setText(i18n("&Continue")); + m_continueDebugger->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); + m_continueDebugger->setToolTip(i18n("Continue application execution") ); + m_continueDebugger->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).") ); + } +} + ContextMenuExtension DebugController::contextMenuExtension( Context* context ) { ContextMenuExtension menuExt; if( context->type() != Context::EditorContext ) return menuExt; KDevelop::EditorContext *econtext = dynamic_cast(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(); + } else { + if (ICore::self()->runController()->launchConfigurations().isEmpty()) { + LaunchConfigurationDialog d; + d.exec(); + } + ICore::self()->runController()->executeDefaultLaunch(QStringLiteral("debug")); } } 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()!=QLatin1String("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=QIcon::fromTheme(QStringLiteral("go-next")).pixmap(QSize(22,22), QIcon::Normal, QIcon::Off); return &pixmap; } } diff --git a/shell/debugcontroller.h b/shell/debugcontroller.h index a1b8cd44ee..eb018d30ac 100644 --- a/shell/debugcontroller.h +++ b/shell/debugcontroller.h @@ -1,123 +1,124 @@ /* This file is part of KDevelop * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * 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. */ #ifndef KDEVPLATFORM_DEBUGCONTROLLER_H #define KDEVPLATFORM_DEBUGCONTROLLER_H #include #include #include #include #include "../interfaces/idebugcontroller.h" #include "../debugger/interfaces/idebugsession.h" class QAction; namespace Sublime { class Area; } namespace KParts { class Part; } namespace KDevelop { class Context; class ContextMenuExtension; class DebugController : public IDebugController, public KXMLGUIClient { Q_OBJECT public: explicit DebugController(QObject *parent = nullptr); ~DebugController() override; void initialize(); void cleanup(); /// Must be called by debugger plugin that needs debugger actions and toolviews. void initializeUi() override; void addSession(IDebugSession* session) override; IDebugSession* currentSession() override; ContextMenuExtension contextMenuExtension( Context* context ); BreakpointModel* breakpointModel() override; VariableCollection* variableCollection() override; private Q_SLOTS: //void restartDebugger(); void stopDebugger(); void interruptDebugger(); void run(); void runToCursor(); void jumpToCursor(); void stepOver(); void stepIntoInstruction(); void stepInto(); void stepOverInstruction(); void stepOut(); void toggleBreakpoint(); void debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state); void showStepInSource(const QUrl &file, int line); void clearExecutionPoint(); void partAdded(KParts::Part* part); void areaChanged(Sublime::Area* newArea); Q_SIGNALS: void raiseFramestackViews(); private: void setupActions(); void updateDebuggerState(KDevelop::IDebugSession::DebuggerState state, KDevelop::IDebugSession* session); + void setContinueStartsDebug(bool startsDebug); static const QPixmap* executionPointPixmap(); QAction* m_continueDebugger; //QAction* m_restartDebugger; QAction* m_stopDebugger; QAction* m_interruptDebugger; QAction* m_runToCursor; QAction* m_jumpToCursor; QAction* m_stepOver; QAction* m_stepIntoInstruction; QAction* m_stepInto; QAction* m_stepOverInstruction; QAction* m_stepOut; QAction* m_toggleBreakpoint; QPointer m_currentSession; BreakpointModel *m_breakpointModel; VariableCollection *m_variableCollection; bool m_uiInitialized; }; } #endif diff --git a/shell/debugger/kdevdebuggershellui.rc b/shell/debugger/kdevdebuggershellui.rc index dfd2f1d530..b486a8e612 100644 --- a/shell/debugger/kdevdebuggershellui.rc +++ b/shell/debugger/kdevdebuggershellui.rc @@ -1,90 +1,92 @@ - + Run Debugger Toolbar + + ++ - + -