diff --git a/analyzers/cppcheck/config/globalconfigpage.cpp b/analyzers/cppcheck/config/globalconfigpage.cpp index 945bcbcb55..72f63f98d5 100644 --- a/analyzers/cppcheck/config/globalconfigpage.cpp +++ b/analyzers/cppcheck/config/globalconfigpage.cpp @@ -1,60 +1,58 @@ /* * Copyright 2015 Laszlo Kis-Adam * Copyright 2016 Anton Anikin * * 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, see . */ #include "globalconfigpage.h" #include "ui_globalconfigpage.h" #include "globalsettings.h" -#include - namespace cppcheck { GlobalConfigPage::GlobalConfigPage(KDevelop::IPlugin* plugin, QWidget* parent) : ConfigPage(plugin, GlobalSettings::self(), parent) { Ui::GlobalConfigPage ui; ui.setupUi(this); } GlobalConfigPage::~GlobalConfigPage() { } KDevelop::ConfigPage::ConfigPageType GlobalConfigPage::configPageType() const { return KDevelop::ConfigPage::AnalyzerConfigPage; } QString GlobalConfigPage::name() const { return i18n("Cppcheck"); } QString GlobalConfigPage::fullName() const { return i18n("Configure Cppcheck Settings"); } QIcon GlobalConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("cppcheck")); } } diff --git a/analyzers/cppcheck/plugin.cpp b/analyzers/cppcheck/plugin.cpp index 4d89c2677d..2437fcb5da 100644 --- a/analyzers/cppcheck/plugin.cpp +++ b/analyzers/cppcheck/plugin.cpp @@ -1,263 +1,262 @@ /* This file is part of KDevelop Copyright 2013 Christoph Thielecke Copyright 2016-2017 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugin.h" #include "config/globalconfigpage.h" #include "config/projectconfigpage.h" #include "globalsettings.h" #include "debug.h" #include "job.h" #include "problemmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include K_PLUGIN_FACTORY_WITH_JSON(CppcheckFactory, "kdevcppcheck.json", registerPlugin();) namespace cppcheck { Plugin::Plugin(QObject* parent, const QVariantList&) : IPlugin("kdevcppcheck", parent) , m_job(nullptr) , m_currentProject(nullptr) , m_model(new ProblemModel(this)) { qCDebug(KDEV_CPPCHECK) << "setting cppcheck rc file"; setXMLFile("kdevcppcheck.rc"); m_actionFile = new QAction(QIcon::fromTheme("cppcheck"), i18n("Cppcheck (Current File)"), this); connect(m_actionFile, &QAction::triggered, [this](){ runCppcheck(false); }); actionCollection()->addAction("cppcheck_file", m_actionFile); m_actionProject = new QAction(QIcon::fromTheme("cppcheck"), i18n("Cppcheck (Current Project)"), this); connect(m_actionProject, &QAction::triggered, [this](){ runCppcheck(true); }); actionCollection()->addAction("cppcheck_project", m_actionProject); m_actionProjectItem = new QAction(QIcon::fromTheme("cppcheck"), i18n("Cppcheck"), this); connect(core()->documentController(), &KDevelop::IDocumentController::documentClosed, this, &Plugin::updateActions); connect(core()->documentController(), &KDevelop::IDocumentController::documentActivated, this, &Plugin::updateActions); connect(core()->projectController(), &KDevelop::IProjectController::projectOpened, this, &Plugin::updateActions); connect(core()->projectController(), &KDevelop::IProjectController::projectClosed, this, &Plugin::projectClosed); updateActions(); } Plugin::~Plugin() { killCppcheck(); } bool Plugin::isRunning() { return m_job; } void Plugin::killCppcheck() { if (m_job) { m_job->kill(KJob::EmitResult); } } void Plugin::raiseProblemsView() { m_model->show(); } void Plugin::raiseOutputView() { core()->uiController()->findToolView( i18nc("@title:window", "Test"), nullptr, KDevelop::IUiController::FindFlags::Raise); } void Plugin::updateActions() { m_currentProject = nullptr; m_actionFile->setEnabled(false); m_actionProject->setEnabled(false); if (isRunning()) { return; } KDevelop::IDocument* activeDocument = core()->documentController()->activeDocument(); if (!activeDocument) { return; } QUrl url = activeDocument->url(); m_currentProject = core()->projectController()->findProjectForUrl(url); if (!m_currentProject) { return; } m_actionFile->setEnabled(true); m_actionProject->setEnabled(true); } void Plugin::projectClosed(KDevelop::IProject* project) { if (project != m_model->project()) { return; } killCppcheck(); m_model->reset(); } void Plugin::runCppcheck(bool checkProject) { KDevelop::IDocument* doc = core()->documentController()->activeDocument(); Q_ASSERT(doc); if (checkProject) { runCppcheck(m_currentProject, m_currentProject->path().toUrl().toLocalFile()); } else { runCppcheck(m_currentProject, doc->url().toLocalFile()); } } void Plugin::runCppcheck(KDevelop::IProject* project, const QString& path) { m_model->reset(project, path); Parameters params(project); params.checkPath = path; m_job = new Job(params); connect(m_job, &Job::problemsDetected, m_model.data(), &ProblemModel::addProblems); connect(m_job, &Job::finished, this, &Plugin::result); core()->uiController()->registerStatus(new KDevelop::JobStatus(m_job, "Cppcheck")); core()->runController()->registerJob(m_job); if (params.hideOutputView) { raiseProblemsView(); } else { raiseOutputView(); } updateActions(); } void Plugin::result(KJob*) { if (!core()->projectController()->projects().contains(m_model->project())) { m_model->reset(); } else { m_model->setProblems(); if (m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded || m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { raiseProblemsView(); } else { raiseOutputView(); } } m_job = nullptr; // job is automatically deleted later updateActions(); } KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context); if (context->hasType(KDevelop::Context::EditorContext) && m_currentProject && !isRunning()) { extension.addAction(KDevelop::ContextMenuExtension::AnalyzeGroup, m_actionFile); extension.addAction(KDevelop::ContextMenuExtension::AnalyzeGroup, m_actionProject); } if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) { auto pContext = dynamic_cast(context); if (pContext->items().size() != 1) { return extension; } auto item = pContext->items().first(); switch (item->type()) { case KDevelop::ProjectBaseItem::File: case KDevelop::ProjectBaseItem::Folder: case KDevelop::ProjectBaseItem::BuildFolder: break; default: return extension; } m_actionProjectItem->disconnect(); connect(m_actionProjectItem, &QAction::triggered, [this, item](){ runCppcheck(item->project(), item->path().toLocalFile()); }); extension.addAction(KDevelop::ContextMenuExtension::AnalyzeGroup, m_actionProjectItem); } return extension; } KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) { return number ? nullptr : new ProjectConfigPage(this, options.project, parent); } KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) { return number ? nullptr : new GlobalConfigPage(this, parent); } } #include "plugin.moc" diff --git a/analyzers/heaptrack/job.cpp b/analyzers/heaptrack/job.cpp index b5663624c4..7055377902 100644 --- a/analyzers/heaptrack/job.cpp +++ b/analyzers/heaptrack/job.cpp @@ -1,151 +1,150 @@ /* This file is part of KDevelop Copyright 2017 Anton Anikin 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "job.h" #include "debug.h" #include "globalsettings.h" #include "utils.h" #include #include #include #include #include #include -#include #include #include #include #include namespace Heaptrack { Job::Job(KDevelop::ILaunchConfiguration* launchConfig) : m_pid(-1) { Q_ASSERT(launchConfig); auto pluginController = KDevelop::ICore::self()->pluginController(); auto iface = pluginController->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"))->extension(); Q_ASSERT(iface); QString envProfile = iface->environmentProfileName(launchConfig); if (envProfile.isEmpty()) { envProfile = KDevelop::EnvironmentProfileList(KSharedConfig::openConfig()).defaultProfileName(); } setEnvironmentProfile(envProfile); QString errorString; m_analyzedExecutable = iface->executable(launchConfig, errorString).toLocalFile(); if (!errorString.isEmpty()) { setError(-1); setErrorText(errorString); } QStringList analyzedExecutableArguments = iface->arguments(launchConfig, errorString); if (!errorString.isEmpty()) { setError(-1); setErrorText(errorString); } QUrl workDir = iface->workingDirectory(launchConfig); if (workDir.isEmpty() || !workDir.isValid()) { workDir = QUrl::fromLocalFile(QFileInfo(m_analyzedExecutable).absolutePath()); } setWorkingDirectory(workDir); *this << KDevelop::Path(GlobalSettings::heaptrackExecutable()).toLocalFile(); *this << m_analyzedExecutable; *this << analyzedExecutableArguments; setup(); } Job::Job(long int pid) : m_pid(pid) { *this << KDevelop::Path(GlobalSettings::heaptrackExecutable()).toLocalFile(); *this << QStringLiteral("-p"); *this << QString::number(m_pid); setup(); } void Job::setup() { setProperties(DisplayStdout); setProperties(DisplayStderr); setProperties(PostProcessOutput); setCapabilities(Killable); setStandardToolView(KDevelop::IOutputView::TestView); setBehaviours(KDevelop::IOutputView::AutoScroll); KDevelop::ICore::self()->uiController()->registerStatus(this); connect(this, &Job::finished, this, [this]() { emit hideProgress(this); }); } Job::~Job() { } QString Job::statusName() const { QString target = m_pid < 0 ? QFileInfo(m_analyzedExecutable).fileName() : QString("PID: %1").arg(m_pid); return i18n("Heaptrack Analysis (%1)", target); } QString Job::resultsFile() const { return m_resultsFile; } void Job::start() { emit showProgress(this, 0, 0, 0); OutputExecuteJob::start(); } void Job::postProcessStdout(const QStringList& lines) { static const auto resultRegex = QRegularExpression(QStringLiteral("heaptrack output will be written to \\\"(.+)\\\"")); if (m_resultsFile.isEmpty()) { QRegularExpressionMatch match; for (const QString & line : lines) { match = resultRegex.match(line); if (match.hasMatch()) { m_resultsFile = match.captured(1); break; } } } OutputExecuteJob::postProcessStdout(lines); } } diff --git a/app/main.cpp b/app/main.cpp index ffdfeee1dd..1d7ed32bbd 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,756 +1,755 @@ /*************************************************************************** * Copyright 2003-2009 Alexander Dymo * * Copyright 2007 Ralf Habacker * * Copyright 2006-2007 Matt Rogers * * Copyright 2006-2007 Hamish Rodda * * Copyright 2005-2007 Adam Treat * * Copyright 2003-2007 Jens Dagerbo * * Copyright 2001-2002 Bernd Gehrmann * * Copyright 2001-2002 Matthias Hoelzer-Kluepfel * * Copyright 2003 Roberto Raggi * * Copyright 2010 Niko Sams * * Copyright 2015 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library 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 #include "urlinfo.h" #include #include #include #include #include #include #include #include #include #include -#include -#include +#include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(APP) Q_LOGGING_CATEGORY(APP, "kdevelop.app") #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdevideextension.h" #if KDEVELOP_SINGLE_APP #include "qtsingleapplication.h" #endif #include #ifdef Q_OS_MAC #include #endif using namespace KDevelop; namespace { #if KDEVELOP_SINGLE_APP QString serializeOpenFilesMessage(const QVector &infos) { QByteArray message; QDataStream stream(&message, QIODevice::WriteOnly); stream << QByteArrayLiteral("open"); stream << infos; return QString::fromLatin1(message.toHex()); } #endif void openFiles(const QVector& infos) { foreach (const UrlInfo& file, infos) { if (!ICore::self()->documentController()->openDocument(file.url, file.cursor)) { qWarning() << i18n("Could not open %1", file.url.toDisplayString(QUrl::PreferLocalFile)); } } } } class KDevelopApplication: #if KDEVELOP_SINGLE_APP public SharedTools::QtSingleApplication #else public QApplication #endif { public: explicit KDevelopApplication(int &argc, char **argv, bool GUIenabled = true) #if KDEVELOP_SINGLE_APP : SharedTools::QtSingleApplication(QStringLiteral("KDevelop"), argc, argv) #else : QApplication(argc, argv, GUIenabled) #endif { Q_UNUSED(GUIenabled); connect(this, &QGuiApplication::saveStateRequest, this, &KDevelopApplication::saveState); } #if KDEVELOP_SINGLE_APP public Q_SLOTS: void remoteArguments(const QString &message, QObject *socket) { Q_UNUSED(socket); QByteArray ba = QByteArray::fromHex(message.toLatin1()); QDataStream stream(ba); QByteArray command; stream >> command; qCDebug(APP) << "Received remote command: " << command; if (command == "open") { QVector infos; stream >> infos; openFiles(infos); } else { qCWarning(APP) << "Unknown remote command: " << command; } } void fileOpenRequested(const QString &file) { openFiles({UrlInfo(file)}); } #endif private Q_SLOTS: void saveState( QSessionManager& sm ) { if (KDevelop::Core::self() && KDevelop::Core::self()->sessionController()) { QString x11SessionId = QString("%1_%2").arg(sm.sessionId()).arg(sm.sessionKey()); const auto activeSession = KDevelop::Core::self()->sessionController()->activeSession(); if (!activeSession) { qWarning() << "No active session, can't save state"; return; } QString kdevelopSessionId = activeSession->id().toString(); sm.setRestartCommand({QCoreApplication::applicationFilePath(), "-session", x11SessionId, "-s", kdevelopSessionId}); } } }; /// Tries to find a session identified by @p data in @p sessions. /// The @p data may be either a session's name or a string-representation of its UUID. /// @return pointer to the session or NULL if nothing appropriate has been found static const KDevelop::SessionInfo* findSessionInList( const SessionInfos& sessions, const QString& data ) { // We won't search a session without input data, since that could lead to false-positives // with unnamed sessions if( data.isEmpty() ) return nullptr; for( auto it = sessions.constBegin(); it != sessions.constEnd(); ++it ) { if ( ( it->name == data ) || ( it->uuid.toString() == data ) ) { const KDevelop::SessionInfo& sessionRef = *it; return &sessionRef; } } return nullptr; } /// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid /// Returns the exit status static int openFilesInRunningInstance(const QVector& files, qint64 pid) { const QString service = QString("org.kdevelop.kdevelop-%1").arg(pid); QDBusInterface iface(service, "/org/kdevelop/DocumentController", "org.kdevelop.DocumentController"); QStringList urls; bool errors_occured = false; foreach ( const UrlInfo& file, files ) { QDBusReply result = iface.call("openDocumentSimple", file.url.toString(), file.cursor.line(), file.cursor.column()); if ( ! result.value() ) { QTextStream err(stderr); err << i18n("Could not open file %1.", file.url.toDisplayString(QUrl::PreferLocalFile)) << "\n"; errors_occured = true; } } // make the window visible QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, "/kdevelop/MainWindow", "org.kdevelop.MainWindow", "ensureVisible" ); QDBusConnection::sessionBus().asyncCall( makeVisible ); return errors_occured; } /// Gets the PID of a running KDevelop instance, eventually asking the user if there is more than one. /// Returns -1 in case there are no running sessions. static qint64 getRunningSessionPid() { SessionInfos candidates; foreach( const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos() ) { if( KDevelop::SessionController::isSessionRunning(si.uuid.toString()) ) { candidates << si; } } if ( candidates.isEmpty() ) { return -1; } QString sessionUuid; if ( candidates.size() == 1 ) { sessionUuid = candidates.first().uuid.toString(); } else { const QString title = i18n("Select the session to open the document in"); sessionUuid = KDevelop::SessionController::showSessionChooserDialog(title, true); } return KDevelop::SessionController::sessionRunInfo(sessionUuid).holderPid; } static QString findSessionId(const SessionInfos& availableSessionInfos, const QString& session) { //If there is a session and a project with the same name, always open the session //regardless of the order encountered QString projectAsSession; foreach(const KDevelop::SessionInfo& si, availableSessionInfos) { if ( session == si.name || session == si.uuid.toString() ) { return si.uuid.toString(); } else if (projectAsSession.isEmpty()) { foreach(const QUrl& k, si.projects) { QString fn(k.fileName()); fn = fn.left(fn.indexOf('.')); if ( session == fn ) { projectAsSession = si.uuid.toString(); } } } } if (projectAsSession.isEmpty()) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot open unknown session %1. See `--list-sessions` switch for available sessions or use `-n` to create a new one.", session) << endl; } return projectAsSession; } static qint64 findSessionPid(const QString &sessionId) { KDevelop::SessionRunInfo sessionInfo = KDevelop::SessionController::sessionRunInfo( sessionId ); return sessionInfo.holderPid; } int main( int argc, char *argv[] ) { QElapsedTimer timer; timer.start(); // TODO: Maybe generalize, add KDEVELOP_STANDALONE build option #if defined(Q_OS_WIN) || defined(Q_OS_MAC) qputenv("KDE_FORK_SLAVES", "1"); // KIO slaves will be forked off instead of being started via DBus #endif // Useful for valgrind runs, just `export KDEV_DISABLE_JIT=1` if (qEnvironmentVariableIsSet("KDEV_DISABLE_JIT")) { qputenv("KDEV_DISABLE_WELCOMEPAGE", "1"); qputenv("QT_ENABLE_REGEXP_JIT", "0"); } // Don't show any debug output by default. // If you need to enable additional logging for debugging use a rules file // as explained in the QLoggingCategory documentation: // http://qt-project.org/doc/qt-5/qloggingcategory.html#logging-rules QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\n")); KLocalizedString::setApplicationDomain("kdevelop"); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); #ifdef Q_OS_MAC CFBundleRef mainBundle = CFBundleGetMainBundle(); if (mainBundle) { // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist, // for regular executables it is obtained in another way. CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle); if (infoDict) { // Try to prevent App Nap on OS X. This can be tricky in practice, at least in 10.9 . CFDictionarySetValue(infoDict, CFSTR("NSAppSleepDisabled"), kCFBooleanTrue); CFDictionarySetValue(infoDict, CFSTR("NSSupportsAutomaticTermination"), kCFBooleanFalse); } } #endif static const char description[] = I18N_NOOP( "The KDevelop Integrated Development Environment" ); KAboutData aboutData( "kdevelop", i18n( "KDevelop" ), QByteArray(VERSION), i18n(description), KAboutLicense::GPL, i18n("Copyright 1999-2017, The KDevelop developers"), QString(), "https://www.kdevelop.org/"); aboutData.setDesktopFileName(QStringLiteral("org.kde.kdevelop.desktop")); aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support" ), "kfunk@kde.org" ); aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), "svenbrauch@gmail.com" ); aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), "aleixpol@gmail.com" ); aboutData.addAuthor( i18n("Milian Wolff"), i18n( "C++/Clang, Generic manager, Webdevelopment Plugins, Snippets, Performance" ), "mail@milianw.de" ); aboutData.addAuthor( i18n("Olivier JG"), i18n( "C++/Clang, DUChain, Bug Fixes" ), "olivier.jg@gmail.com" ); aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), "apaku@gmx.de" ); aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), "adymo@kdevelop.org" ); aboutData.addAuthor( i18n("David Nolden"), i18n( "Definition-Use Chain, C++ Support, Code Navigation, Code Completion, Coding Assistance, Refactoring" ), "david.nolden.kdevelop@art-master.de" ); aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), "ghost@cs.msu.su" ); aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), "rodda@kde.org" ); aboutData.addAuthor( i18n("Amilcar do Carmo Lucas"), i18n( "Website admin, API documentation, Doxygen and autoproject patches" ), "amilcar@kdevelop.org" ); aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), "niko.sams@gmail.com" ); aboutData.addCredit( i18n("Matt Rogers"), QString(), "mattr@kde.org"); aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), "cedric.pasteur@free.fr" ); aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), "powerfox@kde.ru" ); // QTest integration is separate in playground currently. //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integration"), "mbr.nxi@gmail.com" ); aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), "rgruber@users.sourceforge.net" ); aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), "dukjuahn@gmail.com" ); aboutData.addCredit( i18n("Harald Fernengel"), i18n( "Ported to Qt 3, patches, valgrind, diff and perforce support" ), "harry@kdevelop.org" ); aboutData.addCredit( i18n("Roberto Raggi"), i18n( "C++ parser" ), "roberto@kdevelop.org" ); aboutData.addCredit( i18n("The KWrite authors"), i18n( "Kate editor component" ), "kwrite-devel@kde.org" ); aboutData.addCredit( i18n("Nokia Corporation/Qt Software"), i18n( "Designer code" ), "qt-info@nokia.com" ); aboutData.addCredit( i18n("Contributors to older versions:"), QString(), "" ); aboutData.addCredit( i18n("Bernd Gehrmann"), i18n( "Initial idea, basic architecture, much initial source code" ), "bernd@kdevelop.org" ); aboutData.addCredit( i18n("Caleb Tennis"), i18n( "KTabBar, bugfixes" ), "caleb@aei-tech.com" ); aboutData.addCredit( i18n("Richard Dale"), i18n( "Java & Objective C support" ), "Richard_Dale@tipitina.demon.co.uk" ); aboutData.addCredit( i18n("John Birch"), i18n( "Debugger frontend" ), "jbb@kdevelop.org" ); aboutData.addCredit( i18n("Sandy Meier"), i18n( "PHP support, context menu stuff" ), "smeier@kdevelop.org" ); aboutData.addCredit( i18n("Kurt Granroth"), i18n( "KDE application templates" ), "kurth@granroth.org" ); aboutData.addCredit( i18n("Ian Reinhart Geiser"), i18n( "Dist part, bash support, application templates" ), "geiseri@yahoo.com" ); aboutData.addCredit( i18n("Matthias Hoelzer-Kluepfel"), i18n( "Several components, htdig indexing" ), "hoelzer@kde.org" ); aboutData.addCredit( i18n("Victor Roeder"), i18n( "Help with Automake manager and persistent class store" ), "victor_roeder@gmx.de" ); aboutData.addCredit( i18n("Simon Hausmann"), i18n( "Help with KParts infrastructure" ), "hausmann@kde.org" ); aboutData.addCredit( i18n("Oliver Kellogg"), i18n( "Ada support" ), "okellogg@users.sourceforge.net" ); aboutData.addCredit( i18n("Jakob Simon-Gaarde"), i18n( "QMake projectmanager" ), "jsgaarde@tdcspace.dk" ); aboutData.addCredit( i18n("Falk Brettschneider"), i18n( "MDI modes, QEditor, bugfixes" ), "falkbr@kdevelop.org" ); aboutData.addCredit( i18n("Mario Scalas"), i18n( "PartExplorer, redesign of CvsPart, patches, bugs(fixes)" ), "mario.scalas@libero.it" ); aboutData.addCredit( i18n("Jens Dagerbo"), i18n( "Replace, Bookmarks, FileList and CTags2 plugins. Overall improvements and patches" ), "jens.dagerbo@swipnet.se" ); aboutData.addCredit( i18n("Julian Rockey"), i18n( "Filecreate part and other bits and patches" ), "linux@jrockey.com" ); aboutData.addCredit( i18n("Ajay Guleria"), i18n( "ClearCase support" ), "ajay_guleria@yahoo.com" ); aboutData.addCredit( i18n("Marek Janukowicz"), i18n( "Ruby support" ), "child@t17.ds.pwr.wroc.pl" ); aboutData.addCredit( i18n("Robert Moniot"), i18n( "Fortran documentation" ), "moniot@fordham.edu" ); aboutData.addCredit( i18n("Ka-Ping Yee"), i18n( "Python documentation utility" ), "ping@lfw.org" ); aboutData.addCredit( i18n("Dimitri van Heesch"), i18n( "Doxygen wizard" ), "dimitri@stack.nl" ); aboutData.addCredit( i18n("Hugo Varotto"), i18n( "Fileselector component" ), "hugo@varotto-usa.com" ); aboutData.addCredit( i18n("Matt Newell"), i18n( "Fileselector component" ), "newellm@proaxis.com" ); aboutData.addCredit( i18n("Daniel Engelschalt"), i18n( "C++ code completion, persistent class store" ), "daniel.engelschalt@gmx.net" ); aboutData.addCredit( i18n("Stephane Ancelot"), i18n( "Patches" ), "sancelot@free.fr" ); aboutData.addCredit( i18n("Jens Zurheide"), i18n( "Patches" ), "jens.zurheide@gmx.de" ); aboutData.addCredit( i18n("Luc Willems"), i18n( "Help with Perl support" ), "Willems.luc@pandora.be" ); aboutData.addCredit( i18n("Marcel Turino"), i18n( "Documentation index view" ), "M.Turino@gmx.de" ); aboutData.addCredit( i18n("Yann Hodique"), i18n( "Patches" ), "Yann.Hodique@lifl.fr" ); aboutData.addCredit( i18n("Tobias Gl\303\244\303\237er") , i18n( "Documentation Finder, qmake projectmanager patches, usability improvements, bugfixes ... " ), "tobi.web@gmx.de" ); aboutData.addCredit( i18n("Andreas Koepfle") , i18n( "QMake project manager patches" ), "koepfle@ti.uni-mannheim.de" ); aboutData.addCredit( i18n("Sascha Cunz") , i18n( "Cleanup and bugfixes for qEditor, AutoMake and much other stuff" ), "mail@sacu.de" ); aboutData.addCredit( i18n("Zoran Karavla"), i18n( "Artwork for the ruby language" ), "webmaster@the-error.net", "http://the-error.net" ); //we can't use KCmdLineArgs as it doesn't allow arguments for the debugee //so lookup the --debug switch and eat everything behind by decrementing argc //debugArgs is filled with args after --debug QStringList debugArgs; QString debugeeName; { bool debugFound = false; int c = argc; for (int i=0; i < c; ++i) { if (debugFound) { debugArgs << argv[i]; } else if ((qstrcmp(argv[i], "--debug") == 0) || (qstrcmp(argv[i], "-d") == 0)) { if (argc > (i + 1)) { i++; } argc = i + 1; debugFound = true; } else if (QString(argv[i]).startsWith("--debug=")) { argc = i + 1; debugFound = true; } } } KDevelopApplication app(argc, argv); KCrash::initialize(); Kdelibs4ConfigMigrator migrator(QStringLiteral("kdevelop")); migrator.setConfigFiles({QStringLiteral("kdeveloprc")}); migrator.setUiFiles({QStringLiteral("kdevelopui.rc")}); migrator.migrate(); // High DPI support app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); qCDebug(APP) << "Startup"; QCommandLineParser parser; KAboutData::setApplicationData(aboutData); parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); parser.addOption(QCommandLineOption{QStringList{"n", "new-session"}, i18n("Open KDevelop with a new session using the given name."), "name"}); parser.addOption(QCommandLineOption{QStringList{"s", "open-session"}, i18n("Open KDevelop with the given session.\n" "You can pass either hash or the name of the session." ), "session"}); parser.addOption(QCommandLineOption{QStringList{"rm", "remove-session"}, i18n("Delete the given session.\n" "You can pass either hash or the name of the session." ), "session"}); parser.addOption(QCommandLineOption{QStringList{"ps", "pick-session"}, i18n("Shows all available sessions and lets you select one to open.")}); parser.addOption(QCommandLineOption{QStringList{"pss", "pick-session-shell"}, i18n("List all available sessions on shell and lets you select one to open.")}); parser.addOption(QCommandLineOption{QStringList{"l", "list-sessions"}, i18n("List available sessions and quit.")}); parser.addOption(QCommandLineOption{QStringList{"p", "project"}, i18n("Open KDevelop and load the given project."), "project"}); parser.addOption(QCommandLineOption{QStringList{"d", "debug"}, i18n("Start debugging an application in KDevelop with the given debugger.\n" "The executable that should be debugged must follow - including arguments.\n" "Example: kdevelop --debug gdb myapp --foo bar"), "debugger"}); // this is used by the 'kdevelop!' script to retrieve the pid of a KDEVELOP // instance. When this is called, then we should just print the PID on the // standard-output. If a session is specified through open-session, then // we should return the PID of that session. Otherwise, if only a single // session is running, then we should just return the PID of that session. // Otherwise, we should print a command-line session-chooser dialog ("--pss"), // which only shows the running sessions, and the user can pick one. parser.addOption(QCommandLineOption{QStringList{"pid"}}); parser.addPositionalArgument("files", i18n( "Files to load" ), "[FILE...]"); // The session-controller needs to arguments to eventually pass them to newly opened sessions KDevelop::SessionController::setArguments(argc, argv); parser.process(app); aboutData.processCommandLine(&parser); if(parser.isSet("list-sessions")) { QTextStream qout(stdout); qout << endl << ki18n("Available sessions (use '-s HASH' or '-s NAME' to open a specific one):").toString() << endl << endl; qout << QString("%1").arg(ki18n("Hash").toString(), -38) << '\t' << ki18n("Name: Opened Projects").toString() << endl; foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } qout << si.uuid.toString() << '\t' << si.description; if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) qout << " " << i18n("[running]"); qout << endl; } return 0; } // Handle extra arguments, which stand for files to open QVector initialFiles; foreach (const QString &file, parser.positionalArguments()) { initialFiles.append(UrlInfo(file)); } const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos(); if (!initialFiles.isEmpty() && !parser.isSet("new-session")) { #if KDEVELOP_SINGLE_APP if (app.isRunning()) { bool success = app.sendMessage(serializeOpenFilesMessage(initialFiles)); if (success) { return 0; } } #else qint64 pid = -1; if (parser.isSet("open-session")) { const QString session = findSessionId(availableSessionInfos, parser.value("open-session")); if (session.isEmpty()) { return 1; } else if (KDevelop::SessionController::isSessionRunning(session)) { pid = findSessionPid(session); } } else { pid = getRunningSessionPid(); } if ( pid > 0 ) { return openFilesInRunningInstance(initialFiles, pid); } // else there are no running sessions, and the generated list of files will be opened below. #endif } // if empty, restart kdevelop with last active session, see SessionController::defaultSessionId QString session; uint nRunningSessions = 0; foreach(const KDevelop::SessionInfo& si, availableSessionInfos) if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) ++nRunningSessions; // also show the picker dialog when a pid shall be retrieved and multiple // sessions are running. if(parser.isSet("pss") || (parser.isSet("pid") && !parser.isSet("open-session") && !parser.isSet("ps") && nRunningSessions > 1)) { QTextStream qerr(stderr); SessionInfos candidates; foreach(const KDevelop::SessionInfo& si, availableSessionInfos) if( (!si.name.isEmpty() || !si.projects.isEmpty() || parser.isSet("pid")) && (!parser.isSet("pid") || KDevelop::SessionController::isSessionRunning(si.uuid.toString()))) candidates << si; if(candidates.size() == 0) { qerr << "no session available" << endl; return 1; } if(candidates.size() == 1 && parser.isSet("pid")) { session = candidates[0].uuid.toString(); }else{ for(int i = 0; i < candidates.size(); ++i) qerr << "[" << i << "]: " << candidates[i].description << endl; int chosen; std::cin >> chosen; if(std::cin.good() && (chosen >= 0 && chosen < candidates.size())) { session = candidates[chosen].uuid.toString(); }else{ qerr << "invalid selection" << endl; return 1; } } } if(parser.isSet("ps")) { bool onlyRunning = parser.isSet("pid"); session = KDevelop::SessionController::showSessionChooserDialog(i18n("Select the session you would like to use"), onlyRunning); if(session.isEmpty()) return 1; } if ( parser.isSet("debug") ) { if ( debugArgs.isEmpty() ) { QTextStream qerr(stderr); qerr << endl << i18nc("@info:shell", "Specify the executable you want to debug.") << endl; return 1; } QFileInfo executableFileInfo(debugArgs.first()); if (!executableFileInfo.exists()) { executableFileInfo = QStandardPaths::findExecutable(debugArgs.first()); if (!executableFileInfo.exists()) { QTextStream qerr(stderr); qerr << endl << i18nc("@info:shell", "Specified executable does not exist.") << endl; return 1; } } debugArgs.first() = executableFileInfo.absoluteFilePath(); debugeeName = i18n("Debug %1", executableFileInfo.fileName()); session = debugeeName; } else if ( parser.isSet("new-session") ) { session = parser.value("new-session"); foreach(const KDevelop::SessionInfo& si, availableSessionInfos) { if ( session == si.name ) { QTextStream qerr(stderr); qerr << endl << i18n("A session with the name %1 exists already. Use the -s switch to open it.", session) << endl; return 1; } } // session doesn't exist, we can create it } else if ( parser.isSet("open-session") ) { session = findSessionId(availableSessionInfos, parser.value("open-session")); if (session.isEmpty()) { return 1; } } else if ( parser.isSet("remove-session") ) { session = parser.value("remove-session"); auto si = findSessionInList(availableSessionInfos, session); if (!si) { QTextStream qerr(stderr); qerr << endl << i18n("No session with the name %1 exists.", session) << endl; return 1; } auto sessionLock = KDevelop::SessionController::tryLockSession(si->uuid.toString()); if (!sessionLock.lock) { QTextStream qerr(stderr); qerr << endl << i18n("Could not lock session %1 for deletion.", session) << endl; return 1; } KDevelop::SessionController::deleteSessionFromDisk(sessionLock.lock); QTextStream qout(stdout); qout << endl << i18n("Session with name %1 was successfully removed.", session) << endl; return 0; } if(parser.isSet("pid")) { if (session.isEmpty()) { // just pick the first running session foreach(const KDevelop::SessionInfo& si, availableSessionInfos) if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) session = si.uuid.toString(); } const KDevelop::SessionInfo* sessionData = findSessionInList(availableSessionInfos, session); if( !sessionData ) { qCritical() << "session not given or does not exist"; return 5; } const auto pid = findSessionPid(sessionData->uuid.toString()); if (pid > 0) { // Print the PID and we're ready std::cout << pid << std::endl; return 0; } else { qCritical() << sessionData->uuid.toString() << sessionData->name << "is not running"; return 5; } } KDevIDEExtension::init(); if(!Core::initialize(Core::Default, session)) return 5; // register a DBUS service for this process, so that we can open files in it from other invocations QDBusConnection::sessionBus().registerService(QString("org.kdevelop.kdevelop-%1").arg(app.applicationPid())); // TODO: port to kf5 // KGlobal::locale()->insertCatalog( Core::self()->componentData().catalogName() ); Core* core = Core::self(); if (!QProcessEnvironment::systemEnvironment().contains("KDEV_DISABLE_WELCOMEPAGE")) { core->pluginController()->loadPlugin("KDevWelcomePage"); } QStringList projectNames = parser.values("project"); foreach(const QString& projectName, projectNames) { QFileInfo info( projectName ); if( info.suffix() == "kdev4" ) { // make sure the project is not already opened by the session controller bool shouldOpen = true; Path path(info.absoluteFilePath()); foreach(KDevelop::IProject* project, core->projectController()->projects()) { if (project->projectFile() == path) { shouldOpen = false; break; } } if (shouldOpen) { core->projectController()->openProject( path.toUrl() ); } } } const QString debugStr = QStringLiteral("debug"); if ( parser.isSet(debugStr) ) { Q_ASSERT( !debugeeName.isEmpty() ); QString launchName = debugeeName; KDevelop::LaunchConfiguration* launch = nullptr; qCDebug(APP) << launchName; foreach (KDevelop::LaunchConfiguration *l, core->runControllerInternal()->launchConfigurationsInternal()) { qCDebug(APP) << l->name(); if (l->name() == launchName) { launch = l; } } KDevelop::LaunchConfigurationType *type = nullptr; foreach (KDevelop::LaunchConfigurationType *t, core->runController()->launchConfigurationTypes()) { qCDebug(APP) << t->id(); if (t->id() == "Native Application") { type = t; break; } } if (!type) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot find native launch configuration type") << endl; return 1; } if (launch && launch->type()->id() != "Native Application") launch = nullptr; if (launch && launch->launcherForMode(debugStr) != parser.value(debugStr)) launch = nullptr; if (!launch) { qCDebug(APP) << launchName << "not found, creating a new one"; QPair launcher; launcher.first = debugStr; foreach (KDevelop::ILauncher *l, type->launchers()) { if (l->id() == parser.value(debugStr)) { if (l->supportedModes().contains(debugStr)) { launcher.second = l->id(); } } } if (launcher.second.isEmpty()) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot find launcher %1", parser.value(debugStr)) << endl; return 1; } KDevelop::ILaunchConfiguration* ilaunch = core->runController()->createLaunchConfiguration(type, launcher, nullptr, launchName); launch = dynamic_cast(ilaunch); } type->configureLaunchFromCmdLineArguments(launch->config(), debugArgs); launch->config().writeEntry("Break on Start", true); core->runControllerInternal()->setDefaultLaunch(launch); core->runControllerInternal()->execute(debugStr, launch); } else { openFiles(initialFiles); } #if KDEVELOP_SINGLE_APP // Set up remote arguments. QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived, &app, &KDevelopApplication::remoteArguments); QObject::connect(&app, &SharedTools::QtSingleApplication::fileOpenRequest, &app, &KDevelopApplication::fileOpenRequested); #endif #ifdef WITH_WELCOMEPAGE // make it possible to disable the welcome page, useful for valgrind runs e.g. if (!qEnvironmentVariableIsSet("KDEV_DISABLE_WELCOMEPAGE")) { trySetupWelcomePageView(); } #endif qCDebug(APP) << "Done startup" << "- took:" << timer.elapsed() << "ms"; timer.invalidate(); return app.exec(); } diff --git a/app/plasma/dataengine/kdevelopsessionsengine.cpp b/app/plasma/dataengine/kdevelopsessionsengine.cpp index b84b588f9e..508f5deff7 100644 --- a/app/plasma/dataengine/kdevelopsessionsengine.cpp +++ b/app/plasma/dataengine/kdevelopsessionsengine.cpp @@ -1,161 +1,159 @@ /***************************************************************************** * Copyright (C) 2012 by Eike Hein * * Copyright (C) 2011 by Shaun Reich * * Copyright (C) 2008 by Montel Laurent * * * * 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, see . * *****************************************************************************/ #include "kdevelopsessionsengine.h" #include "kdevelopsessionsservice.h" -#include -#include #include #include #include #include #include KDevelopSessionsEngine::KDevelopSessionsEngine(QObject *parent, const QVariantList &args) : Plasma::DataEngine(parent, args), m_dirWatch(nullptr) { init(); } KDevelopSessionsEngine::~KDevelopSessionsEngine() { } void KDevelopSessionsEngine::init() { m_dirWatch = new KDirWatch( this ); const QStringList sessionDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevelop/sessions", QStandardPaths::LocateDirectory ); for ( int i = 0; i < sessionDirs.count(); ++i ) m_dirWatch->addDir( sessionDirs[i], KDirWatch::WatchSubDirs ); connect(m_dirWatch, &KDirWatch::dirty, this, &KDevelopSessionsEngine::updateSessions); updateSessions(); } Plasma::Service *KDevelopSessionsEngine::serviceForSource(const QString &source) { return new KDevelopSessionsService( this, source ); } QStringList findSessions() { QStringList sessionDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevelop/sessions", QStandardPaths::LocateDirectory); QStringList sessionrcs; Q_FOREACH(const QString& dir, sessionDirs) { QDir d(dir); Q_FOREACH(const QString& sessionDir, d.entryList(QDir::Dirs)) { QDir sd(d.absoluteFilePath(sessionDir)); QString path(sd.filePath("sessionrc")); if(QFile::exists(path)) { sessionrcs += path; } } } return sessionrcs; } void KDevelopSessionsEngine::updateSessions() { QStringList sessionrcs = findSessions(); QHash sessions; QStringList::const_iterator it; for (it = sessionrcs.constBegin(); it != sessionrcs.constEnd(); ++it) { KConfig cfg( *it, KConfig::SimpleConfig ); // Only consider sessions that have open projects. if ( cfg.hasGroup( "General Options" ) && !cfg.group( "General Options" ).readEntry( "Open Projects", "" ).isEmpty() ) { Session session; session.hash = QFileInfo( *it ).dir().dirName(); session.name = cfg.group( "" ).readEntry( "SessionName", "" ); session.description = cfg.group( "" ).readEntry( "SessionPrettyContents", "" ); sessions.insert(session.hash, session); } } QHash::const_iterator it2; for (it2 = sessions.constBegin(); it2 != sessions.constEnd(); ++it2) { const Session& session = it2.value(); if ( !m_currentSessions.contains( session.hash ) ) { // Publish new session. m_currentSessions.insert( session.hash, session ); setData( session.hash, "sessionName", session.name ); setData( session.hash, "sessionString", session.description ); } else { // Publish data changes for older sessions. Session oldSession( m_currentSessions.value(session.hash) ); bool modified = false; if ( session.name != oldSession.name ) { oldSession.name = session.name; modified = true; setData( session.hash, "sessionName", session.name ); } if ( session.description != oldSession.description ) { oldSession.description = session.description; modified = true; setData( session.hash, "sessionString", session.description ); } if ( modified ) m_currentSessions.insert( oldSession.hash, oldSession ); } } QHash::iterator it3 = m_currentSessions.begin(); while ( it3 != m_currentSessions.end() ) { const Session& session = it3.value(); if ( !sessions.contains( session.hash ) ) { removeSource( session.hash ); it3 = m_currentSessions.erase( it3 ); } else ++it3; } } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(kdevelopsessionsengine, KDevelopSessionsEngine, "plasma-dataengine-kdevelopsessions.json") #include "kdevelopsessionsengine.moc" diff --git a/app/plasma/runner/kdevelopsessions.cpp b/app/plasma/runner/kdevelopsessions.cpp index 98a732c2e8..1d1ba4b2ff 100644 --- a/app/plasma/runner/kdevelopsessions.cpp +++ b/app/plasma/runner/kdevelopsessions.cpp @@ -1,186 +1,183 @@ /* * Copyright 2008,2011 Sebastian Kügler * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, 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 Library 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 "kdevelopsessions.h" #include -#include #include #include #include #include #include #include #include #include #include -#include -#include #include K_EXPORT_PLASMA_RUNNER(kdevelopsessions, KDevelopSessions) bool kdevelopsessions_runner_compare_sessions(const Session &s1, const Session &s2) { QCollator c; return c.compare(s1.name, s2.name) < 0; } KDevelopSessions::KDevelopSessions(QObject *parent, const QVariantList& args) : Plasma::AbstractRunner(parent, args) { setObjectName(QStringLiteral("KDevelop Sessions")); setIgnoredTypes(Plasma::RunnerContext::File | Plasma::RunnerContext::Directory | Plasma::RunnerContext::NetworkLocation); m_icon = QIcon::fromTheme(QStringLiteral("kdevelop")); loadSessions(); // listen for changes to the list of kdevelop sessions KDirWatch *historyWatch = new KDirWatch(this); const QStringList sessiondirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/sessions")); foreach (const QString &dir, sessiondirs) { historyWatch->addDir(dir); } connect(historyWatch, &KDirWatch::dirty, this, &KDevelopSessions::loadSessions); connect(historyWatch, &KDirWatch::created, this, &KDevelopSessions::loadSessions); connect(historyWatch, &KDirWatch::deleted, this, &KDevelopSessions::loadSessions); Plasma::RunnerSyntax s(QStringLiteral(":q:"), i18n("Finds KDevelop sessions matching :q:.")); s.addExampleQuery(QStringLiteral("kdevelop :q:")); addSyntax(s); setDefaultSyntax(Plasma::RunnerSyntax(QStringLiteral("kdevelop"), i18n("Lists all the KDevelop editor sessions in your account."))); } KDevelopSessions::~KDevelopSessions() = default; QStringList findSessions() { QStringList sessionDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/sessions"), QStandardPaths::LocateDirectory); QStringList sessionrcs; Q_FOREACH(const QString& dir, sessionDirs) { QDir d(dir); Q_FOREACH(const QString& sessionDir, d.entryList(QDir::Dirs)) { QDir sd(d.absoluteFilePath(sessionDir)); QString path(sd.filePath(QStringLiteral("sessionrc"))); if(QFile::exists(path)) { sessionrcs += path; } } } return sessionrcs; } void KDevelopSessions::loadSessions() { m_sessions.clear(); // Switch kdevelop session: -u // Should we add a match for this option or would that clutter the matches too much? const QStringList list = findSessions(); foreach (const QString &sessionfile, list) { Session session; session.id = sessionfile.section('/', -2, -2); KConfig cfg(sessionfile, KConfig::SimpleConfig); KConfigGroup group = cfg.group(QString()); session.name = group.readEntry("SessionPrettyContents");; m_sessions << session; } std::sort(m_sessions.begin(), m_sessions.end(), kdevelopsessions_runner_compare_sessions); } void KDevelopSessions::match(Plasma::RunnerContext &context) { if (m_sessions.isEmpty()) { return; } QString term = context.query(); if (term.length() < 3) { return; } bool listAll = false; if (term.startsWith(QStringLiteral("kdevelop"), Qt::CaseInsensitive)) { if (term.trimmed().compare(QStringLiteral("kdevelop"), Qt::CaseInsensitive) == 0) { listAll = true; term.clear(); } else if (term.at(8) == QLatin1Char(' ') ) { term.remove(QStringLiteral("kdevelop"), Qt::CaseInsensitive); term = term.trimmed(); } else { term.clear(); } } if (term.isEmpty() && !listAll) { return; } foreach (const Session &session, m_sessions) { if (!context.isValid()) { return; } if (listAll || (!term.isEmpty() && session.name.contains(term, Qt::CaseInsensitive))) { Plasma::QueryMatch match(this); if (listAll) { // All sessions listed, but with a low priority match.setType(Plasma::QueryMatch::ExactMatch); match.setRelevance(0.8); } else { if (session.name.compare(term, Qt::CaseInsensitive) == 0) { // parameter to kdevelop matches session exactly, bump it up! match.setType(Plasma::QueryMatch::ExactMatch); match.setRelevance(1.0); } else { // fuzzy match of the session in "kdevelop $session" match.setType(Plasma::QueryMatch::PossibleMatch); match.setRelevance(0.8); } } match.setIcon(m_icon); match.setData(session.id); match.setText(session.name); match.setSubtext(i18n("Open KDevelop Session")); context.addMatch(match); } } } void KDevelopSessions::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) { Q_UNUSED(context) QString sessionId = match.data().toString(); if (sessionId.isEmpty()) { qWarning() << "No KDevelop session id in match!"; return; } qDebug() << "Open KDevelop session" << sessionId; const QStringList args = {QStringLiteral("--open-session"), sessionId}; KToolInvocation::kdeinitExec(QStringLiteral("kdevelop"), args); } #include "kdevelopsessions.moc" diff --git a/debuggers/common/dialogs/processselection.cpp b/debuggers/common/dialogs/processselection.cpp index 57200f4736..0c4dbbbd4f 100644 --- a/debuggers/common/dialogs/processselection.cpp +++ b/debuggers/common/dialogs/processselection.cpp @@ -1,100 +1,98 @@ /* KDevelop GDB Support * * Copyright 2009 Aleix Pol * * 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 "processselection.h" #include #include #include #include #include #include -#include #include #include -#include #include #include using namespace KDevMI; ProcessSelectionDialog::ProcessSelectionDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("Attach to a Process")); m_processList = new KSysGuardProcessList(this); QVBoxLayout* mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(m_processList); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); connect(m_processList->treeView()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProcessSelectionDialog::selectionChanged); m_processList->treeView()->setSelectionMode(QAbstractItemView::SingleSelection); m_processList->setState(ProcessFilter::UserProcesses); m_processList->setKillButtonVisible(false); m_processList->filterLineEdit()->setFocus(); //m_processList->setPidFilter(qApp->pid()); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); m_attachButton = buttonBox->button(QDialogButtonBox::Ok); m_attachButton->setDefault(true); m_attachButton->setText(i18n("Attach")); m_attachButton->setShortcut(Qt::CTRL | Qt::Key_Return); m_attachButton->setEnabled(false); KConfigGroup config = KSharedConfig::openConfig()->group("GdbProcessSelectionDialog"); m_processList->filterLineEdit()->setText(config.readEntry("filterText", QString())); m_processList->loadSettings(config); restoreGeometry(config.readEntry("dialogGeometry", QByteArray())); } ProcessSelectionDialog::~ProcessSelectionDialog() { KConfigGroup config = KSharedConfig::openConfig()->group("GdbProcessSelectionDialog"); config.writeEntry("filterText", m_processList->filterLineEdit()->text()); m_processList->saveSettings(config); config.writeEntry("dialogGeometry", saveGeometry()); } long int ProcessSelectionDialog::pidSelected() { QList ps=m_processList->selectedProcesses(); Q_ASSERT(ps.count()==1); KSysGuard::Process* process=ps.first(); return process->pid(); } QSize ProcessSelectionDialog::sizeHint() const { return QSize(740, 720); } void ProcessSelectionDialog::selectionChanged(const QItemSelection &selected) { m_attachButton->setEnabled(selected.count()); } diff --git a/debuggers/common/mi/milexer.h b/debuggers/common/mi/milexer.h index 08268ad17d..c647b11132 100644 --- a/debuggers/common/mi/milexer.h +++ b/debuggers/common/mi/milexer.h @@ -1,151 +1,149 @@ /*************************************************************************** * Copyright (C) 2004 by Roberto Raggi * * roberto@kdevelop.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef MILEXER_H #define MILEXER_H -#include -#include #include namespace KDevMI { namespace MI { class MILexer; struct TokenStream; typedef void (MILexer::*scan_fun_ptr)(int *kind); struct Token { int kind; int position; int length; }; struct FileSymbol { QByteArray contents; TokenStream *tokenStream; inline FileSymbol() : tokenStream(nullptr) {} inline ~FileSymbol(); }; struct TokenStream { inline int lookAhead(int n = 0) const { return (m_currentToken + n)->kind; } inline int currentToken() const { return m_currentToken->kind; } inline QByteArray currentTokenText() const { return tokenText(-1); } QByteArray tokenText(int index = 0) const; inline int lineOffset(int line) const { return m_lines.at(line); } void positionAt(int position, int *line, int *column) const; inline void getTokenStartPosition(int index, int *line, int *column) const { positionAt((m_firstToken + index)->position, line, column); } inline void getTokenEndPosition(int index, int *line, int *column) const { Token *tk = m_firstToken + index; positionAt(tk->position + tk->length, line, column); } inline void rewind(int index) { m_currentToken = m_firstToken + index; } inline int cursor() const { return m_currentToken - m_firstToken; } inline void nextToken() { m_currentToken++; m_cursor++; } //private: QByteArray m_contents; QVector m_lines; int m_line; QVector m_tokens; int m_tokensCount; Token *m_firstToken; Token *m_currentToken; int m_cursor; }; class MILexer { public: MILexer(); ~MILexer(); TokenStream *tokenize(const FileSymbol *fileSymbol); private: int nextToken(int &position, int &len); void scanChar(int *kind); void scanUnicodeChar(int *kind); void scanNewline(int *kind); void scanWhiteSpaces(int *kind); void scanStringLiteral(int *kind); void scanNumberLiteral(int *kind); void scanIdentifier(int *kind); void setupScanTable(); private: static bool s_initialized; static scan_fun_ptr s_scan_table[128 + 1]; QByteArray m_contents; int m_ptr; // Cached 'm_contents.length()' int m_length; QVector m_lines; int m_line; QVector m_tokens; int m_tokensCount; int m_cursor; }; inline FileSymbol::~FileSymbol() { delete tokenStream; tokenStream = nullptr; } } // end of MI } // end of KDevMI #endif diff --git a/debuggers/common/mi/miparser.h b/debuggers/common/mi/miparser.h index d9dbbf5140..8ffbf2040f 100644 --- a/debuggers/common/mi/miparser.h +++ b/debuggers/common/mi/miparser.h @@ -1,85 +1,85 @@ /*************************************************************************** * Copyright (C) 2004 by Roberto Raggi * * roberto@kdevelop.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef MIPARSER_H #define MIPARSER_H #include #include "mi.h" #include "milexer.h" -#include +class QString; namespace KDevMI { namespace MI { /** @author Roberto Raggi */ class MIParser { public: MIParser(); ~MIParser(); std::unique_ptr parse(FileSymbol *file); protected: // rules std::unique_ptr parseResultOrAsyncRecord(); std::unique_ptr parsePrompt(); std::unique_ptr parseStreamRecord(); bool parseResult(Result *&result); bool parseValue(Value *&value); bool parseTuple(Value *&value); bool parseList(Value *&value); /** Creates new TupleValue object, writes its address into *value, parses a comma-separated set of values, and adds each new value into (*value)->results. If 'start' and 'end' are not zero, they specify start and end delimiter of the list. Parsing stops when we see 'end' character, or, if 'end' is zero, at the end of input. */ bool parseCSV(TupleValue** value, char start = 0, char end = 0); /** @overload Same as above, but writes into existing tuple. */ bool parseCSV(TupleValue& value, char start = 0, char end = 0); /** Parses a string literal and returns it. Advances the lexer past the literal. Processes C escape sequences in the string. @pre lex->lookAhead(0) == Token_string_literal */ QString parseStringLiteral(); private: MILexer m_lexer; TokenStream *m_lex; }; } // end of namespace MI } // end of namespace KDevMI #endif diff --git a/debuggers/common/mibreakpointcontroller.h b/debuggers/common/mibreakpointcontroller.h index a3b13a42af..161066e24b 100644 --- a/debuggers/common/mibreakpointcontroller.h +++ b/debuggers/common/mibreakpointcontroller.h @@ -1,122 +1,118 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef MIBREAKPOINTCONTROLLER_H #define MIBREAKPOINTCONTROLLER_H #include "dbgglobal.h" #include #include -#include - -class QModelIndex; - namespace KDevMI { namespace MI { struct AsyncRecord; struct ResultRecord; struct Value; } struct BreakpointData { int debuggerId; KDevelop::BreakpointModel::ColumnFlags dirty; KDevelop::BreakpointModel::ColumnFlags sent; KDevelop::BreakpointModel::ColumnFlags errors; bool pending; BreakpointData() : debuggerId(-1) , pending(false) {} }; typedef QSharedPointer BreakpointDataPtr; class MIDebugSession; /** * Handles signals from the editor that relate to breakpoints and the execution * point of the debugger. * We may change, add or remove breakpoints in this class. */ class MIBreakpointController : public KDevelop::IBreakpointController { Q_OBJECT public: explicit MIBreakpointController(MIDebugSession* parent); using IBreakpointController::breakpointModel; /** * Controls whether when duplicate breakpoints are received via async notification from GDB, * one of the duplicates will be deleted. */ void setDeleteDuplicateBreakpoints(bool enable); void breakpointAdded(int row) override; void breakpointModelChanged(int row, KDevelop::BreakpointModel::ColumnFlags columns) override; void breakpointAboutToBeDeleted(int row) override; void debuggerStateChanged(KDevelop::IDebugSession::DebuggerState) override; void notifyBreakpointCreated(const MI::AsyncRecord& r); void notifyBreakpointModified(const MI::AsyncRecord& r); void notifyBreakpointDeleted(const MI::AsyncRecord& r); public Q_SLOTS: void initSendBreakpoints(); private Q_SLOTS: void programStopped(const MI::AsyncRecord &r); private: MIDebugSession* debugSession() const; int breakpointRow(const BreakpointDataPtr& breakpoint); void createBreakpoint(int row); void sendUpdates(int row); void recalculateState(int row); void sendMaybe(KDevelop::Breakpoint *breakpoint) override; void createFromDebugger(const MI::Value& miBkpt); void updateFromDebugger(int row, const MI::Value& miBkpt, KDevelop::BreakpointModel::ColumnFlags lockedColumns = nullptr); int rowFromDebuggerId(int gdbId) const; struct Handler; struct InsertedHandler; struct UpdateHandler; struct DeleteHandler; struct IgnoreChanges; QList m_breakpoints; QList m_pendingDeleted; int m_ignoreChanges = 0; bool m_deleteDuplicateBreakpoints = false; }; } // end of namespace KDevMI #endif // MIBREAKPOINTCONTROLLER_H diff --git a/debuggers/common/midebugjobs.cpp b/debuggers/common/midebugjobs.cpp index 5eb77fe95d..673929624a 100644 --- a/debuggers/common/midebugjobs.cpp +++ b/debuggers/common/midebugjobs.cpp @@ -1,218 +1,217 @@ /* * Common Code for Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * Copyright 2016 Aetf * * 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 "midebugjobs.h" #include "debuglog.h" #include "dialogs/selectcoredialog.h" #include "midebugsession.h" #include "midebuggerplugin.h" #include #include #include #include #include #include #include #include #include #include #include -#include using namespace KDevMI; using namespace KDevelop; MIDebugJob::MIDebugJob(MIDebuggerPlugin* p, ILaunchConfiguration* launchcfg, IExecutePlugin* execute, QObject* parent) : KDevelop::OutputJob(parent) , m_launchcfg(launchcfg) , m_execute(execute) { setCapabilities(Killable); m_session = p->createSession(); connect(m_session, &MIDebugSession::inferiorStdoutLines, this, &MIDebugJob::stdoutReceived); connect(m_session, &MIDebugSession::inferiorStderrLines, this, &MIDebugJob::stderrReceived); connect(m_session, &MIDebugSession::finished, this, &MIDebugJob::done); if (launchcfg->project()) { setObjectName(i18nc("ProjectName: run configuration name", "%1: %2", launchcfg->project()->name(), launchcfg->name())); } else { setObjectName(launchcfg->name()); } } void MIDebugJob::start() { Q_ASSERT(m_execute); QString err; // check if the config is valid QString executable = m_execute->executable(m_launchcfg, err).toLocalFile(); if (!err.isEmpty()) { setError(-1); setErrorText(err); emitResult(); return; } if (!QFileInfo(executable).isExecutable()) { setError(-1); setErrorText(i18n("'%1' is not an executable", executable)); emitResult(); return; } QStringList arguments = m_execute->arguments(m_launchcfg, err); if (!err.isEmpty()) { setError(-1); setErrorText(err); emitResult(); return; } setStandardToolView(IOutputView::DebugView); setBehaviours(IOutputView::Behaviours(IOutputView::AllowUserClose) | KDevelop::IOutputView::AutoScroll); auto model = new KDevelop::OutputModel; model->setFilteringStrategy(OutputModel::NativeAppErrorFilter); setModel(model); setTitle(m_launchcfg->name()); KConfigGroup grp = m_launchcfg->config(); QString startWith = grp.readEntry(Config::StartWithEntry, QString("ApplicationOutput")); if (startWith == "ApplicationOutput") { setVerbosity(Verbose); } else { setVerbosity(Silent); } startOutput(); if (!m_session->startDebugging(m_launchcfg, m_execute)) { done(); } } bool MIDebugJob::doKill() { m_session->stopDebugger(); return true; } void MIDebugJob::stderrReceived(const QStringList& l) { if (OutputModel* m = model()) { m->appendLines(l); } } void MIDebugJob::stdoutReceived(const QStringList& l) { if (OutputModel* m = model()) { m->appendLines(l); } } OutputModel* MIDebugJob::model() { return qobject_cast(OutputJob::model()); } void MIDebugJob::done() { emitResult(); } MIExamineCoreJob::MIExamineCoreJob(MIDebuggerPlugin *plugin, QObject *parent) : KJob(parent) { setCapabilities(Killable); m_session = plugin->createSession(); connect(m_session, &MIDebugSession::finished, this, &MIExamineCoreJob::done); setObjectName(i18n("Debug core file")); } void MIExamineCoreJob::start() { SelectCoreDialog dlg(ICore::self()->uiController()->activeMainWindow()); if (dlg.exec() == QDialog::Rejected) { done(); return; } if (!m_session->examineCoreFile(dlg.executableFile(), dlg.core())) { done(); } } bool MIExamineCoreJob::doKill() { m_session->stopDebugger(); return true; } void MIExamineCoreJob::done() { emitResult(); } MIAttachProcessJob::MIAttachProcessJob(MIDebuggerPlugin *plugin, int pid, QObject *parent) : KJob(parent) , m_pid(pid) { setCapabilities(Killable); m_session = plugin->createSession(); connect(m_session, &MIDebugSession::finished, this, &MIAttachProcessJob::done); setObjectName(i18n("Debug process %1", pid)); } void MIAttachProcessJob::start() { if (!m_session->attachToProcess(m_pid)) { done(); } } bool MIAttachProcessJob::doKill() { m_session->stopDebugger(); return true; } void MIAttachProcessJob::done() { emitResult(); } diff --git a/debuggers/common/midebugsession.cpp b/debuggers/common/midebugsession.cpp index 89c53ec4bb..910ecb05d1 100644 --- a/debuggers/common/midebugsession.cpp +++ b/debuggers/common/midebugsession.cpp @@ -1,1316 +1,1317 @@ /* * Common code for debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * Copyright 2016 Aetf * * 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 . * */ #include "midebugsession.h" #include "debuglog.h" #include "midebugger.h" #include "mivariable.h" #include "mi/mi.h" #include "mi/micommand.h" #include "mi/micommandqueue.h" #include "stty.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; MIDebugSession::MIDebugSession(MIDebuggerPlugin *plugin) : m_procLineMaker(new ProcessLineMaker(this)) , m_commandQueue(new CommandQueue) , m_sessionState(NotStartedState) , m_debugger(nullptr) , m_debuggerState(s_dbgNotStarted | s_appNotStarted) , m_stateReloadInProgress(false) , m_stateReloadNeeded(false) , m_tty(nullptr) , m_hasCrashed(false) , m_sourceInitFile(true) , m_plugin(plugin) { // setup signals connect(m_procLineMaker, &ProcessLineMaker::receivedStdoutLines, this, &MIDebugSession::inferiorStdoutLines); connect(m_procLineMaker, &ProcessLineMaker::receivedStderrLines, this, &MIDebugSession::inferiorStderrLines); // forward tty output to process line maker connect(this, &MIDebugSession::inferiorTtyStdout, m_procLineMaker, &ProcessLineMaker::slotReceivedStdout); connect(this, &MIDebugSession::inferiorTtyStderr, m_procLineMaker, &ProcessLineMaker::slotReceivedStderr); // FIXME: see if this still works //connect(statusBarIndicator, SIGNAL(doubleClicked()), // controller, SLOT(explainDebuggerStatus())); // FIXME: reimplement / re-enable //connect(this, SIGNAL(addWatchVariable(QString)), controller->variables(), SLOT(slotAddWatchVariable(QString))); //connect(this, SIGNAL(evaluateExpression(QString)), controller->variables(), SLOT(slotEvaluateExpression(QString))); } MIDebugSession::~MIDebugSession() { qCDebug(DEBUGGERCOMMON) << "Destroying MIDebugSession"; // Deleting the session involves shutting down gdb nicely. // When were attached to a process, we must first detach so that the process // can continue running as it was before being attached. gdb is quite slow to // detach from a process, so we must process events within here to get a "clean" // shutdown. if (!debuggerStateIsOn(s_dbgNotStarted)) { stopDebugger(); } } IDebugSession::DebuggerState MIDebugSession::state() const { return m_sessionState; } QMap & MIDebugSession::variableMapping() { return m_allVariables; } MIVariable* MIDebugSession::findVariableByVarobjName(const QString &varobjName) const { if (m_allVariables.count(varobjName) == 0) return nullptr; return m_allVariables.value(varobjName); } void MIDebugSession::markAllVariableDead() { for (auto i = m_allVariables.begin(), e = m_allVariables.end(); i != e; ++i) { i.value()->markAsDead(); } m_allVariables.clear(); } bool MIDebugSession::restartAvaliable() const { if (debuggerStateIsOn(s_attached) || debuggerStateIsOn(s_core)) { return false; } else { return true; } } bool MIDebugSession::startDebugger(ILaunchConfiguration *cfg) { qCDebug(DEBUGGERCOMMON) << "Starting new debugger instance"; if (m_debugger) { qCWarning(DEBUGGERCOMMON) << "m_debugger object still exists"; delete m_debugger; m_debugger = nullptr; } m_debugger = createDebugger(); m_debugger->setParent(this); // output signals connect(m_debugger, &MIDebugger::applicationOutput, this, [this](const QString &output) { auto lines = output.split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts); for (auto &line : lines) { int p = line.length(); while (p >= 1 && (line[p-1] == '\r' || line[p-1] == '\n')) p--; if (p != line.length()) line.remove(p, line.length() - p); } emit inferiorStdoutLines(lines); }); connect(m_debugger, &MIDebugger::userCommandOutput, this, &MIDebugSession::debuggerUserCommandOutput); connect(m_debugger, &MIDebugger::internalCommandOutput, this, &MIDebugSession::debuggerInternalCommandOutput); connect(m_debugger, &MIDebugger::debuggerInternalOutput, this, &MIDebugSession::debuggerInternalOutput); // state signals connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::inferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::inferiorRunning); // internal handlers connect(m_debugger, &MIDebugger::ready, this, &MIDebugSession::slotDebuggerReady); connect(m_debugger, &MIDebugger::exited, this, &MIDebugSession::slotDebuggerExited); connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::slotInferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::slotInferiorRunning); connect(m_debugger, &MIDebugger::notification, this, &MIDebugSession::processNotification); // start the debugger. Do this after connecting all signals so that initial // debugger output, and important events like the debugger died are reported. QStringList extraArguments; if (!m_sourceInitFile) extraArguments << "--nx"; auto config = cfg ? cfg->config() // FIXME: this is only used when attachToProcess or examineCoreFile. // Change to use a global launch configuration when calling : KConfigGroup(KSharedConfig::openConfig(), "GDB Config"); if (!m_debugger->start(config, extraArguments)) { // debugger failed to start, ensure debugger and session state are correctly updated. setDebuggerStateOn(s_dbgFailedStart); return false; } // FIXME: here, we should wait until the debugger is up and waiting for input. // Then, clear s_dbgNotStarted // It's better to do this right away so that the state bit is always correct. setDebuggerStateOff(s_dbgNotStarted); // Initialise debugger. At this stage debugger is sitting wondering what to do, // and to whom. initializeDebugger(); qCDebug(DEBUGGERCOMMON) << "Debugger instance started"; return true; } bool MIDebugSession::startDebugging(ILaunchConfiguration* cfg, IExecutePlugin* iexec) { qCDebug(DEBUGGERCOMMON) << "Starting new debug session"; Q_ASSERT(cfg); Q_ASSERT(iexec); // Ensure debugger is started first if (debuggerStateIsOn(s_appNotStarted)) { emit showMessage(i18n("Running program"), 1000); } if (debuggerStateIsOn(s_dbgNotStarted)) { if (!startDebugger(cfg)) return false; } if (debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "Tried to run when debugger shutting down"; return false; } // Only dummy err here, actual erros have been checked already in the job and we don't get here if there were any QString err; QString executable = iexec->executable(cfg, err).toLocalFile(); configInferior(cfg, iexec, executable); // Set up the tty for the inferior bool config_useExternalTerminal = iexec->useTerminal(cfg); QString config_ternimalName = iexec->terminal(cfg); if (!config_ternimalName.isEmpty()) { // the external terminal cmd contains additional arguments, just get the terminal name config_ternimalName = KShell::splitArgs(config_ternimalName).first(); } m_tty.reset(new STTY(config_useExternalTerminal, config_ternimalName)); if (!config_useExternalTerminal) { connect(m_tty.get(), &STTY::OutOutput, this, &MIDebugSession::inferiorTtyStdout); connect(m_tty.get(), &STTY::ErrOutput, this, &MIDebugSession::inferiorTtyStderr); } QString tty(m_tty->getSlave()); if (tty.isEmpty()) { KMessageBox::information(qApp->activeWindow(), m_tty->lastError(), i18n("warning")); m_tty.reset(nullptr); return false; } addCommand(InferiorTtySet, tty); // Change the working directory to the correct one QString dir = iexec->workingDirectory(cfg).toLocalFile(); if (dir.isEmpty()) { dir = QFileInfo(executable).absolutePath(); } addCommand(EnvironmentCd, '"' + dir + '"'); // Set the run arguments QStringList arguments = iexec->arguments(cfg, err); if (!arguments.isEmpty()) addCommand(ExecArguments, KShell::joinArgs(arguments)); // Do other debugger specific config options and actually start the inferior program if (!execInferior(cfg, iexec, executable)) { return false; } QString config_startWith = cfg->config().readEntry(Config::StartWithEntry, QStringLiteral("ApplicationOutput")); if (config_startWith == "GdbConsole") { emit raiseDebuggerConsoleViews(); } else if (config_startWith == "FrameStack") { emit raiseFramestackViews(); } else { // ApplicationOutput is raised in DebugJob (by setting job to Verbose/Silent) } return true; } // FIXME: use same configuration process as startDebugging bool MIDebugSession::attachToProcess(int pid) { qCDebug(DEBUGGERCOMMON) << "Attach to process" << pid; emit showMessage(i18n("Attaching to process %1", pid), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } setDebuggerStateOn(s_attached); //set current state to running, after attaching we will get *stopped response setDebuggerStateOn(s_appRunning); addCommand(TargetAttach, QString::number(pid), this, &MIDebugSession::handleTargetAttach, CmdHandlesError); addCommand(new SentinelCommand(breakpointController(), &MIBreakpointController::initSendBreakpoints)); raiseEvent(connected_to_program); emit raiseFramestackViews(); return true; } void MIDebugSession::handleTargetAttach(const MI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not attach debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } bool MIDebugSession::examineCoreFile(const QUrl &debugee, const QUrl &coreFile) { emit showMessage(i18n("Examining core file %1", coreFile.toLocalFile()), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } // FIXME: support non-local URLs if (!loadCoreFile(nullptr, debugee.toLocalFile(), coreFile.toLocalFile())) { return false; } raiseEvent(program_state_changed); return true; } #define ENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) void MIDebugSession::setSessionState(DebuggerState state) { qCDebug(DEBUGGERCOMMON) << "Session state changed to" << ENUM_NAME(IDebugSession, DebuggerState, state) << "(" << state << ")"; if (state != m_sessionState) { m_sessionState = state; emit stateChanged(state); } } bool MIDebugSession::debuggerStateIsOn(DBGStateFlags state) const { return m_debuggerState & state; } DBGStateFlags MIDebugSession::debuggerState() const { return m_debuggerState; } void MIDebugSession::setDebuggerStateOn(DBGStateFlags stateOn) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, m_debuggerState | stateOn); m_debuggerState |= stateOn; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::setDebuggerStateOff(DBGStateFlags stateOff) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, m_debuggerState & ~stateOff); m_debuggerState &= ~stateOff; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::setDebuggerState(DBGStateFlags newState) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, newState); m_debuggerState = newState; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::debuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState) { int delta = oldState ^ newState; if (delta) { QString out; #define STATE_CHECK(name) \ do { \ if (delta & name) { \ out += ((newState & name) ? " +" : " -"); \ out += #name; \ delta &= ~name; \ } \ } while (0) STATE_CHECK(s_dbgNotStarted); STATE_CHECK(s_appNotStarted); STATE_CHECK(s_programExited); STATE_CHECK(s_attached); STATE_CHECK(s_core); STATE_CHECK(s_shuttingDown); STATE_CHECK(s_dbgBusy); STATE_CHECK(s_appRunning); STATE_CHECK(s_dbgNotListening); STATE_CHECK(s_automaticContinue); #undef STATE_CHECK for (unsigned int i = 0; delta != 0 && i < 32; ++i) { if (delta & (1 << i)) { delta &= ~(1 << i); out += ((1 << i) & newState) ? " +" : " -"; out += QString::number(i); } } qCDebug(DEBUGGERCOMMON) << "Debugger state change:" << out; } } void MIDebugSession::handleDebuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState) { QString message; DebuggerState oldSessionState = state(); DebuggerState newSessionState = oldSessionState; DBGStateFlags changedState = oldState ^ newState; if (newState & s_dbgNotStarted) { if (changedState & s_dbgNotStarted) { message = i18n("Debugger stopped"); emit finished(); } if (oldSessionState != NotStartedState || newState & s_dbgFailedStart) { newSessionState = EndedState; } } else { if (newState & s_appNotStarted) { if (oldSessionState == NotStartedState || oldSessionState == StartingState) { newSessionState = StartingState; } else { newSessionState = StoppedState; } } else if (newState & s_programExited) { if (changedState & s_programExited) { message = i18n("Process exited"); } newSessionState = StoppedState; } else if (newState & s_appRunning) { if (changedState & s_appRunning) { message = i18n("Application is running"); } newSessionState = ActiveState; } else { if (changedState & s_appRunning) { message = i18n("Application is paused"); } newSessionState = PausedState; } } // And now? :-) qCDebug(DEBUGGERCOMMON) << "Debugger state changed to: " << newState << message; if (!message.isEmpty()) emit showMessage(message, 3000); emit debuggerStateChanged(oldState, newState); // must be last, since it can lead to deletion of the DebugSession if (newSessionState != oldSessionState) { setSessionState(newSessionState); } } void MIDebugSession::restartDebugger() { // We implement restart as kill + slotRun, as opposed as plain "run" // command because kill + slotRun allows any special logic in slotRun // to apply for restart. // // That includes: // - checking for out-of-date project // - special setup for remote debugging. // // Had we used plain 'run' command, restart for remote debugging simply // would not work. if (!debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) { // FIXME: s_dbgBusy or m_debugger->isReady()? if (debuggerStateIsOn(s_dbgBusy)) { interruptDebugger(); } // The -exec-abort is not implemented in gdb // addCommand(ExecAbort); addCommand(NonMI, "kill"); } run(); } void MIDebugSession::stopDebugger() { if (debuggerStateIsOn(s_dbgNotStarted)) { // we are force to stop even before debugger started, just reset qCDebug(DEBUGGERCOMMON) << "Stopping debugger when it's not started"; return; } m_commandQueue->clear(); qCDebug(DEBUGGERCOMMON) << "try stopping debugger"; if (debuggerStateIsOn(s_shuttingDown) || !m_debugger) return; setDebuggerStateOn(s_shuttingDown); qCDebug(DEBUGGERCOMMON) << "stopping debugger"; // Get debugger's attention if it's busy. We need debugger to be at the // command line so we can stop it. if (!m_debugger->isReady()) { qCDebug(DEBUGGERCOMMON) << "debugger busy on shutdown - interruping"; interruptDebugger(); } // If the app is attached then we release it here. This doesn't stop // the app running. if (debuggerStateIsOn(s_attached)) { addCommand(TargetDetach); emit debuggerUserCommandOutput("(gdb) detach\n"); } // Now try to stop debugger running. addCommand(GdbExit); emit debuggerUserCommandOutput("(gdb) quit"); // We cannot wait forever, kill gdb after 5 seconds if it's not yet quit QPointer guarded_this(this); QTimer::singleShot(5000, [guarded_this](){ if (guarded_this) { if (!guarded_this->debuggerStateIsOn(s_programExited) && guarded_this->debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "debugger not shutdown - killing"; guarded_this->m_debugger->kill(); guarded_this->setDebuggerState(s_dbgNotStarted | s_appNotStarted); guarded_this->raiseEvent(debugger_exited); } } }); emit reset(); } void MIDebugSession::interruptDebugger() { Q_ASSERT(m_debugger); // Explicitly send the interrupt in case something went wrong with the usual // ensureGdbListening logic. m_debugger->interrupt(); addCommand(ExecInterrupt, QString(), CmdInterrupt); } void MIDebugSession::run() { if (debuggerStateIsOn(s_appNotStarted|s_dbgNotStarted|s_shuttingDown)) return; addCommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning); } void MIDebugSession::runToCursor() { if (IDocument* doc = ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) runUntil(doc->url(), cursor.line() + 1); } } void MIDebugSession::jumpToCursor() { if (IDocument* doc = ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) jumpTo(doc->url(), cursor.line() + 1); } } void MIDebugSession::stepOver() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecNext, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepIntoInstruction() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecStepInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepInto() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecStep, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepOverInstruction() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecNextInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepOut() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecFinish, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::runUntil(const QUrl& url, int line) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!url.isValid()) { addCommand(ExecUntil, QString::number(line), CmdMaybeStartsRunning | CmdTemporaryRun); } else { addCommand(ExecUntil, QString("%1:%2").arg(url.toLocalFile()).arg(line), CmdMaybeStartsRunning | CmdTemporaryRun); } } void MIDebugSession::runUntil(const QString& address) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { addCommand(ExecUntil, QString("*%1").arg(address), CmdMaybeStartsRunning | CmdTemporaryRun); } } void MIDebugSession::jumpTo(const QUrl& url, int line) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (url.isValid()) { addCommand(NonMI, QString("tbreak %1:%2").arg(url.toLocalFile()).arg(line)); addCommand(NonMI, QString("jump %1:%2").arg(url.toLocalFile()).arg(line)); } } void MIDebugSession::jumpToMemoryAddress(const QString& address) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { addCommand(NonMI, QString("tbreak *%1").arg(address)); addCommand(NonMI, QString("jump *%1").arg(address)); } } void MIDebugSession::addUserCommand(const QString& cmd) { auto usercmd = createUserCommand(cmd); if (!usercmd) return; queueCmd(usercmd); // User command can theoreticall modify absolutely everything, // so need to force a reload. // We can do it right now, and don't wait for user command to finish // since commands used to reload all view will be executed after // user command anyway. if (!debuggerStateIsOn(s_appNotStarted) && !debuggerStateIsOn(s_programExited)) raiseEvent(program_state_changed); } MICommand *MIDebugSession::createUserCommand(const QString &cmd) const { MICommand *res = nullptr; if (!cmd.isEmpty() && cmd[0].isDigit()) { // Add a space to the beginning, so debugger won't get confused if the // command starts with a number (won't mix it up with command token added) res = new UserCommand(MI::NonMI, " " + cmd); } else { res = new UserCommand(MI::NonMI, cmd); } return res; } MICommand *MIDebugSession::createCommand(CommandType type, const QString& arguments, CommandFlags flags) const { return new MICommand(type, arguments, flags); } void MIDebugSession::addCommand(MICommand* cmd) { queueCmd(cmd); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, MI::CommandFlags flags) { queueCmd(createCommand(type, arguments, flags)); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, MI::MICommandHandler *handler, MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(handler); queueCmd(cmd); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, const MI::FunctionCommandHandler::Function& callback, MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(callback); queueCmd(cmd); } // Fairly obvious that we'll add whatever command you give me to a queue // Not quite so obvious though is that if we are going to run again. then any // information requests become redundent and must be removed. // We also try and run whatever command happens to be at the head of // the queue. void MIDebugSession::queueCmd(MICommand *cmd) { if (debuggerStateIsOn(s_dbgNotStarted)) { KMessageBox::information( qApp->activeWindow(), i18n("Gdb command sent when debugger is not running
" "The command was:
%1", cmd->initialString()), i18n("Internal error")); return; } if (m_stateReloadInProgress) cmd->setStateReloading(true); m_commandQueue->enqueue(cmd); qCDebug(DEBUGGERCOMMON) << "QUEUE: " << cmd->initialString() << (m_stateReloadInProgress ? "(state reloading)" : "") << m_commandQueue->count() << "pending"; bool varCommandWithContext= (cmd->type() >= MI::VarAssign && cmd->type() <= MI::VarUpdate && cmd->type() != MI::VarDelete); bool stackCommandWithContext = (cmd->type() >= MI::StackInfoDepth && cmd->type() <= MI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { if (cmd->thread() == -1) qCDebug(DEBUGGERCOMMON) << "\t--thread will be added on execution"; if (cmd->frame() == -1) qCDebug(DEBUGGERCOMMON) << "\t--frame will be added on execution"; } setDebuggerStateOn(s_dbgBusy); raiseEvent(debugger_busy); executeCmd(); } void MIDebugSession::executeCmd() { Q_ASSERT(m_debugger); if (debuggerStateIsOn(s_dbgNotListening) && m_commandQueue->haveImmediateCommand()) { // We may have to call this even while a command is currently executing, because // debugger can get into a state where a command such as ExecRun does not send a response // while the inferior is running. ensureDebuggerListening(); } if (!m_debugger->isReady()) return; MICommand* currentCmd = m_commandQueue->nextCommand(); if (!currentCmd) return; if (currentCmd->flags() & (CmdMaybeStartsRunning | CmdInterrupt)) { setDebuggerStateOff(s_automaticContinue); } if (currentCmd->flags() & CmdMaybeStartsRunning) { // GDB can be in a state where it is listening for commands while the program is running. // However, when we send a command such as ExecContinue in this state, GDB may return to // the non-listening state without acknowledging that the ExecContinue command has even // finished, let alone sending a new notification about the program's running state. // So let's be extra cautious about ensuring that we will wake GDB up again if required. setDebuggerStateOn(s_dbgNotListening); } bool varCommandWithContext= (currentCmd->type() >= MI::VarAssign && currentCmd->type() <= MI::VarUpdate && currentCmd->type() != MI::VarDelete); bool stackCommandWithContext = (currentCmd->type() >= MI::StackInfoDepth && currentCmd->type() <= MI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { // Most var commands should be executed in the context // of the selected thread and frame. if (currentCmd->thread() == -1) currentCmd->setThread(frameStackModel()->currentThread()); if (currentCmd->frame() == -1) currentCmd->setFrame(frameStackModel()->currentFrame()); } QString commandText = currentCmd->cmdToSend(); bool bad_command = false; QString message; int length = commandText.length(); // No i18n for message since it's mainly for debugging. if (length == 0) { // The command might decide it's no longer necessary to send // it. if (SentinelCommand* sc = dynamic_cast(currentCmd)) { qCDebug(DEBUGGERCOMMON) << "SEND: sentinel command, not sending"; sc->invokeHandler(); } else { qCDebug(DEBUGGERCOMMON) << "SEND: command " << currentCmd->initialString() << "changed its mind, not sending"; } delete currentCmd; executeCmd(); return; } else { if (commandText[length-1] != '\n') { bad_command = true; message = "Debugger command does not end with newline"; } } if (bad_command) { KMessageBox::information(qApp->activeWindow(), i18n("Invalid debugger command
%1", message), i18n("Invalid debugger command")); executeCmd(); return; } m_debugger->execute(currentCmd); } void MIDebugSession::ensureDebuggerListening() { Q_ASSERT(m_debugger); // Note: we don't use interruptDebugger() here since // we don't want to queue more commands before queuing a command m_debugger->interrupt(); setDebuggerStateOn(s_interruptSent); if (debuggerStateIsOn(s_appRunning)) setDebuggerStateOn(s_automaticContinue); setDebuggerStateOff(s_dbgNotListening); } void MIDebugSession::destroyCmds() { m_commandQueue->clear(); } // FIXME: I don't fully remember what is the business with // m_stateReloadInProgress and whether we can lift it to the // generic level. void MIDebugSession::raiseEvent(event_t e) { if (e == program_exited || e == debugger_exited) { m_stateReloadInProgress = false; } if (e == program_state_changed) { m_stateReloadInProgress = true; qCDebug(DEBUGGERCOMMON) << "State reload in progress\n"; } IDebugSession::raiseEvent(e); if (e == program_state_changed) { m_stateReloadInProgress = false; } } bool KDevMI::MIDebugSession::hasCrashed() const { return m_hasCrashed; } void MIDebugSession::slotDebuggerReady() { Q_ASSERT(m_debugger); m_stateReloadInProgress = false; executeCmd(); if (m_debugger->isReady()) { /* There is nothing in the command queue and no command is currently executing. */ if (debuggerStateIsOn(s_automaticContinue)) { if (!debuggerStateIsOn(s_appRunning)) { qCDebug(DEBUGGERCOMMON) << "Posting automatic continue"; addCommand(ExecContinue, QString(), CmdMaybeStartsRunning); } setDebuggerStateOff(s_automaticContinue); return; } if (m_stateReloadNeeded && !debuggerStateIsOn(s_appRunning)) { qCDebug(DEBUGGERCOMMON) << "Finishing program stop"; // Set to false right now, so that if 'actOnProgramPauseMI_part2' // sends some commands, we won't call it again when handling replies // from that commands. m_stateReloadNeeded = false; reloadProgramState(); } qCDebug(DEBUGGERCOMMON) << "No more commands"; setDebuggerStateOff(s_dbgBusy); raiseEvent(debugger_ready); } } void MIDebugSession::slotDebuggerExited(bool abnormal, const QString &msg) { /* Technically speaking, GDB is likely not to kill the application, and we should have some backup mechanism to make sure the application is killed by KDevelop. But even if application stays around, we no longer can control it in any way, so mark it as exited. */ setDebuggerStateOn(s_appNotStarted); setDebuggerStateOn(s_dbgNotStarted); setDebuggerStateOn(s_programExited); setDebuggerStateOff(s_shuttingDown); if (!msg.isEmpty()) emit showMessage(msg, 3000); if (abnormal) { /* The error is reported to user in MIDebugger now. KMessageBox::information( KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Debugger exited abnormally" "

This is likely a bug in GDB. " "Examine the gdb output window and then stop the debugger"), i18n("Debugger exited abnormally")); */ // FIXME: not sure if the following still applies. // Note: we don't stop the debugger here, becuse that will hide gdb // window and prevent the user from finding the exact reason of the // problem. } /* FIXME: raiseEvent is handled across multiple places where we explicitly * stop/kill the debugger, a better way is to let the debugger itself report * its exited event. */ // raiseEvent(debugger_exited); } void MIDebugSession::slotInferiorStopped(const MI::AsyncRecord& r) { /* By default, reload all state on program stop. */ m_stateReloadNeeded = true; setDebuggerStateOff(s_appRunning); setDebuggerStateOff(s_dbgNotListening); QString reason; if (r.hasField("reason")) reason = r["reason"].literal(); if (reason == "exited-normally" || reason == "exited") { if (r.hasField("exit-code")) { programNoApp(i18n("Exited with return code: %1", r["exit-code"].literal())); } else { programNoApp(i18n("Exited normally")); } m_stateReloadNeeded = false; return; } if (reason == "exited-signalled") { programNoApp(i18n("Exited on signal %1", r["signal-name"].literal())); m_stateReloadNeeded = false; return; } if (reason == "watchpoint-scope") { QString number = r["wpnum"].literal(); // FIXME: shuld remove this watchpoint // But first, we should consider if removing all // watchpoinst on program exit is the right thing to // do. addCommand(ExecContinue, QString(), CmdMaybeStartsRunning); m_stateReloadNeeded = false; return; } bool wasInterrupt = false; if (reason == "signal-received") { QString name = r["signal-name"].literal(); QString user_name = r["signal-meaning"].literal(); // SIGINT is a "break into running program". // We do this when the user set/mod/clears a breakpoint but the // application is running. // And the user does this to stop the program also. if (name == "SIGINT" && debuggerStateIsOn(s_interruptSent)) { wasInterrupt = true; } else { // Whenever we have a signal raised then tell the user, but don't // end the program as we want to allow the user to look at why the // program has a signal that's caused the prog to stop. // Continuing from SIG FPE/SEGV will cause a "Cannot ..." and // that'll end the program. programFinished(i18n("Program received signal %1 (%2)", name, user_name)); m_hasCrashed = true; } } if (!reason.contains("exited")) { // FIXME: we should immediately update the current thread and // frame in the framestackmodel, so that any user actions // are in that thread. However, the way current framestack model // is implemented, we can't change thread id until we refresh // the entire list of threads -- otherwise we might set a thread // id that is not already in the list, and it will be upset. //Indicates if program state should be reloaded immediately. bool updateState = false; if (r.hasField("frame")) { const MI::Value& frame = r["frame"]; QString file, line, addr; if (frame.hasField("fullname")) file = frame["fullname"].literal();; if (frame.hasField("line")) line = frame["line"].literal(); if (frame.hasField("addr")) addr = frame["addr"].literal(); // gdb counts lines from 1 and we don't setCurrentPosition(QUrl::fromLocalFile(file), line.toInt() - 1, addr); updateState = true; } if (updateState) { reloadProgramState(); } } setDebuggerStateOff(s_interruptSent); if (!wasInterrupt) setDebuggerStateOff(s_automaticContinue); } void MIDebugSession::slotInferiorRunning() { setDebuggerStateOn(s_appRunning); raiseEvent(program_running); if (m_commandQueue->haveImmediateCommand() || (m_debugger->currentCommand() && (m_debugger->currentCommand()->flags() & (CmdImmediately | CmdInterrupt)))) { ensureDebuggerListening(); } else { setDebuggerStateOn(s_dbgNotListening); } } void MIDebugSession::processNotification(const MI::AsyncRecord & async) { if (async.reason == "thread-group-started") { setDebuggerStateOff(s_appNotStarted | s_programExited); } else if (async.reason == "thread-group-exited") { setDebuggerStateOn(s_programExited); } else if (async.reason == "library-loaded") { // do nothing } else if (async.reason == "breakpoint-created") { breakpointController()->notifyBreakpointCreated(async); } else if (async.reason == "breakpoint-modified") { breakpointController()->notifyBreakpointModified(async); } else if (async.reason == "breakpoint-deleted") { breakpointController()->notifyBreakpointDeleted(async); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled notification: " << async.reason; } } void MIDebugSession::reloadProgramState() { raiseEvent(program_state_changed); m_stateReloadNeeded = false; } // There is no app anymore. This can be caused by program exiting // an invalid program specified or ... // gdb is still running though, but only the run command (may) make sense // all other commands are disabled. void MIDebugSession::programNoApp(const QString& msg) { qCDebug(DEBUGGERCOMMON) << msg; setDebuggerState(s_appNotStarted | s_programExited | (m_debuggerState & s_shuttingDown)); destroyCmds(); // The application has existed, but it's possible that // some of application output is still in the pipe. We use // different pipes to communicate with gdb and to get application // output, so "exited" message from gdb might have arrived before // last application output. Get this last bit. // Note: this method can be called when we open an invalid // core file. In that case, tty_ won't be set. if (m_tty){ m_tty->readRemaining(); // Tty is no longer usable, delete it. Without this, QSocketNotifier // will continuously bomd STTY with signals, so we need to either disable // QSocketNotifier, or delete STTY. The latter is simpler, since we can't // reuse it for future debug sessions anyway. m_tty.reset(nullptr); } stopDebugger(); raiseEvent(program_exited); raiseEvent(debugger_exited); emit showMessage(msg, 0); programFinished(msg); } void MIDebugSession::programFinished(const QString& msg) { QString m = QString("*** %0 ***").arg(msg.trimmed()); emit inferiorStderrLines(QStringList(m)); /* Also show message in gdb window, so that users who prefer to look at gdb window know what's up. */ emit debuggerUserCommandOutput(m); } void MIDebugSession::explainDebuggerStatus() { MICommand* currentCmd_ = m_debugger->currentCommand(); QString information = i18np("1 command in queue\n", "%1 commands in queue\n", m_commandQueue->count()) + i18ncp("Only the 0 and 1 cases need to be translated", "1 command being processed by gdb\n", "%1 commands being processed by gdb\n", (currentCmd_ ? 1 : 0)) + i18n("Debugger state: %1\n", m_debuggerState); if (currentCmd_) { QString extra = i18n("Current command class: '%1'\n" "Current command text: '%2'\n" "Current command original text: '%3'\n", typeid(*currentCmd_).name(), currentCmd_->cmdToSend(), currentCmd_->initialString()); information += extra; } KMessageBox::information(qApp->activeWindow(), information, i18n("Debugger status")); } // There is no app anymore. This can be caused by program exiting // an invalid program specified or ... // gdb is still running though, but only the run command (may) make sense // all other commands are disabled. void MIDebugSession::handleNoInferior(const QString& msg) { qCDebug(DEBUGGERCOMMON) << msg; setDebuggerState(s_appNotStarted | s_programExited | (debuggerState() & s_shuttingDown)); destroyCmds(); // The application has existed, but it's possible that // some of application output is still in the pipe. We use // different pipes to communicate with gdb and to get application // output, so "exited" message from gdb might have arrived before // last application output. Get this last bit. // Note: this method can be called when we open an invalid // core file. In that case, tty_ won't be set. if (m_tty){ m_tty->readRemaining(); // Tty is no longer usable, delete it. Without this, QSocketNotifier // will continuously bomd STTY with signals, so we need to either disable // QSocketNotifier, or delete STTY. The latter is simpler, since we can't // reuse it for future debug sessions anyway. m_tty.reset(nullptr); } stopDebugger(); raiseEvent(program_exited); raiseEvent(debugger_exited); emit showMessage(msg, 0); handleInferiorFinished(msg); } void MIDebugSession::handleInferiorFinished(const QString& msg) { QString m = QStringLiteral("*** %0 ***").arg(msg.trimmed()); emit inferiorStderrLines(QStringList(m)); /* Also show message in gdb window, so that users who prefer to look at gdb window know what's up. */ emit debuggerUserCommandOutput(m); } // FIXME: connect to debugger's slot. void MIDebugSession::defaultErrorHandler(const MI::ResultRecord& result) { QString msg = result["msg"].literal(); if (msg.contains("No such process")) { setDebuggerState(s_appNotStarted|s_programExited); raiseEvent(program_exited); return; } KMessageBox::information( qApp->activeWindow(), i18n("Debugger error" "

Debugger reported the following error:" "

%1", result["msg"].literal()), i18n("Debugger error")); // Error most likely means that some change made in GUI // was not communicated to the gdb, so GUI is now not // in sync with gdb. Resync it. // // Another approach is to make each widget reload it content // on errors from commands that it sent, but that's too complex. // Errors are supposed to happen rarely, so full reload on error // is not a big deal. Well, maybe except for memory view, but // it's no auto-reloaded anyway. // // Also, don't reload state on errors appeared during state // reloading! if (!m_debugger->currentCommand()->stateReloading()) raiseEvent(program_state_changed); } void MIDebugSession::setSourceInitFile(bool enable) { m_sourceInitFile = enable; } diff --git a/debuggers/common/mivariable.h b/debuggers/common/mivariable.h index 952dbd871c..2619203b0c 100644 --- a/debuggers/common/mivariable.h +++ b/debuggers/common/mivariable.h @@ -1,88 +1,87 @@ /* * MI based debugger specific Variable * * Copyright 2009 Vladimir Prus * * 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 MIVARIABLE_H #define MIVARIABLE_H #include "mi/mi.h" #include -#include #include class CreateVarobjHandler; class FetchMoreChildrenHandler; class SetFormatHandler; namespace KDevMI { class MIDebugSession; class MIVariable : public KDevelop::Variable { public: MIVariable(MIDebugSession *session, KDevelop::TreeModel* model, KDevelop::TreeItem* parent, const QString& expression, const QString& display = ""); ~MIVariable(); /* FIXME: should eventually remove, so that existence of varobjs is fully encapsulalated inside GdbVariable. */ const QString& varobj() const; void handleUpdate(const MI::Value& var); /* Called when debugger dies. Clears the association between varobj names and Variable instances. */ void markAsDead(); bool canSetFormat() const override { return true; } protected: // Variable overrides void attachMaybe(QObject *callback, const char *callbackMethod) override; void fetchMoreChildren() override; void formatChanged() override; protected: // Internal friend class ::CreateVarobjHandler; friend class ::FetchMoreChildrenHandler; friend class ::SetFormatHandler; /** * Construct a MIVariable child directly from a MI value */ MIVariable *createChild(const MI::Value &child); QString enquotedExpression() const; virtual QString formatValue(const QString &rawValue) const; bool sessionIsAlive() const; void setVarobj(const QString& v); QString varobj_; QPointer debugSession; // How many children should be fetched in one // increment. static const int fetchStep = 5; }; } // end of KDevMI #endif diff --git a/debuggers/common/registers/modelsmanager.h b/debuggers/common/registers/modelsmanager.h index ad2315f198..974e63efab 100644 --- a/debuggers/common/registers/modelsmanager.h +++ b/debuggers/common/registers/modelsmanager.h @@ -1,101 +1,100 @@ /* * Class to manage register models. * Copyright (C) 2013 Vlas Puhov * * 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 MODELSMANAGER_H #define MODELSMANAGER_H #include #include #include #include #include #include "registercontroller.h" class QAbstractItemView; -class QStandardItemModel; class QStandardItem; class QModelIndex; namespace KDevMI { class Models; class IRegisterController; struct Register; struct RegistersGroup; class GroupsName; class ModelsManager : public QObject { Q_OBJECT public: explicit ModelsManager(QObject* parent = nullptr); ~ModelsManager() override; ///Adds new @p view with @p name, if not yet registered. ///All views removed after debug session ended. ///@return: Name of the new view. QString addView(QAbstractItemView* view); void setController(IRegisterController* rc); ///Sets @p format for the @p group, if format is valid. Does nothing otherwise. void setFormat(const QString& group, Format format); ///Returns all supported formats for @p group. The first one is current. QVector formats(const QString& group) const; ///Sets @p mode for the @p group, if mode is valid. Does nothing otherwise. void setMode(const QString& group, Mode mode); ///Returns all supported modes for @p group. The first one is current. QVector modes(const QString& group) const; Q_SIGNALS: ///Emitted when a register in a model changed. Updated value should be send to the debugger. void registerChanged(const Register&); public Q_SLOTS: void updateModelForGroup(const RegistersGroup& group); ///Forcedly updates registers in @p group. void updateRegisters(const QString& group = QString()); private: void save(const GroupsName&); void load(const GroupsName&); private Q_SLOTS: void flagChanged(const QModelIndex&); void itemChanged(QStandardItem*); private: QScopedPointer m_models; IRegisterController* m_controller; KConfigGroup m_config; }; } // end of namespace KDevMI #endif // MODELSMANAGER_H diff --git a/debuggers/common/stringhelpers.cpp b/debuggers/common/stringhelpers.cpp index 57bbb3f13a..7be2e8a4a6 100644 --- a/debuggers/common/stringhelpers.cpp +++ b/debuggers/common/stringhelpers.cpp @@ -1,286 +1,284 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "stringhelpers.h" #include "debuglog.h" #include #include -#include #include #include -#include namespace Utils { bool parenFits( QChar c1, QChar c2 ) { if( c1 == '<' && c2 == '>' ) return true; else if( c1 == '(' && c2 == ')' ) return true; else if( c1 == '[' && c2 == ']' ) return true; else if( c1 == '{' && c2 == '}' ) return true; else return false; } bool isParen( QChar c1 ) { if( c1 == '<' || c1 == '>' ) return true; else if( c1 == '(' || c1 == ')' ) return true; else if( c1 == '[' || c1 == ']' ) return true; else if( c1 == '{' || c1 == '}' ) return true; else return false; } bool isTypeParen( QChar c1 ) { if( c1 == '<' || c1 == '>' ) return true; else return false; } bool isTypeOpenParen( QChar c1 ) { if( c1 == '<' ) return true; else return false; } bool isTypeCloseParen( QChar c1 ) { if( c1 == '>' ) return true; else return false; } bool isLeftParen( QChar c1 ) { if( c1 == '<' ) return true; else if( c1 == '(' ) return true; else if( c1 == '[' ) return true; else if( c1 == '{' ) return true; else return false; } enum { T_ACCESS, T_PAREN, T_BRACKET, T_IDE, T_UNKNOWN, T_TEMP }; int expressionAt( const QString& text, int index ) { if( index == 0 ) return 0; int last = T_UNKNOWN; int start = index; --index; while ( index > 0 ) { while ( index > 0 && text[ index ].isSpace() ) { --index; } QChar ch = text[ index ]; QString ch2 = text.mid( index - 1, 2 ); if ( ( last != T_IDE ) && ( ch.isLetterOrNumber() || ch == '_' ) ) { while ( index > 0 && ( text[ index ].isLetterOrNumber() || text[ index ] == '_' ) ) { --index; } last = T_IDE; } else if ( last != T_IDE && ch == ')' ) { int count = 0; while ( index > 0 ) { QChar ch = text[ index ]; if ( ch == '(' ) { ++count; } else if ( ch == ')' ) { --count; } --index; if ( count == 0 ) { //index; last = T_PAREN; break; } } } else if ( last != T_IDE && ch == '>' && ch2 != "->" ) { int count = 0; while ( index > 0 ) { QChar ch = text[ index ]; if ( ch == '<' ) { ++count; } else if ( ch == '>' ) { --count; } else if ( count == 0 ) { //--index; last = T_TEMP; break; } --index; } } else if ( ch == ']' ) { int count = 0; while ( index > 0 ) { QChar ch = text[ index ]; if ( ch == '[' ) { ++count; } else if ( ch == ']' ) { --count; } else if ( count == 0 ) { //--index; last = T_BRACKET; break; } --index; } } else if ( ch == '.' ) { --index; last = T_ACCESS; } else if ( ch2 == "::" ) { index -= 2; last = T_ACCESS; } else if ( ch2 == "->" ) { index -= 2; last = T_ACCESS; } else { if ( start > index ) { ++index; } last = T_UNKNOWN; break; } } ///If we're at the first item, the above algorithm cannot be used safely, ///so just determine whether the sign is valid for the beginning of an expression, if it isn't reject it. if ( index == 0 && start > index && !( text[ index ].isLetterOrNumber() || text[ index ] == '_' || text[ index ] == ':' ) ) { ++index; } return index; } QString quoteExpression(QString expr) { return quote(expr, '"'); } QString unquoteExpression(QString expr) { return unquote(expr, false); } QString quote(QString str, char quoteCh) { str.replace("\\", "\\\\").replace(quoteCh, QStringLiteral("\\") + quoteCh); return str.prepend(quoteCh).append(quoteCh); } QString unquote(const QString &str, bool unescapeUnicode, char quoteCh) { if (str.startsWith(quoteCh) && str.endsWith(quoteCh)) { QString res; res.reserve(str.length()); bool esc = false; int type = 0; QString escSeq; escSeq.reserve(4); // skip beginning and ending quoteCh, no need for str = str.mid(1, str.length() - 2) for (int i = 1; i != str.length() - 1; i++) { auto ch = str[i]; if (esc) { switch (ch.unicode()) { case '\\': if (type != 0) { escSeq += ch; qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; res += '\\'; res += escSeq; escSeq.clear(); esc = false; type = 0; } else { res.append('\\'); // escSeq.clear(); // escSeq must be empty. esc = false; } break; case 'u': case 'x': if (type != 0 || !unescapeUnicode) { escSeq += ch; qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; res += '\\'; res += escSeq; escSeq.clear(); esc = false; type = 0; } else { type = ch == 'u' ? 1 : 2; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': escSeq += ch; if (type == 0) { qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; res += '\\'; res += escSeq; escSeq.clear(); esc = false; type = 0; } else { // \uNNNN // \xNN if (escSeq.length() == (type == 1 ? 4 : 2)) { // no need to handle error, we know for sure escSeq is '[0-9a-fA-F]+' auto code = escSeq.toInt(nullptr, 16); res += QChar(code); escSeq.clear(); esc = false; type = 0; } } break; default: if (type == 0 && ch == quoteCh) { res += ch; } else { escSeq += ch; qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; res += '\\'; res += escSeq; escSeq.clear(); } esc = false; type = 0; break; } } else { if (ch == '\\') { esc = true; continue; } res += ch; } } return res; } else { return str; } } } // end of namespace Utils diff --git a/debuggers/common/widgets/debuggerconsoleview.cpp b/debuggers/common/widgets/debuggerconsoleview.cpp index 4c0f644f04..b2c8f6affd 100644 --- a/debuggers/common/widgets/debuggerconsoleview.cpp +++ b/debuggers/common/widgets/debuggerconsoleview.cpp @@ -1,409 +1,408 @@ /* * Debugger Console View * * Copyright 2003 John Birch * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2016 Aetf * * 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 . * */ #include "debuggerconsoleview.h" #include "debuglog.h" #include "midebuggerplugin.h" #include "midebugsession.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include -#include +#include 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("Debugger Console

" "Shows all debugger commands being executed. " "You can also issue any other debugger command while debugging.

")); 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"), this); 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.
" "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()); updateColors(); } void DebuggerConsoleView::changeEvent(QEvent *event) { if (event->type() == QEvent::PaletteChange) { updateColors(); } } 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->setMargin(0); vbox->addWidget(m_textView); vbox->addWidget(m_toolBar); setLayout(vbox); m_cmdEditor = new KHistoryComboBox(this); m_cmdEditor->setDuplicatesEnabled(false); connect(m_cmdEditor, static_cast(&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::setReplacePrompt(const QString& prompt) { m_alterPrompt = prompt; } 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 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); 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::toHtmlEscaped(QString text) { text = text.toHtmlEscaped(); text.replace('\n', "
"); return text; } QString DebuggerConsoleView::colorify(QString text, const QColor& color) { text = "" + text + ""; 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 = toHtmlEscaped(line); if (colored.startsWith("(gdb)")) { if (!m_alterPrompt.isEmpty()) { colored.replace(0, 5, m_alterPrompt); } colored = colorify(colored, m_stdColor); } 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 = toHtmlEscaped(line); colored = colorify(colored, 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(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()); } diff --git a/debuggers/common/widgets/debuggerconsoleview.h b/debuggers/common/widgets/debuggerconsoleview.h index d64101dc46..83f79f4cc5 100644 --- a/debuggers/common/widgets/debuggerconsoleview.h +++ b/debuggers/common/widgets/debuggerconsoleview.h @@ -1,165 +1,164 @@ /* * Debugger Console View * * Copyright 2003 John Birch * Copyright 2007 Hamish Rodda * Copyright 2016 Aetf * * 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 . * */ #ifndef DEBUGGERCONSOLEVIEW_H #define DEBUGGERCONSOLEVIEW_H #include -#include #include #include #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: explicit DebuggerConsoleView(MIDebuggerPlugin *plugin, QWidget *parent = nullptr); ~DebuggerConsoleView(); /** * Whether show a button allowing user to interrupt debugger execution. */ void setShowInterrupt(bool enable); /** * If set to a nonempty string, the default "(gdb)" prompt will be replaced. * This only affects output lines after the call. */ void setReplacePrompt(const QString &prompt); 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(); /** * escape html meta characters and handle line break */ QString toHtmlEscaped(QString text); 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); void changeEvent(QEvent *e) 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; QString m_alterPrompt; }; } // end of namespace KDevMI #endif // DEBUGGERCONSOLEVIEW_H diff --git a/debuggers/common/widgets/disassemblewidget.cpp b/debuggers/common/widgets/disassemblewidget.cpp index ddf99a2311..672f035972 100644 --- a/debuggers/common/widgets/disassemblewidget.cpp +++ b/debuggers/common/widgets/disassemblewidget.cpp @@ -1,541 +1,539 @@ /* * GDB Debugger Support * * Copyright 2000 John Birch * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2013 Vlas Puhov * * 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 "disassemblewidget.h" #include "midebuggerplugin.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include "registers/registersmanager.h" #include #include #include #include #include #include -#include #include #include #include #include -#include -#include +#include +#include #include #include -#include #include using namespace KDevMI; using namespace KDevMI::MI; SelectAddressDialog::SelectAddressDialog(QWidget* parent) : QDialog(parent) { m_ui.setupUi(this); setWindowTitle(i18n("Address Selector")); connect(m_ui.comboBox, &KHistoryComboBox::editTextChanged, this, &SelectAddressDialog::validateInput); connect(m_ui.comboBox, static_cast(&KHistoryComboBox::returnPressed), this, &SelectAddressDialog::itemSelected); } QString SelectAddressDialog::address() const { return hasValidAddress() ? m_ui.comboBox->currentText() : QString(); } bool SelectAddressDialog::hasValidAddress() const { bool ok; m_ui.comboBox->currentText().toLongLong(&ok, 16); return ok; } void SelectAddressDialog::updateOkState() { m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(hasValidAddress()); } void SelectAddressDialog::validateInput() { updateOkState(); } void SelectAddressDialog::itemSelected() { QString text = m_ui.comboBox->currentText(); if( hasValidAddress() && m_ui.comboBox->findText(text) < 0 ) m_ui.comboBox->addItem(text); } DisassembleWindow::DisassembleWindow(QWidget *parent, DisassembleWidget* widget) : QTreeWidget(parent) { /*context menu commands */{ m_selectAddrAction = new QAction(i18n("Change &address"), this); m_selectAddrAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(m_selectAddrAction, &QAction::triggered, widget, &DisassembleWidget::slotChangeAddress); m_jumpToLocation = new QAction(QIcon::fromTheme("debug-execute-to-cursor"), i18n("&Jump to Cursor"), this); m_jumpToLocation->setWhatsThis(i18n("Sets the execution pointer to the current cursor position.")); connect(m_jumpToLocation,&QAction::triggered, widget, &DisassembleWidget::jumpToCursor); m_runUntilCursor = new QAction(QIcon::fromTheme("debug-run-cursor"), i18n("&Run to Cursor"), this); m_runUntilCursor->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); connect(m_runUntilCursor,&QAction::triggered, widget, &DisassembleWidget::runToCursor); m_disassemblyFlavorAtt = new QAction(i18n("&AT&&T"), this); m_disassemblyFlavorAtt->setToolTip(i18n("GDB will use the AT&T disassembly flavor (e.g. mov 0xc(%ebp),%eax).")); m_disassemblyFlavorAtt->setData(DisassemblyFlavorATT); m_disassemblyFlavorAtt->setCheckable(true); m_disassemblyFlavorIntel = new QAction(i18n("&Intel"), this); m_disassemblyFlavorIntel->setToolTip(i18n("GDB will use the Intel disassembly flavor (e.g. mov eax, DWORD PTR [ebp+0xc]).")); m_disassemblyFlavorIntel->setData(DisassemblyFlavorIntel); m_disassemblyFlavorIntel->setCheckable(true); m_disassemblyFlavorActionGroup = new QActionGroup(this); m_disassemblyFlavorActionGroup->setExclusive(true); m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorAtt); m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorIntel); connect(m_disassemblyFlavorActionGroup, &QActionGroup::triggered, widget, &DisassembleWidget::setDisassemblyFlavor); } } void DisassembleWindow::setDisassemblyFlavor(DisassemblyFlavor flavor) { switch(flavor) { default: case DisassemblyFlavorUnknown: m_disassemblyFlavorAtt->setChecked(false); m_disassemblyFlavorIntel->setChecked(false); break; case DisassemblyFlavorATT: m_disassemblyFlavorAtt->setChecked(true); m_disassemblyFlavorIntel->setChecked(false); break; case DisassemblyFlavorIntel: m_disassemblyFlavorAtt->setChecked(false); m_disassemblyFlavorIntel->setChecked(true); break; } } void DisassembleWindow::contextMenuEvent(QContextMenuEvent *e) { QMenu popup(this); popup.addAction(m_selectAddrAction); popup.addAction(m_jumpToLocation); popup.addAction(m_runUntilCursor); QMenu* disassemblyFlavorMenu = popup.addMenu(i18n("Disassembly flavor")); disassemblyFlavorMenu->addAction(m_disassemblyFlavorAtt); disassemblyFlavorMenu->addAction(m_disassemblyFlavorIntel); popup.exec(e->globalPos()); } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ DisassembleWidget::DisassembleWidget(MIDebuggerPlugin* plugin, QWidget *parent) : QWidget(parent), active_(false), lower_(0), upper_(0), address_(0), m_splitter(new KDevelop::AutoOrientedSplitter(this)) { QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QHBoxLayout* controlsLayout = new QHBoxLayout; topLayout->addLayout(controlsLayout); { // initialize disasm/registers views topLayout->addWidget(m_splitter); //topLayout->setMargin(0); m_disassembleWindow = new DisassembleWindow(m_splitter, this); m_disassembleWindow->setWhatsThis(i18n("Machine code display

" "A machine code view into your running " "executable with the current instruction " "highlighted. You can step instruction by " "instruction using the debuggers toolbar " "buttons of \"step over\" instruction and " "\"step into\" instruction.")); m_disassembleWindow->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_disassembleWindow->setSelectionMode(QTreeWidget::SingleSelection); m_disassembleWindow->setColumnCount(ColumnCount); m_disassembleWindow->setUniformRowHeights(true); m_disassembleWindow->setRootIsDecorated(false); m_disassembleWindow->setHeaderLabels(QStringList() << "" << i18n("Address") << i18n("Function") << i18n("Instruction")); m_splitter->setStretchFactor(0, 1); m_splitter->setContentsMargins(0, 0, 0, 0); m_registersManager = new RegistersManager(m_splitter); m_config = KSharedConfig::openConfig()->group("Disassemble/Registers View"); QByteArray state = m_config.readEntry("splitterState", QByteArray()); if (!state.isEmpty()) { m_splitter->restoreState(state); } } setLayout(topLayout); setWindowIcon( QIcon::fromTheme("system-run", windowIcon()) ); setWindowTitle(i18n("Disassemble/Registers View")); KDevelop::IDebugController* pDC=KDevelop::ICore::self()->debugController(); Q_ASSERT(pDC); connect(pDC, &KDevelop::IDebugController::currentSessionChanged, this, &DisassembleWidget::currentSessionChanged); connect(plugin, &MIDebuggerPlugin::reset, this, &DisassembleWidget::slotDeactivate); m_dlg = new SelectAddressDialog(this); // show the data if debug session is active KDevelop::IDebugSession* pS = pDC->currentSession(); currentSessionChanged(pS); if(pS && !pS->currentAddr().isEmpty()) slotShowStepInSource(pS->currentUrl(), pS->currentLine(), pS->currentAddr()); } void DisassembleWidget::jumpToCursor() { MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if (s && s->isRunning()) { QString address = m_disassembleWindow->selectedItems().at(0)->text(Address); s->jumpToMemoryAddress(address); } } void DisassembleWidget::runToCursor(){ MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if (s && s->isRunning()) { QString address = m_disassembleWindow->selectedItems().at(0)->text(Address); s->runUntil(address); } } void DisassembleWidget::currentSessionChanged(KDevelop::IDebugSession* s) { MIDebugSession *session = qobject_cast(s); enableControls( session != nullptr ); // disable if session closed m_registersManager->setSession(session); if (session) { connect(session, &MIDebugSession::showStepInSource, this, &DisassembleWidget::slotShowStepInSource); connect(session,&MIDebugSession::showStepInDisassemble,this, &DisassembleWidget::update); } } /***************************************************************************/ DisassembleWidget::~DisassembleWidget() { m_config.writeEntry("splitterState", m_splitter->saveState()); } /***************************************************************************/ bool DisassembleWidget::displayCurrent() { if(address_ < lower_ || address_ > upper_) return false; bool bFound=false; for (int line=0; line < m_disassembleWindow->topLevelItemCount(); line++) { QTreeWidgetItem* item = m_disassembleWindow->topLevelItem(line); unsigned long address = item->text(Address).toULong(&ok,16); if (address == address_) { // put cursor at start of line and highlight the line m_disassembleWindow->setCurrentItem(item); static const QIcon icon = QIcon::fromTheme(QStringLiteral("go-next")); item->setIcon(Icon, icon); bFound = true; // need to process all items to clear icons } else if(!item->icon(Icon).isNull()) item->setIcon(Icon, QIcon()); } return bFound; } /***************************************************************************/ void DisassembleWidget::slotActivate(bool activate) { qCDebug(DEBUGGERCOMMON) << "Disassemble widget active: " << activate; if (active_ != activate) { active_ = activate; if (active_) { updateDisassemblyFlavor(); m_registersManager->updateRegisters(); if (!displayCurrent()) disassembleMemoryRegion(); } } } /***************************************************************************/ void DisassembleWidget::slotShowStepInSource(const QUrl&, int, const QString& currentAddress) { update(currentAddress); } void DisassembleWidget::updateExecutionAddressHandler(const ResultRecord& r) { const Value& content = r["asm_insns"]; const Value& pc = content[0]; if( pc.hasField("address") ){ QString addr = pc["address"].literal(); address_ = addr.toULong(&ok,16); disassembleMemoryRegion(addr); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryRegion(const QString& from, const QString& to) { MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) return; //only get $pc if (from.isEmpty()){ s->addCommand(DataDisassemble, "-s \"$pc\" -e \"$pc+1\" -- 0", this, &DisassembleWidget::updateExecutionAddressHandler); }else{ QString cmd = (to.isEmpty())? QString("-s %1 -e \"%1 + 256\" -- 0").arg(from ): QString("-s %1 -e %2+1 -- 0").arg(from).arg(to); // if both addr set s->addCommand(DataDisassemble, cmd, this, &DisassembleWidget::disassembleMemoryHandler); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryHandler(const ResultRecord& r) { const Value& content = r["asm_insns"]; QString currentFunction; m_disassembleWindow->clear(); for(int i = 0; i < content.size(); ++i) { const Value& line = content[i]; QString addr, fct, offs, inst; if( line.hasField("address") ) addr = line["address"].literal(); if( line.hasField("func-name") ) fct = line["func-name"].literal(); if( line.hasField("offset") ) offs = line["offset"].literal(); if( line.hasField("inst") ) inst = line["inst"].literal(); //We use offset at the same column where function is. if(currentFunction == fct){ if(!fct.isEmpty()){ fct = QString("+") + offs; } }else { currentFunction = fct; } m_disassembleWindow->addTopLevelItem(new QTreeWidgetItem(m_disassembleWindow, QStringList() << QString() << addr << fct << inst)); if (i == 0) { lower_ = addr.toULong(&ok,16); } else if (i == content.size()-1) { upper_ = addr.toULong(&ok,16); } } displayCurrent(); m_disassembleWindow->resizeColumnToContents(Icon); // make Icon always visible m_disassembleWindow->resizeColumnToContents(Address); // make entire address always visible } void DisassembleWidget::showEvent(QShowEvent*) { slotActivate(true); //it doesn't work for large names of functions // for (int i = 0; i < m_disassembleWindow->model()->columnCount(); ++i) // m_disassembleWindow->resizeColumnToContents(i); } void DisassembleWidget::hideEvent(QHideEvent*) { slotActivate(false); } void DisassembleWidget::slotDeactivate() { slotActivate(false); } void DisassembleWidget::enableControls(bool enabled) { m_disassembleWindow->setEnabled(enabled); } void DisassembleWidget::slotChangeAddress() { if(!m_dlg) return; m_dlg->updateOkState(); if (!m_disassembleWindow->selectedItems().isEmpty()) { m_dlg->setAddress(m_disassembleWindow->selectedItems().first()->text(Address)); } if (m_dlg->exec() == QDialog::Rejected) return; unsigned long addr = m_dlg->address().toULong(&ok,16); if (addr < lower_ || addr > upper_ || !displayCurrent()) disassembleMemoryRegion(m_dlg->address()); } void SelectAddressDialog::setAddress ( const QString& address ) { m_ui.comboBox->setCurrentItem ( address, true ); } void DisassembleWidget::update(const QString &address) { if (!active_) { return; } address_ = address.toULong(&ok, 16); if (!displayCurrent()) { disassembleMemoryRegion(); } m_registersManager->updateRegisters(); } void DisassembleWidget::setDisassemblyFlavor(QAction* action) { MIDebugSession* s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) { return; } DisassemblyFlavor disassemblyFlavor = static_cast(action->data().toInt()); QString cmd; switch(disassemblyFlavor) { default: // unknown flavor, do not build a GDB command break; case DisassemblyFlavorATT: cmd = QStringLiteral("disassembly-flavor att"); break; case DisassemblyFlavorIntel: cmd = QStringLiteral("disassembly-flavor intel"); break; } qCDebug(DEBUGGERCOMMON) << "Disassemble widget set " << cmd; if (!cmd.isEmpty()) { s->addCommand(GdbSet, cmd, this, &DisassembleWidget::setDisassemblyFlavorHandler); } } void DisassembleWidget::setDisassemblyFlavorHandler(const ResultRecord& r) { if (r.reason == "done" && active_) { disassembleMemoryRegion(); } } void DisassembleWidget::updateDisassemblyFlavor() { MIDebugSession* s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) { return; } s->addCommand(GdbShow, QStringLiteral("disassembly-flavor"), this, &DisassembleWidget::showDisassemblyFlavorHandler); } void DisassembleWidget::showDisassemblyFlavorHandler(const ResultRecord& r) { const Value& value = r["value"]; qCDebug(DEBUGGERCOMMON) << "Disassemble widget disassembly flavor" << value.literal(); DisassemblyFlavor disassemblyFlavor = DisassemblyFlavorUnknown; if (value.literal() == "att") { disassemblyFlavor = DisassemblyFlavorATT; } else if (value.literal() == "intel") { disassemblyFlavor = DisassemblyFlavorIntel; } else if (value.literal() == "default") { disassemblyFlavor = DisassemblyFlavorATT; } m_disassembleWindow->setDisassemblyFlavor(disassemblyFlavor); } diff --git a/debuggers/gdb/debuggerplugin.h b/debuggers/gdb/debuggerplugin.h index 1bd00603aa..c72981a014 100644 --- a/debuggers/gdb/debuggerplugin.h +++ b/debuggers/gdb/debuggerplugin.h @@ -1,93 +1,81 @@ /* * GDB Debugger Support * Copyright 2016 Aetf * * 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 . * */ #ifndef _DEBUGGERPART_H_ #define _DEBUGGERPART_H_ #include "config.h" -#include -#include -#include +#include #include #include -#include #include #include #include "midebuggerplugin.h" #include "debugsession.h" -class QLabel; -class QMenu; -class QDBusInterface; -class QSignalMapper; -class ProcessWidget; - -class KToolBar; -class QAction; - class GdbLauncher; namespace KDevelop { class Context; class ProcessLineMaker; } namespace KDevMI { class DisassembleWidget; namespace GDB { class GDBOutputWidget; class MemoryViewerWidget; class CppDebuggerPlugin : public MIDebuggerPlugin { Q_OBJECT public: friend class DebugSession; explicit CppDebuggerPlugin(QObject *parent, const QVariantList & = QVariantList()); ~CppDebuggerPlugin() override; void unload() override; DebugSession *createSession() override; void unloadToolviews() override; void setupToolviews() override; private: void setupExecutePlugin(KDevelop::IPlugin* plugin, bool load); DebuggerToolFactory* disassemblefactory; DebuggerToolFactory* gdbfactory; DebuggerToolFactory* memoryviewerfactory; QHash m_launchers; }; } // end of namespace GDB } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/debuggertracingdialog.cpp b/debuggers/gdb/debuggertracingdialog.cpp index ca01ebbe72..654c2385bf 100644 --- a/debuggers/gdb/debuggertracingdialog.cpp +++ b/debuggers/gdb/debuggertracingdialog.cpp @@ -1,127 +1,124 @@ /* * Dialog for configuring breakpoint tracing. * * Copyright 2006 Vladimir Prus * * 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 "debuggertracingdialog.h" #include "breakpoint.h" -#include -#include -#include #include #include /* WARNING: this code was not yet ported to KDevelop4 and is unused, but is intended to be ported. */ using namespace KDevMI::GDB; DebuggerTracingDialog ::DebuggerTracingDialog(Breakpoint* bp, QWidget* parent) : QDialog(parent), bp_(bp) { setupUi(this); expressions->setButtons(KEditListBox::Add | KEditListBox::Remove); connect(enable, SIGNAL(stateChanged(int)), this, SLOT(enableOrDisable(int))); connect(enableCustomFormat, SIGNAL(stateChanged(int)), this, SLOT(enableOrDisableCustomFormat(int))); enable->setChecked(bp_->tracingEnabled()); expressions->setItems(bp_->tracedExpressions()); enableCustomFormat->setChecked(bp_->traceFormatStringEnabled()); customFormat->setText(bp_->traceFormatString()); enableOrDisable(enable->isChecked()); // Go away if the breakpoint does connect(bp_, SIGNAL(destroyed(QObject*)), this, SLOT(reject())); } void DebuggerTracingDialog::enableOrDisable(int state) { bool enable = (state == Qt::Checked); expressionsLabel->setEnabled(enable); expressions->setEnabled(enable); enableCustomFormat->setEnabled(enable); customFormat->setEnabled(enable && enableCustomFormat->isChecked()); } void DebuggerTracingDialog::enableOrDisableCustomFormat(int state) { customFormat->setEnabled(state == Qt::Checked); } void DebuggerTracingDialog::accept() { // Check that if we use format string, // the number of expression is not larget than the number of // format specifiers bool ok = true; if (enableCustomFormat->isChecked()) { QString s = customFormat->text(); int percent_count = 0; for (int i = 0; i < s.length(); ++i) if (s[i] == '%') { if (i+1 < s.length()) { if (s[i+1] != '%') { ++percent_count; } else { // Double % ++i; } } } if (percent_count < expressions->items().count()) { ok = false; KMessageBox::error( this, "Not enough format specifiers" "

The number of format specifiers in the custom format " "string is less than the number of expressions. Either remove " "some expressions or edit the format string.", "Not enough format specifiers"); } } if (ok) { bp_->setTracingEnabled(enable->isChecked()); bp_->setTracedExpressions(expressions->items()); bp_->setTraceFormatStringEnabled(enableCustomFormat->isChecked()); bp_->setTraceFormatString(customFormat->text()); QDialog::accept(); } } diff --git a/debuggers/gdb/debuggertracingdialog.ui b/debuggers/gdb/debuggertracingdialog.ui index f23e37fc1a..39583ab52a 100644 --- a/debuggers/gdb/debuggertracingdialog.ui +++ b/debuggers/gdb/debuggertracingdialog.ui @@ -1,129 +1,128 @@ DebuggerTracingDialog 0 0 438 409 Tracing Configuration false <b>Custom format string</b> <p>Specify a C-style format string that will be used when printing the chosen expression. For example: <p align="center"> <tt>Tracepoint 1: g = %d</tt></p> If custom format string is not enabled, names and values of all expressions will be printed, using "%d" as format specifier for all expressions. false Expressions to print: false <b>Enable tracing</b> <p>Tracing is a mechanism to automatically print values of the chosen expressions and continue execution when breakpoint is hit. You can think of it as printf debugging that does not require modifying the source.</p> Enable tracing false Custom format string false QDialogButtonBox::Cancel|QDialogButtonBox::Ok KEditListBox QGroupBox

keditlistbox.h
keditlistbox.h - klineedit.h buttonBox accepted() DebuggerTracingDialog accept() 307 384 63 8 buttonBox rejected() DebuggerTracingDialog reject() 400 385 342 8 diff --git a/debuggers/gdb/debugsession.h b/debuggers/gdb/debugsession.h index 8016397f23..f5493ba1db 100644 --- a/debuggers/gdb/debugsession.h +++ b/debuggers/gdb/debugsession.h @@ -1,111 +1,105 @@ /* * GDB Debugger Support * * 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 GDB_DEBUGSESSION_H #define GDB_DEBUGSESSION_H #include "midebugsession.h" #include "dbgglobal.h" #include "gdb.h" #include "gdbbreakpointcontroller.h" #include "gdbframestackmodel.h" #include "variablecontroller.h" #include "mi/mi.h" - #include -#include -#include - - class IExecutePlugin; -class KToolBar; namespace KDevelop { class ProcessLineMaker; class ILaunchConfiguration; } namespace KDevMI { class STTY; namespace MI { class MICommand; class CommandQueue; } namespace GDB { class CppDebuggerPlugin; class DebugSession : public MIDebugSession { Q_OBJECT public: explicit DebugSession(CppDebuggerPlugin *plugin = nullptr); ~DebugSession() override; BreakpointController * breakpointController() const override; VariableController * variableController() const override; GdbFrameStackModel * frameStackModel() const override; /// FIXME: only used in unit test currently, potentially could /// be made a user configurable option. /// Whether turn off auto-disable ASLR when starting inferiors void setAutoDisableASLR(bool enable); protected: GdbDebugger *createDebugger() const override; void initializeDebugger() override; void configInferior(KDevelop::ILaunchConfiguration* cfg, IExecutePlugin* iexec, const QString&) override; bool execInferior(KDevelop::ILaunchConfiguration* cfg, IExecutePlugin*, const QString& executable) override; bool loadCoreFile(KDevelop::ILaunchConfiguration *cfg, const QString &debugee, const QString &corefile) override; private Q_SLOTS: void handleVersion(const QStringList& s); void handleFileExecAndSymbols(const MI::ResultRecord& r); void handleCoreFile(const MI::ResultRecord& r); private: friend class GdbTest; BreakpointController *m_breakpointController; VariableController *m_variableController; GdbFrameStackModel *m_frameStackModel; bool m_autoDisableASLR; }; } // end of namespace GDB } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/gdb.cpp b/debuggers/gdb/gdb.cpp index f3d24fc0f3..5462ab3340 100644 --- a/debuggers/gdb/gdb.cpp +++ b/debuggers/gdb/gdb.cpp @@ -1,99 +1,98 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * Copyright 2016 Aetf * * 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 "gdb.h" #include "dbgglobal.h" #include "debuglog.h" -#include #include #include #include #include #include #include #include using namespace KDevMI::GDB; using namespace KDevMI::MI; GdbDebugger::GdbDebugger(QObject* parent) : MIDebugger(parent) { } GdbDebugger::~GdbDebugger() { } bool GdbDebugger::start(KConfigGroup& config, const QStringList& extraArguments) { // FIXME: verify that default value leads to something sensible QUrl gdbUrl = config.readEntry(Config::GdbPathEntry, QUrl()); if (gdbUrl.isEmpty()) { debuggerExecutable_ = QStringLiteral("gdb"); } else { // FIXME: verify its' a local path. debuggerExecutable_ = gdbUrl.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); } QStringList arguments = extraArguments; arguments << "--interpreter=mi2" << "-quiet"; QUrl shell = config.readEntry(Config::DebuggerShellEntry, QUrl()); if(!shell.isEmpty()) { qCDebug(DEBUGGERGDB) << "have shell" << shell; QString shell_without_args = shell.toLocalFile().split(QChar(' ')).first(); QFileInfo info(shell_without_args); /*if( info.isRelative() ) { shell_without_args = build_dir + "/" + shell_without_args; info.setFile( shell_without_args ); }*/ if(!info.exists()) { KMessageBox::information( qApp->activeWindow(), i18n("Could not locate the debugging shell '%1'.", shell_without_args ), i18n("Debugging Shell Not Found") ); return false; } arguments.insert(0, debuggerExecutable_); arguments.insert(0, shell.toLocalFile()); process_->setShellCommand(KShell::joinArgs(arguments)); } else { process_->setProgram(debuggerExecutable_, arguments); } process_->start(); qCDebug(DEBUGGERGDB) << "Starting GDB with command" << shell.toLocalFile() + QLatin1Char(' ') + debuggerExecutable_ + QLatin1Char(' ') + arguments.join(QLatin1Char(' ')); qCDebug(DEBUGGERGDB) << "GDB process pid:" << process_->pid(); emit userCommandOutput(shell.toLocalFile() + QLatin1Char(' ') + debuggerExecutable_ + QLatin1Char(' ') + arguments.join(QLatin1Char(' ')) + QLatin1Char('\n')); return true; } diff --git a/debuggers/gdb/gdbconfigpage.cpp b/debuggers/gdb/gdbconfigpage.cpp index bd28bfd16e..fe65550349 100644 --- a/debuggers/gdb/gdbconfigpage.cpp +++ b/debuggers/gdb/gdbconfigpage.cpp @@ -1,193 +1,191 @@ /* * GDB Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * * 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 "gdbconfigpage.h" #include #include #include -#include #include -#include #include #include #include #include #include #include #include #include #include #include #include "dbgglobal.h" #include "debugsession.h" #include "debuggerplugin.h" #include "midebugjobs.h" #include "ui_gdbconfigpage.h" #include #include using namespace KDevelop; namespace Config = KDevMI::GDB::Config; GdbConfigPage::GdbConfigPage( QWidget* parent ) : LaunchConfigurationPage(parent), ui( new Ui::GdbConfigPage ) { ui->setupUi( this ); ui->kcfg_gdbPath->setMode(KFile::File|KFile::ExistingOnly|KFile::LocalOnly); connect(ui->kcfg_asmDemangle, &QCheckBox::toggled, this, &GdbConfigPage::changed); connect(ui->kcfg_configGdbScript, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); //connect(ui->kcfg_dbgTerminal, SIGNAL(toggled(bool)), this, SIGNAL(changed())); connect(ui->kcfg_debuggingShell, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_displayStaticMembers, &QCheckBox::toggled, this, &GdbConfigPage::changed); connect(ui->kcfg_gdbPath, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_runGdbScript, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_runShellScript, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_startWith, static_cast(&QComboBox::currentIndexChanged), this, &GdbConfigPage::changed); //Setup data info for combobox ui->kcfg_startWith->setItemData(0, "ApplicationOutput" ); ui->kcfg_startWith->setItemData(1, "GdbConsole" ); ui->kcfg_startWith->setItemData(2, "FrameStack" ); } GdbConfigPage::~GdbConfigPage() { delete ui; } QIcon GdbConfigPage::icon() const { return QIcon(); } void GdbConfigPage::loadFromConfiguration( const KConfigGroup& cfg, KDevelop::IProject* ) { bool block = blockSignals( true ); ui->kcfg_gdbPath->setUrl( cfg.readEntry( Config::GdbPathEntry, QUrl() ) ); ui->kcfg_debuggingShell->setUrl( cfg.readEntry( Config::DebuggerShellEntry, QUrl() ) ); ui->kcfg_configGdbScript->setUrl( cfg.readEntry( Config::RemoteGdbConfigEntry, QUrl() ) ); ui->kcfg_runShellScript->setUrl( cfg.readEntry( Config::RemoteGdbShellEntry, QUrl() ) ); ui->kcfg_runGdbScript->setUrl( cfg.readEntry( Config::RemoteGdbRunEntry, QUrl() ) ); ui->kcfg_displayStaticMembers->setChecked( cfg.readEntry( Config::StaticMembersEntry, false ) ); ui->kcfg_asmDemangle->setChecked( cfg.readEntry( Config::DemangleNamesEntry, true) ); ui->kcfg_startWith->setCurrentIndex( ui->kcfg_startWith->findData( cfg.readEntry( KDevMI::Config::StartWithEntry, "ApplicationOutput" ) ) ); blockSignals( block ); } void GdbConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* ) const { cfg.writeEntry(Config::GdbPathEntry, ui->kcfg_gdbPath->url() ); cfg.writeEntry(Config::DebuggerShellEntry, ui->kcfg_debuggingShell->url() ); cfg.writeEntry(Config::RemoteGdbConfigEntry, ui->kcfg_configGdbScript->url() ); cfg.writeEntry(Config::RemoteGdbShellEntry, ui->kcfg_runShellScript->url() ); cfg.writeEntry(Config::RemoteGdbRunEntry, ui->kcfg_runGdbScript->url() ); cfg.writeEntry(Config::StaticMembersEntry, ui->kcfg_displayStaticMembers->isChecked() ); cfg.writeEntry(Config::DemangleNamesEntry, ui->kcfg_asmDemangle->isChecked() ); cfg.writeEntry(KDevMI::Config::StartWithEntry, ui->kcfg_startWith->itemData( ui->kcfg_startWith->currentIndex() ).toString() ); } QString GdbConfigPage::title() const { return i18n( "GDB Configuration" ); } GdbLauncher::GdbLauncher( KDevMI::GDB::CppDebuggerPlugin* p, IExecutePlugin* execute ) : m_plugin( p ) , m_execute( execute ) { factoryList << new GdbConfigPageFactory(); } QList< KDevelop::LaunchConfigurationPageFactory* > GdbLauncher::configPages() const { return factoryList; } QString GdbLauncher::id() { return "gdb"; } QString GdbLauncher::name() const { return i18n("GDB"); } KJob* GdbLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return nullptr; } if( launchMode == "debug" ) { Q_ASSERT(m_execute); Q_ASSERT(m_plugin); if (KDevelop::ICore::self()->debugController()->currentSession() != nullptr) { KMessageBox::ButtonCode answer = KMessageBox::warningYesNo( nullptr, i18n("A program is already being debugged. Do you want to abort the " "currently running debug session and continue with the launch?")); if (answer == KMessageBox::No) return nullptr; } QList l; KJob* depjob = m_execute->dependencyJob(cfg); if( depjob ) { l << depjob; } l << new KDevMI::MIDebugJob( m_plugin, cfg, m_execute ); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l ); } qWarning() << "Unknown launch mode" << launchMode << "for config:" << cfg->name(); return nullptr; } QStringList GdbLauncher::supportedModes() const { return QStringList() << "debug"; } QString GdbLauncher::description() const { return i18n("Executes a native application in GDB"); } KDevelop::LaunchConfigurationPage* GdbConfigPageFactory::createWidget( QWidget* parent ) { return new GdbConfigPage( parent ); } diff --git a/debuggers/gdb/gdboutputwidget.cpp b/debuggers/gdb/gdboutputwidget.cpp index b0b02c2839..990342392e 100644 --- a/debuggers/gdb/gdboutputwidget.cpp +++ b/debuggers/gdb/gdboutputwidget.cpp @@ -1,459 +1,458 @@ /* * GDB Debugger Support * * Copyright 2003 John Birch * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * * 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 "gdboutputwidget.h" #include "dbgglobal.h" #include "debuggerplugin.h" #include "debuglog.h" #include "debugsession.h" #include #include #include -#include #include #include #include #include #include #include #include -#include +#include #include #include #include #include using namespace KDevMI::GDB; /***************************************************************************/ GDBOutputWidget::GDBOutputWidget(CppDebuggerPlugin* plugin, QWidget *parent) : QWidget(parent), m_userGDBCmdEditor(nullptr), m_Interrupt(nullptr), m_gdbView(nullptr), showInternalCommands_(false), maxLines_(5000) { setWindowIcon(QIcon::fromTheme("dialog-scripts", windowIcon())); setWindowTitle(i18n("GDB Output")); setWhatsThis(i18n("GDB output

" "Shows all gdb commands being executed. " "You can also issue any other gdb command while debugging.

")); m_gdbView = new OutputTextEdit(this); m_gdbView->setReadOnly(true); m_userGDBCmdEditor = new KHistoryComboBox (this); QLabel *label = new QLabel(i18n("&GDB cmd:"), this); label->setBuddy(m_userGDBCmdEditor); m_Interrupt = new QToolButton( this ); m_Interrupt->setIcon ( QIcon::fromTheme( "media-playback-pause" ) ); m_Interrupt->setToolTip( i18n ( "Pause execution of the app to enter gdb commands" ) ); QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(m_gdbView); topLayout->setStretchFactor(m_gdbView, 1); topLayout->setMargin(0); QBoxLayout *userGDBCmdEntry = new QHBoxLayout(); userGDBCmdEntry->addWidget(label); userGDBCmdEntry->addWidget(m_userGDBCmdEditor); userGDBCmdEntry->setStretchFactor(m_userGDBCmdEditor, 1); userGDBCmdEntry->addWidget(m_Interrupt); topLayout->addLayout(userGDBCmdEntry); setLayout(topLayout); slotStateChanged(s_none, s_dbgNotStarted); connect(m_userGDBCmdEditor, static_cast(&KHistoryComboBox::returnPressed), this, &GDBOutputWidget::slotGDBCmd); connect(m_Interrupt, &QToolButton::clicked, this, &GDBOutputWidget::breakInto); updateTimer_.setSingleShot(true); connect(&updateTimer_, &QTimer::timeout, this, &GDBOutputWidget::flushPending); connect(KDevelop::ICore::self()->debugController(), &KDevelop::IDebugController::currentSessionChanged, this, &GDBOutputWidget::currentSessionChanged); connect(plugin, &CppDebuggerPlugin::reset, this, &GDBOutputWidget::clear); connect(plugin, &CppDebuggerPlugin::raiseDebuggerConsoleViews, this, &GDBOutputWidget::requestRaise); currentSessionChanged(KDevelop::ICore::self()->debugController()->currentSession()); // TODO Port to KF5 // connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), // this, SLOT(updateColors())); updateColors(); } void GDBOutputWidget::updateColors() { KColorScheme scheme(QPalette::Active); gdbColor_ = scheme.foreground(KColorScheme::LinkText).color(); errorColor_ = scheme.foreground(KColorScheme::NegativeText).color(); } void GDBOutputWidget::currentSessionChanged(KDevelop::IDebugSession* s) { if (!s) return; DebugSession *session = qobject_cast(s); if (!session) return; connect(this, &GDBOutputWidget::userGDBCmd, session, &DebugSession::addUserCommand); connect(this, &GDBOutputWidget::breakInto, session, &DebugSession::interruptDebugger); connect(session, &DebugSession::debuggerInternalCommandOutput, this, &GDBOutputWidget::slotInternalCommandStdout); connect(session, &DebugSession::debuggerUserCommandOutput, this, &GDBOutputWidget::slotUserCommandStdout); // debugger internal output, treat it as an internal command output connect(session, &DebugSession::debuggerInternalOutput, this, &GDBOutputWidget::slotInternalCommandStdout); connect(session, &DebugSession::debuggerStateChanged, this, &GDBOutputWidget::slotStateChanged); slotStateChanged(s_none, session->debuggerState()); } /***************************************************************************/ GDBOutputWidget::~GDBOutputWidget() { delete m_gdbView; delete m_userGDBCmdEditor; } /***************************************************************************/ void GDBOutputWidget::clear() { if (m_gdbView) m_gdbView->clear(); userCommands_.clear(); allCommands_.clear(); } /***************************************************************************/ void GDBOutputWidget::slotInternalCommandStdout(const QString& line) { newStdoutLine(line, true); } void GDBOutputWidget::slotUserCommandStdout(const QString& line) { qCDebug(DEBUGGERGDB) << "User command stdout: " << line; newStdoutLine(line, false); } namespace { QString 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 = "" + text + "
"; return text; } } void GDBOutputWidget::newStdoutLine(const QString& line, bool internal) { QString s = line.toHtmlEscaped(); if (s.startsWith("(gdb)")) { s = colorify(s, gdbColor_); } else s.replace('\n', "
"); allCommands_.append(s); allCommandsRaw_.append(line); trimList(allCommands_, maxLines_); trimList(allCommandsRaw_, maxLines_); if (!internal) { userCommands_.append(s); userCommandsRaw_.append(line); trimList(userCommands_, maxLines_); trimList(userCommandsRaw_, maxLines_); } if (!internal || showInternalCommands_) showLine(s); } void GDBOutputWidget::showLine(const QString& line) { pendingOutput_ += line; // To improve performance, we update the view after some delay. if (!updateTimer_.isActive()) { updateTimer_.start(100); } } void GDBOutputWidget::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 GDBOutputWidget::setShowInternalCommands(bool show) { if (show != showInternalCommands_) { showInternalCommands_ = show; // Set of strings to show changes, text edit still has old // set. Refresh. m_gdbView->clear(); QStringList& newList = showInternalCommands_ ? allCommands_ : userCommands_; QStringList::iterator i = newList.begin(), e = newList.end(); for(; i != e; ++i) { // Note that color formatting is already applied to '*i'. showLine(*i); } } } /***************************************************************************/ void GDBOutputWidget::slotReceivedStderr(const char* line) { QString colored = colorify(QString::fromLatin1(line).toHtmlEscaped(), errorColor_); // Errors are shown inside user commands too. allCommands_.append(colored); trimList(allCommands_, maxLines_); userCommands_.append(colored); trimList(userCommands_, maxLines_); allCommandsRaw_.append(line); trimList(allCommandsRaw_, maxLines_); userCommandsRaw_.append(line); trimList(userCommandsRaw_, maxLines_); showLine(colored); } /***************************************************************************/ void GDBOutputWidget::slotGDBCmd() { QString GDBCmd(m_userGDBCmdEditor->currentText()); if (!GDBCmd.isEmpty()) { m_userGDBCmdEditor->addToHistory(GDBCmd); m_userGDBCmdEditor->clearEditText(); emit userGDBCmd(GDBCmd); } } void GDBOutputWidget::flushPending() { m_gdbView->setUpdatesEnabled(false); // QTextEdit adds newline after paragraph automatically. // So, remove trailing newline to avoid double newlines. if (pendingOutput_.endsWith('\n')) pendingOutput_.remove(pendingOutput_.length()-1, 1); Q_ASSERT(!pendingOutput_.endsWith('\n')); QTextDocument *document = m_gdbView->document(); QTextCursor cursor(document); cursor.movePosition(QTextCursor::End); cursor.insertHtml(pendingOutput_); pendingOutput_ = ""; m_gdbView->verticalScrollBar()->setValue(m_gdbView->verticalScrollBar()->maximum()); m_gdbView->setUpdatesEnabled(true); m_gdbView->update(); if (m_cmdEditorHadFocus) { m_userGDBCmdEditor->setFocus(); } } /***************************************************************************/ void GDBOutputWidget::slotStateChanged(KDevMI::DBGStateFlags oldStatus, KDevMI::DBGStateFlags newStatus) { Q_UNUSED(oldStatus) if (newStatus & s_dbgNotStarted) { m_Interrupt->setEnabled(false); m_userGDBCmdEditor->setEnabled(false); return; } else { m_Interrupt->setEnabled(true); } if (newStatus & s_dbgBusy) { if (m_userGDBCmdEditor->isEnabled()) { m_cmdEditorHadFocus = m_userGDBCmdEditor->hasFocus(); } m_userGDBCmdEditor->setEnabled(false); } else { m_userGDBCmdEditor->setEnabled(true); } } /***************************************************************************/ void GDBOutputWidget::focusInEvent(QFocusEvent */*e*/) { m_gdbView->verticalScrollBar()->setValue(m_gdbView->verticalScrollBar()->maximum()); m_userGDBCmdEditor->setFocus(); } void GDBOutputWidget::savePartialProjectSession() { KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); config.writeEntry("showInternalCommands", showInternalCommands_); } void GDBOutputWidget::restorePartialProjectSession() { KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); showInternalCommands_ = config.readEntry("showInternalCommands", false); } void GDBOutputWidget::contextMenuEvent(QContextMenuEvent * e) { QScopedPointer popup(new QMenu(this)); QAction* action = popup->addAction(i18n("Show Internal Commands"), this, SLOT(toggleShowInternalCommands())); action->setCheckable(true); action->setChecked(showInternalCommands_); action->setWhatsThis(i18n( "Controls if commands issued internally by KDevelop " "will be shown or not.
" "This option will affect only future commands, it will not " "add or remove already issued commands from the view.")); popup->addAction(i18n("Copy All"), this, SLOT(copyAll())); popup->exec(e->globalPos()); } void GDBOutputWidget::copyAll() { /* See comments for allCommandRaw_ for explanations of this complex logic, as opposed to calling text(). */ const QStringList& raw = showInternalCommands_ ? allCommandsRaw_ : userCommandsRaw_; QString text; for (int i = 0; i < raw.size(); ++i) text += raw.at(i); // Make sure the text is pastable both with Ctrl-C and with // middle click. QApplication::clipboard()->setText(text, QClipboard::Clipboard); QApplication::clipboard()->setText(text, QClipboard::Selection); } void GDBOutputWidget::toggleShowInternalCommands() { setShowInternalCommands(!showInternalCommands_); } OutputTextEdit::OutputTextEdit(GDBOutputWidget * parent) : QTextEdit(parent) { } void OutputTextEdit::contextMenuEvent(QContextMenuEvent * event) { QMenu* popup = createStandardContextMenu(); QAction* action = popup->addAction(i18n("Show Internal Commands"), parent(), SLOT(toggleShowInternalCommands())); action->setCheckable(true); action->setChecked(static_cast(parent())->showInternalCommands()); action->setWhatsThis(i18n( "Controls if commands issued internally by KDevelop " "will be shown or not.
" "This option will affect only future commands, it will not " "add or remove already issued commands from the view.")); popup->exec(event->globalPos()); } bool GDBOutputWidget::showInternalCommands() const { return showInternalCommands_; } diff --git a/debuggers/gdb/gdboutputwidget.h b/debuggers/gdb/gdboutputwidget.h index c63072adf6..ee6a119cba 100644 --- a/debuggers/gdb/gdboutputwidget.h +++ b/debuggers/gdb/gdboutputwidget.h @@ -1,156 +1,154 @@ /* * GDB Debugger Support * * Copyright 2003 John Birch * Copyright 2007 Hamish Rodda * * 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 _GDBOUTPUTWIDGET_H_ #define _GDBOUTPUTWIDGET_H_ #include "dbgglobal.h" -#include #include #include #include namespace KDevelop { class IDebugSession; } class KHistoryComboBox; -class QTextEdit; class QToolButton; namespace KDevMI { namespace GDB { class GDBController; class CppDebuggerPlugin; class GDBOutputWidget : public QWidget { Q_OBJECT public: explicit GDBOutputWidget(CppDebuggerPlugin* plugin, QWidget *parent=nullptr ); ~GDBOutputWidget() override; void savePartialProjectSession(); void restorePartialProjectSession(); bool showInternalCommands() const; public Q_SLOTS: void clear(); void slotInternalCommandStdout(const QString& line); void slotUserCommandStdout(const QString& line); void slotReceivedStderr(const char* line); void slotStateChanged(DBGStateFlags oldStatus, DBGStateFlags newStatus); void slotGDBCmd(); void flushPending(); void copyAll(); void toggleShowInternalCommands(); private Q_SLOTS: void currentSessionChanged(KDevelop::IDebugSession *session); void updateColors(); protected: void focusInEvent(QFocusEvent *e) override; void contextMenuEvent(QContextMenuEvent* e) override; Q_SIGNALS: void requestRaise(); void userGDBCmd(const QString &cmd); void breakInto(); private: void newStdoutLine(const QString& line, bool internal); /** Arranges for 'line' to be shown to the user. Adds 'line' to pendingOutput_ and makes sure updateTimer_ is running. */ void showLine(const QString& line); /** Makes 'l' no longer than 'max_size' by removing excessive elements from the top. */ void trimList(QStringList& l, int max_size); GDBController* m_controller; KHistoryComboBox* m_userGDBCmdEditor; QToolButton* m_Interrupt; QTextEdit* m_gdbView; bool m_cmdEditorHadFocus; void setShowInternalCommands(bool); friend class OutputText; /** 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 userCommands_, allCommands_; /** Same output, without any fancy formatting. Keeping it here because I can't find any way to extract raw text, without formatting, out of QTextEdit except for selecting everything and calling 'copy()'. The latter approach is just ugly. */ QStringList userCommandsRaw_, allCommandsRaw_; /** For performance reasons, we don't immediately add new text to QTExtEdit. Instead we add it to pendingOutput_ and flush it on timer. */ QString pendingOutput_; QTimer updateTimer_; bool showInternalCommands_; int maxLines_; QColor gdbColor_; QColor errorColor_; }; class OutputTextEdit : public QTextEdit { Q_OBJECT public: explicit OutputTextEdit(GDBOutputWidget* parent); protected: void contextMenuEvent(QContextMenuEvent* event) override; }; } // end of namespace GDB } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/memviewdlg.cpp b/debuggers/gdb/memviewdlg.cpp index b8539373e0..a75066262e 100644 --- a/debuggers/gdb/memviewdlg.cpp +++ b/debuggers/gdb/memviewdlg.cpp @@ -1,477 +1,476 @@ /*************************************************************************** begin : Tue Oct 5 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ************************************************************************** * Copyright 2006 Vladimir Prus *************************************************************************** * * * 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. * * * ***************************************************************************/ #include "memviewdlg.h" #include "dbgglobal.h" #include "debugsession.h" #include "mi/micommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include using KDevMI::MI::CommandType; namespace KDevMI { namespace GDB { /** Container for controls that select memory range. * The memory range selection is embedded into memory view widget, it's not a standalone dialog. However, we want to have easy way to hide/show all controls, so we group them in this class. */ class MemoryRangeSelector : public QWidget { public: QLineEdit* startAddressLineEdit; QLineEdit* amountLineEdit; QPushButton* okButton; QPushButton* cancelButton; explicit MemoryRangeSelector(QWidget* parent) : QWidget(parent) { QVBoxLayout* l = new QVBoxLayout(this); // Form layout: labels + address field auto formLayout = new QFormLayout(); l->addLayout(formLayout); startAddressLineEdit = new QLineEdit(this); formLayout->addRow(i18n("Start:"), startAddressLineEdit); amountLineEdit = new QLineEdit(this); formLayout->addRow(i18n("Amount:"), amountLineEdit); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this); l->addWidget(buttonBox); okButton = buttonBox->button(QDialogButtonBox::Ok); cancelButton = buttonBox->button(QDialogButtonBox::Cancel); setLayout(l); connect(startAddressLineEdit, &QLineEdit::returnPressed, okButton, [this]() { okButton->animateClick(); }); connect(amountLineEdit, &QLineEdit::returnPressed, okButton, [this]() { okButton->animateClick(); }); } }; MemoryView::MemoryView(QWidget* parent) : QWidget(parent), // New memory view can be created only when debugger is active, // so don't set s_appNotStarted here. m_memViewView(nullptr), m_debuggerState(0) { setWindowTitle(i18n("Memory view")); emit captionChanged(windowTitle()); initWidget(); if (isOk()) slotEnableOrDisable(); auto debugController = KDevelop::ICore::self()->debugController(); Q_ASSERT(debugController); connect(debugController, &KDevelop::IDebugController::currentSessionChanged, this, &MemoryView::currentSessionChanged); } void MemoryView::currentSessionChanged(KDevelop::IDebugSession* s) { DebugSession *session = qobject_cast(s); if (!session) return; connect(session, &DebugSession::debuggerStateChanged, this, &MemoryView::slotStateChanged); } void MemoryView::slotStateChanged(DBGStateFlags oldState, DBGStateFlags newState) { Q_UNUSED(oldState); debuggerStateChanged(newState); } void MemoryView::initWidget() { QVBoxLayout *l = new QVBoxLayout(this); l->setContentsMargins(0, 0, 0, 0); m_memViewModel = new Okteta::ByteArrayModel(0, -1, this); m_memViewView = new Okteta::ByteArrayColumnView(this); m_memViewView->setByteArrayModel(m_memViewModel); m_memViewModel->setReadOnly(false); m_memViewView->setReadOnly(false); m_memViewView->setOverwriteMode(true); m_memViewView->setOverwriteOnly(true); m_memViewModel->setAutoDelete(false); m_memViewView->setValueCoding( Okteta::ByteArrayColumnView::HexadecimalCoding ); m_memViewView->setNoOfGroupedBytes(4); m_memViewView->setByteSpacingWidth(2); m_memViewView->setGroupSpacingWidth(12); m_memViewView->setLayoutStyle(Okteta::AbstractByteArrayView::FullSizeLayoutStyle); m_memViewView->setShowsNonprinting(false); m_memViewView->setSubstituteChar('*'); m_rangeSelector = new MemoryRangeSelector(this); l->addWidget(m_rangeSelector); connect(m_rangeSelector->okButton, &QPushButton::clicked, this, &MemoryView::slotChangeMemoryRange); connect(m_rangeSelector->cancelButton, &QPushButton::clicked, this, &MemoryView::slotHideRangeDialog); connect(m_rangeSelector->startAddressLineEdit, &QLineEdit::textChanged, this, &MemoryView::slotEnableOrDisable); connect(m_rangeSelector->amountLineEdit, &QLineEdit::textChanged, this, &MemoryView::slotEnableOrDisable); l->addWidget(m_memViewView); } void MemoryView::debuggerStateChanged(DBGStateFlags state) { if (isOk()) { m_debuggerState = state; slotEnableOrDisable(); } } void MemoryView::slotHideRangeDialog() { m_rangeSelector->hide(); } void MemoryView::slotChangeMemoryRange() { DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (!session) return; QString amount = m_rangeSelector->amountLineEdit->text(); if(amount.isEmpty()) amount = QString("sizeof(%1)").arg(m_rangeSelector->startAddressLineEdit->text()); session->addCommand(new MI::ExpressionValueCommand(amount, this, &MemoryView::sizeComputed)); } void MemoryView::sizeComputed(const QString& size) { DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (!session) return; session->addCommand(MI::DataReadMemory, QString("%1 x 1 1 %2") .arg(m_rangeSelector->startAddressLineEdit->text()) .arg(size), this, &MemoryView::memoryRead); } void MemoryView::memoryRead(const MI::ResultRecord& r) { const MI::Value& content = r["memory"][0]["data"]; bool startStringConverted; m_memStart = r["addr"].literal().toULongLong(&startStringConverted, 16); m_memData.resize(content.size()); m_memStartStr = m_rangeSelector->startAddressLineEdit->text(); m_memAmountStr = m_rangeSelector->amountLineEdit->text(); setWindowTitle(i18np("%2 (1 byte)","%2 (%1 bytes)",m_memData.size(),m_memStartStr)); emit captionChanged(windowTitle()); for(int i = 0; i < content.size(); ++i) { m_memData[i] = content[i].literal().toInt(0, 16); } m_memViewModel->setData(reinterpret_cast(m_memData.data()), m_memData.size()); slotHideRangeDialog(); } void MemoryView::memoryEdited(int start, int end) { DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (!session) return; for(int i = start; i <= end; ++i) { session->addCommand(MI::GdbSet, QString("*(char*)(%1 + %2) = %3") .arg(m_memStart) .arg(i) .arg(QString::number(m_memData[i]))); } } void MemoryView::contextMenuEvent(QContextMenuEvent *e) { if (!isOk()) return; QMenu menu; bool app_running = !(m_debuggerState & s_appNotStarted); QAction* reload = menu.addAction(i18n("&Reload")); reload->setIcon(QIcon::fromTheme("view-refresh")); reload->setEnabled(app_running && !m_memData.isEmpty() ); QActionGroup *formatGroup = NULL; QActionGroup *groupingGroup = NULL; if (m_memViewModel && m_memViewView) { // make Format menu with action group QMenu *formatMenu = new QMenu(i18n("&Format")); formatGroup = new QActionGroup(formatMenu); QAction *binary = formatGroup->addAction(i18n("&Binary")); binary->setData(Okteta::ByteArrayColumnView::BinaryCoding); binary->setShortcut(Qt::Key_B); formatMenu->addAction(binary); QAction *octal = formatGroup->addAction(i18n("&Octal")); octal->setData(Okteta::ByteArrayColumnView::OctalCoding); octal->setShortcut(Qt::Key_O); formatMenu->addAction(octal); QAction *decimal = formatGroup->addAction(i18n("&Decimal")); decimal->setData(Okteta::ByteArrayColumnView::DecimalCoding); decimal->setShortcut(Qt::Key_D); formatMenu->addAction(decimal); QAction *hex = formatGroup->addAction(i18n("&Hexadecimal")); hex->setData(Okteta::ByteArrayColumnView::HexadecimalCoding); hex->setShortcut(Qt::Key_H); formatMenu->addAction(hex); foreach(QAction* act, formatGroup->actions()) { act->setCheckable(true); act->setChecked(act->data().toInt() == m_memViewView->valueCoding()); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); } menu.addMenu(formatMenu); // make Grouping menu with action group QMenu *groupingMenu = new QMenu(i18n("&Grouping")); groupingGroup = new QActionGroup(groupingMenu); QAction *group0 = groupingGroup->addAction(i18n("&0")); group0->setData(0); group0->setShortcut(Qt::Key_0); groupingMenu->addAction(group0); QAction *group1 = groupingGroup->addAction(i18n("&1")); group1->setData(1); group1->setShortcut(Qt::Key_1); groupingMenu->addAction(group1); QAction *group2 = groupingGroup->addAction(i18n("&2")); group2->setData(2); group2->setShortcut(Qt::Key_2); groupingMenu->addAction(group2); QAction *group4 = groupingGroup->addAction(i18n("&4")); group4->setData(4); group4->setShortcut(Qt::Key_4); groupingMenu->addAction(group4); QAction *group8 = groupingGroup->addAction(i18n("&8")); group8->setData(8); group8->setShortcut(Qt::Key_8); groupingMenu->addAction(group8); QAction *group16 = groupingGroup->addAction(i18n("1&6")); group16->setData(16); group16->setShortcut(Qt::Key_6); groupingMenu->addAction(group16); foreach(QAction* act, groupingGroup->actions()) { act->setCheckable(true); act->setChecked(act->data().toInt() == m_memViewView->noOfGroupedBytes()); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); } menu.addMenu(groupingMenu); } QAction* write = menu.addAction(i18n("Write changes")); write->setIcon(QIcon::fromTheme("document-save")); write->setEnabled(app_running && m_memViewView && m_memViewView->isModified()); QAction* range = menu.addAction(i18n("Change memory range")); range->setEnabled(app_running && !m_rangeSelector->isVisible()); range->setIcon(QIcon::fromTheme("document-edit")); QAction* close = menu.addAction(i18n("Close this view")); close->setIcon(QIcon::fromTheme("window-close")); QAction* result = menu.exec(e->globalPos()); if (result == reload) { // We use m_memStart and m_memAmount stored in this, // not textual m_memStartStr and m_memAmountStr, // because program position might have changes and expressions // are no longer valid. DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (session) { session->addCommand(MI::DataReadMemory, QString("%1 x 1 1 %2").arg(m_memStart).arg(m_memData.size()), this, &MemoryView::memoryRead); } } if (result && formatGroup && formatGroup == result->actionGroup()) m_memViewView->setValueCoding( (Okteta::ByteArrayColumnView::ValueCoding)result->data().toInt()); if (result && groupingGroup && groupingGroup == result->actionGroup()) m_memViewView->setNoOfGroupedBytes(result->data().toInt()); if (result == write) { memoryEdited(0, m_memData.size()); m_memViewView->setModified(false); } if (result == range) { m_rangeSelector->startAddressLineEdit->setText(m_memStartStr); m_rangeSelector->amountLineEdit->setText(m_memAmountStr); m_rangeSelector->show(); m_rangeSelector->startAddressLineEdit->setFocus(); } if (result == close) delete this; } bool MemoryView::isOk() const { return m_memViewView; } void MemoryView::slotEnableOrDisable() { bool app_started = !(m_debuggerState & s_appNotStarted); bool enabled_ = app_started && !m_rangeSelector->startAddressLineEdit->text().isEmpty(); m_rangeSelector->okButton->setEnabled(enabled_); } MemoryViewerWidget::MemoryViewerWidget(CppDebuggerPlugin* /*plugin*/, QWidget* parent) : QWidget(parent) { setWindowIcon(QIcon::fromTheme("server-database", windowIcon())); setWindowTitle(i18n("Memory viewer")); QAction * newMemoryViewerAction = new QAction(this); newMemoryViewerAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); newMemoryViewerAction->setText(i18n("New memory viewer")); newMemoryViewerAction->setToolTip(i18nc("@info:tooltip", "Open a new memory viewer.")); newMemoryViewerAction->setIcon(QIcon::fromTheme("window-new")); connect(newMemoryViewerAction, &QAction::triggered, this , &MemoryViewerWidget::slotAddMemoryView); addAction(newMemoryViewerAction); QVBoxLayout *l = new QVBoxLayout(this); l->setContentsMargins(0, 0, 0, 0); m_toolBox = new QToolBox(this); m_toolBox->setContentsMargins(0, 0, 0, 0); l->addWidget(m_toolBox); setLayout(l); // Start with one empty memory view. slotAddMemoryView(); } void MemoryViewerWidget::slotAddMemoryView() { MemoryView* widget = new MemoryView(this); m_toolBox->addItem(widget, widget->windowTitle()); m_toolBox->setCurrentIndex(m_toolBox->indexOf(widget)); connect(widget, &MemoryView::captionChanged, this, &MemoryViewerWidget::slotChildCaptionChanged); } void MemoryViewerWidget::slotChildCaptionChanged(const QString& caption) { const QWidget* s = static_cast(sender()); QWidget* ncs = const_cast(s); QString cap = caption; // Prevent intepreting '&' as accelerator specifier. cap.replace('&', "&&"); m_toolBox->setItemText(m_toolBox->indexOf(ncs), cap); } } // end of namespace GDB } // end of namespace KDevMI diff --git a/debuggers/gdb/memviewdlg.h b/debuggers/gdb/memviewdlg.h index 0b33afd851..25dcb05183 100644 --- a/debuggers/gdb/memviewdlg.h +++ b/debuggers/gdb/memviewdlg.h @@ -1,123 +1,121 @@ /*************************************************************************** begin : Tue Oct 5 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef MEMVIEW_H_ #define MEMVIEW_H_ #include "dbgglobal.h" #include "mi/mi.h" -#include #include namespace Okteta { class ByteArrayModel; } namespace Okteta { class ByteArrayColumnView; } namespace KDevelop { class IDebugSession; } -class QLineEdit; class QToolBox; namespace KDevMI { namespace GDB { class CppDebuggerPlugin; class MemoryView; class GDBController; class MemoryRangeSelector; class MemoryViewerWidget : public QWidget { Q_OBJECT public: explicit MemoryViewerWidget(CppDebuggerPlugin* plugin, QWidget* parent = nullptr); public Q_SLOTS: /** Adds a new memory view. */ void slotAddMemoryView(); Q_SIGNALS: void requestRaise(); private Q_SLOTS: void slotChildCaptionChanged(const QString& caption); private: // Data QToolBox* m_toolBox; }; class MemoryView : public QWidget { Q_OBJECT public: explicit MemoryView(QWidget* parent); void debuggerStateChanged(DBGStateFlags state); Q_SIGNALS: void captionChanged(const QString& caption); private: // Callbacks void sizeComputed(const QString& value); void memoryRead(const MI::ResultRecord& r); // Returns true is we successfully created the memoryView, and // can work. bool isOk() const; private Q_SLOTS: void memoryEdited(int start, int end); /** Informs the view about changes in debugger state. * Allows view to disable itself when debugger is not running. */ void slotStateChanged(DBGStateFlags oldState, DBGStateFlags newState); /** Invoked when user has changed memory range. Gets memory for the new range. */ void slotChangeMemoryRange(); void slotHideRangeDialog(); void slotEnableOrDisable(); private: // QWidget overrides void contextMenuEvent(QContextMenuEvent* e) override; void initWidget(); MemoryRangeSelector* m_rangeSelector; Okteta::ByteArrayModel *m_memViewModel; Okteta::ByteArrayColumnView *m_memViewView; quintptr m_memStart; QString m_memStartStr, m_memAmountStr; QByteArray m_memData; int m_debuggerState; private slots: void currentSessionChanged(KDevelop::IDebugSession* session); }; } // end of namespace GDB } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/printers/tests/qlistcontainer.cpp b/debuggers/gdb/printers/tests/qlistcontainer.cpp index 02fb6000de..12e38b3450 100644 --- a/debuggers/gdb/printers/tests/qlistcontainer.cpp +++ b/debuggers/gdb/printers/tests/qlistcontainer.cpp @@ -1,68 +1,67 @@ #include #include #include #include #include #include #include -#include struct A { explicit A(const QString& _a = QString(), const QString& _b = QString(), int _c = -1, int _d = -1) : a(_a), b(_b), c(_c), d(_d) {} bool operator==(const A& other) const { return a == other.a && b == other.b && c == other.c && d == other.d; } QString a; QString b; int c; int d; }; uint qHash(const A& a) { return qHash(a.a) + qHash(a.b); } template