diff --git a/app/main.cpp b/app/main.cpp index 3f1d04e205..443612e55e 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,825 +1,825 @@ /*************************************************************************** * 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 "config-kdevelop.h" #include "kdevelop_version.h" #include "urlinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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& info, infos) { if (!ICore::self()->documentController()->openDocument(info.url, info.cursor)) { qWarning(APP) << i18n("Could not open %1", info.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; QVector files, directories; for (const auto& info : infos) if (info.isDirectory()) directories << info; else files << info; openFiles(files); for(const auto &urlinfo : directories) ICore::self()->projectController()->openProjectForUrl(urlinfo.url); } 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 = QStringLiteral("%1_%2").arg(sm.sessionId()).arg(sm.sessionKey()); const auto activeSession = KDevelop::Core::self()->sessionController()->activeSession(); if (!activeSession) { qWarning(APP) << "No active session, can't save state"; return; } + const QString x11SessionId = sm.sessionId() + QLatin1Char('_') + sm.sessionKey(); 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; } /// Tries to find sessions containing project @p projectUrl in @p sessions. static const KDevelop::SessionInfos findSessionsWithProject(const SessionInfos& sessions, const QUrl& projectUrl) { if (!projectUrl.isValid()) return {}; KDevelop::SessionInfos infos; for (auto it = sessions.constBegin(); it != sessions.constEnd(); ++it) { if (it->projects.contains(projectUrl)) { infos << *it; } } return infos; } /// 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 = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid); QDBusInterface iface(service, QStringLiteral("/org/kdevelop/DocumentController"), QStringLiteral("org.kdevelop.DocumentController")); QStringList urls; bool errors_occured = false; foreach ( const UrlInfo& file, files ) { QDBusReply result = iface.call(QStringLiteral("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, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"), QStringLiteral("ensureVisible") ); QDBusConnection::sessionBus().asyncCall( makeVisible ); return errors_occured; } /// 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 openProjectInRunningInstance(const QVector& paths, qint64 pid) { const QString service = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid); QDBusInterface iface(service, QStringLiteral("/org/kdevelop/ProjectController"), QStringLiteral("org.kdevelop.ProjectController")); int errors = 0; foreach ( const UrlInfo& path, paths ) { QDBusReply result = iface.call(QStringLiteral("openProjectForUrl"), path.url.toString()); if ( !result.isValid() ) { QTextStream err(stderr); err << i18n("Could not open project '%1': %2", path.url.toDisplayString(QUrl::PreferLocalFile), result.error().message()) << "\n"; ++errors; } } // make the window visible QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"), QStringLiteral("ensureVisible") ); QDBusConnection::sessionBus().asyncCall( makeVisible ); return errors; } /// 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(); #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) // If possible, use the Software backend for QQuickWidget (currently used in the // welcome page plugin). This means we don't need OpenGL at all, avoiding issues // like https://bugs.kde.org/show_bug.cgi?id=386527. QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); #endif // 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"); } 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 //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(QLatin1String("--debug="))) { argc = i + 1; debugFound = true; } } } KDevelopApplication app(argc, argv); KLocalizedString::setApplicationDomain("kdevelop"); static const char description[] = I18N_NOOP( "The KDevelop Integrated Development Environment" ); KAboutData aboutData( QStringLiteral("kdevelop"), i18n( "KDevelop" ), QByteArray(KDEVELOP_VERSION_STRING), i18n(description), KAboutLicense::GPL, i18n("Copyright 1999-2017, The KDevelop developers"), QString(), QStringLiteral("https://www.kdevelop.org/")); aboutData.setDesktopFileName(QStringLiteral("org.kde.kdevelop")); aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support" ), QStringLiteral("kfunk@kde.org") ); aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), QStringLiteral("svenbrauch@gmail.com") ); aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), QStringLiteral("aleixpol@gmail.com") ); aboutData.addAuthor( i18n("Milian Wolff"), i18n( "C++/Clang, Generic manager, Webdevelopment Plugins, Snippets, Performance" ), QStringLiteral("mail@milianw.de") ); aboutData.addAuthor( i18n("Olivier JG"), i18n( "C++/Clang, DUChain, Bug Fixes" ), QStringLiteral("olivier.jg@gmail.com") ); aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), QStringLiteral("apaku@gmx.de") ); aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), QStringLiteral("adymo@kdevelop.org") ); aboutData.addAuthor( i18n("David Nolden"), i18n( "Definition-Use Chain, C++ Support, Code Navigation, Code Completion, Coding Assistance, Refactoring" ), QStringLiteral("david.nolden.kdevelop@art-master.de") ); aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), QStringLiteral("ghost@cs.msu.su") ); aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), QStringLiteral("rodda@kde.org") ); aboutData.addAuthor( i18n("Amilcar do Carmo Lucas"), i18n( "Website admin, API documentation, Doxygen and autoproject patches" ), QStringLiteral("amilcar@kdevelop.org") ); aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), QStringLiteral("niko.sams@gmail.com") ); aboutData.addCredit( i18n("Matt Rogers"), QString(), QStringLiteral("mattr@kde.org")); aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), QStringLiteral("cedric.pasteur@free.fr") ); aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), QStringLiteral("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" ), QStringLiteral("rgruber@users.sourceforge.net") ); aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), QStringLiteral("dukjuahn@gmail.com") ); aboutData.addCredit( i18n("Harald Fernengel"), i18n( "Ported to Qt 3, patches, valgrind, diff and perforce support" ), QStringLiteral("harry@kdevelop.org") ); aboutData.addCredit( i18n("Roberto Raggi"), i18n( "C++ parser" ), QStringLiteral("roberto@kdevelop.org") ); aboutData.addCredit( i18n("The KWrite authors"), i18n( "Kate editor component" ), QStringLiteral("kwrite-devel@kde.org") ); aboutData.addCredit( i18n("Nokia Corporation/Qt Software"), i18n( "Designer code" ), QStringLiteral("qt-info@nokia.com") ); aboutData.addCredit( i18n("Contributors to older versions:"), QString(), QLatin1String("") ); aboutData.addCredit( i18n("Bernd Gehrmann"), i18n( "Initial idea, basic architecture, much initial source code" ), QStringLiteral("bernd@kdevelop.org") ); aboutData.addCredit( i18n("Caleb Tennis"), i18n( "KTabBar, bugfixes" ), QStringLiteral("caleb@aei-tech.com") ); aboutData.addCredit( i18n("Richard Dale"), i18n( "Java & Objective C support" ), QStringLiteral("Richard_Dale@tipitina.demon.co.uk") ); aboutData.addCredit( i18n("John Birch"), i18n( "Debugger frontend" ), QStringLiteral("jbb@kdevelop.org") ); aboutData.addCredit( i18n("Sandy Meier"), i18n( "PHP support, context menu stuff" ), QStringLiteral("smeier@kdevelop.org") ); aboutData.addCredit( i18n("Kurt Granroth"), i18n( "KDE application templates" ), QStringLiteral("kurth@granroth.org") ); aboutData.addCredit( i18n("Ian Reinhart Geiser"), i18n( "Dist part, bash support, application templates" ), QStringLiteral("geiseri@yahoo.com") ); aboutData.addCredit( i18n("Matthias Hoelzer-Kluepfel"), i18n( "Several components, htdig indexing" ), QStringLiteral("hoelzer@kde.org") ); aboutData.addCredit( i18n("Victor Roeder"), i18n( "Help with Automake manager and persistent class store" ), QStringLiteral("victor_roeder@gmx.de") ); aboutData.addCredit( i18n("Simon Hausmann"), i18n( "Help with KParts infrastructure" ), QStringLiteral("hausmann@kde.org") ); aboutData.addCredit( i18n("Oliver Kellogg"), i18n( "Ada support" ), QStringLiteral("okellogg@users.sourceforge.net") ); aboutData.addCredit( i18n("Jakob Simon-Gaarde"), i18n( "QMake projectmanager" ), QStringLiteral("jsgaarde@tdcspace.dk") ); aboutData.addCredit( i18n("Falk Brettschneider"), i18n( "MDI modes, QEditor, bugfixes" ), QStringLiteral("falkbr@kdevelop.org") ); aboutData.addCredit( i18n("Mario Scalas"), i18n( "PartExplorer, redesign of CvsPart, patches, bugs(fixes)" ), QStringLiteral("mario.scalas@libero.it") ); aboutData.addCredit( i18n("Jens Dagerbo"), i18n( "Replace, Bookmarks, FileList and CTags2 plugins. Overall improvements and patches" ), QStringLiteral("jens.dagerbo@swipnet.se") ); aboutData.addCredit( i18n("Julian Rockey"), i18n( "Filecreate part and other bits and patches" ), QStringLiteral("linux@jrockey.com") ); aboutData.addCredit( i18n("Ajay Guleria"), i18n( "ClearCase support" ), QStringLiteral("ajay_guleria@yahoo.com") ); aboutData.addCredit( i18n("Marek Janukowicz"), i18n( "Ruby support" ), QStringLiteral("child@t17.ds.pwr.wroc.pl") ); aboutData.addCredit( i18n("Robert Moniot"), i18n( "Fortran documentation" ), QStringLiteral("moniot@fordham.edu") ); aboutData.addCredit( i18n("Ka-Ping Yee"), i18n( "Python documentation utility" ), QStringLiteral("ping@lfw.org") ); aboutData.addCredit( i18n("Dimitri van Heesch"), i18n( "Doxygen wizard" ), QStringLiteral("dimitri@stack.nl") ); aboutData.addCredit( i18n("Hugo Varotto"), i18n( "Fileselector component" ), QStringLiteral("hugo@varotto-usa.com") ); aboutData.addCredit( i18n("Matt Newell"), i18n( "Fileselector component" ), QStringLiteral("newellm@proaxis.com") ); aboutData.addCredit( i18n("Daniel Engelschalt"), i18n( "C++ code completion, persistent class store" ), QStringLiteral("daniel.engelschalt@gmx.net") ); aboutData.addCredit( i18n("Stephane Ancelot"), i18n( "Patches" ), QStringLiteral("sancelot@free.fr") ); aboutData.addCredit( i18n("Jens Zurheide"), i18n( "Patches" ), QStringLiteral("jens.zurheide@gmx.de") ); aboutData.addCredit( i18n("Luc Willems"), i18n( "Help with Perl support" ), QStringLiteral("Willems.luc@pandora.be") ); aboutData.addCredit( i18n("Marcel Turino"), i18n( "Documentation index view" ), QStringLiteral("M.Turino@gmx.de") ); aboutData.addCredit( i18n("Yann Hodique"), i18n( "Patches" ), QStringLiteral("Yann.Hodique@lifl.fr") ); aboutData.addCredit( i18n("Tobias Gl\303\244\303\237er") , i18n( "Documentation Finder, qmake projectmanager patches, usability improvements, bugfixes ... " ), QStringLiteral("tobi.web@gmx.de") ); aboutData.addCredit( i18n("Andreas Koepfle") , i18n( "QMake project manager patches" ), QStringLiteral("koepfle@ti.uni-mannheim.de") ); aboutData.addCredit( i18n("Sascha Cunz") , i18n( "Cleanup and bugfixes for qEditor, AutoMake and much other stuff" ), QStringLiteral("mail@sacu.de") ); aboutData.addCredit( i18n("Zoran Karavla"), i18n( "Artwork for the ruby language" ), QStringLiteral("webmaster@the-error.net"), QStringLiteral("http://the-error.net") ); KAboutData::setApplicationData(aboutData); // set icon for shells which do not use desktop file metadata // but without setting replacing an existing icon with an empty one! QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("kdevelop"), QApplication::windowIcon())); 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; 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."), QStringLiteral("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." ), QStringLiteral("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." ), QStringLiteral("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{"f", "fetch"}, i18n("Open KDevelop and fetch the project from the given ."), QStringLiteral("repo url")}); parser.addOption(QCommandLineOption{QStringList{"p", "project"}, i18n("Open KDevelop and load the given project. can be either a .kdev4 file or a directory path."), QStringLiteral("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"), QStringLiteral("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(QStringLiteral("files"), i18n( "Files to load, or directories to load as projects" ), QStringLiteral("[FILE[:line[:column]] | DIRECTORY]...")); // 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(QStringLiteral("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 << QStringLiteral("%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; QVector initialDirectories; foreach (const QString &file, parser.positionalArguments()) { const UrlInfo info(file); if (info.isDirectory()) { initialDirectories.append(info); } else { initialFiles.append(info); } } const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos(); if ((!initialFiles.isEmpty() || !initialDirectories.isEmpty()) && !parser.isSet(QStringLiteral("new-session"))) { #if KDEVELOP_SINGLE_APP if (app.isRunning()) { bool success = app.sendMessage(serializeOpenFilesMessage(initialFiles << initialDirectories)); if (success) { return 0; } } #else qint64 pid = -1; if (parser.isSet(QStringLiteral("open-session"))) { const QString session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("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) + openProjectInRunningInstance(initialDirectories, 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(QStringLiteral("pss")) || (parser.isSet(QStringLiteral("pid")) && !parser.isSet(QStringLiteral("open-session")) && !parser.isSet(QStringLiteral("ps")) && nRunningSessions > 1)) { QTextStream qerr(stderr); SessionInfos candidates; foreach(const KDevelop::SessionInfo& si, availableSessionInfos) if( (!si.name.isEmpty() || !si.projects.isEmpty() || parser.isSet(QStringLiteral("pid"))) && (!parser.isSet(QStringLiteral("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(QStringLiteral("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(QStringLiteral("ps"))) { bool onlyRunning = parser.isSet(QStringLiteral("pid")); session = KDevelop::SessionController::showSessionChooserDialog(i18n("Select the session you would like to use"), onlyRunning); if(session.isEmpty()) return 1; } if ( parser.isSet(QStringLiteral("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(QStringLiteral("new-session")) ) { session = parser.value(QStringLiteral("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(QStringLiteral("open-session")) ) { session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("open-session"))); if (session.isEmpty()) { return 1; } } else if ( parser.isSet(QStringLiteral("remove-session")) ) { session = parser.value(QStringLiteral("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(QStringLiteral("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(APP) << "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(APP) << sessionData->uuid.toString() << sessionData->name << "is not running"; return 5; } } if (parser.isSet("project")) { const auto project = parser.value(QStringLiteral("project")); QFileInfo info(project); QUrl projectUrl; if (info.suffix() == QLatin1String("kdev4")) { projectUrl = QUrl::fromLocalFile(info.absoluteFilePath()); } else if (info.isDir()) { QDir dir(info.absoluteFilePath()); const auto potentialProjectFiles = dir.entryList({QStringLiteral("*.kdev4")}, QDir::Files, QDir::Name); qDebug(APP) << "Found these potential project files:" << potentialProjectFiles; if (!potentialProjectFiles.isEmpty()) { projectUrl = QUrl::fromLocalFile(dir.absoluteFilePath(potentialProjectFiles.value(0))); } } else { QTextStream qerr(stderr); qerr << "Invalid project: " << project << " - should be either a path to a .kdev4 file or a directory containing a .kdev4 file"; return 1; } qDebug(APP) << "Attempting to find a suitable session for project" << projectUrl; const auto sessionInfos = findSessionsWithProject(availableSessionInfos, projectUrl); qDebug(APP) << "Found matching sessions:" << sessionInfos.size(); if (!sessionInfos.isEmpty()) { // TODO: If there's more than one match: Allow the user to select which session to open? qDebug(APP) << "Attempting to open session:" << sessionInfos.at(0).name; session = sessionInfos.at(0).uuid.toString(); } } KDevIDEExtension::init(); qDebug(APP) << "Attempting to initialize session:" << session; 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(QStringLiteral("org.kdevelop.kdevelop-%1").arg(app.applicationPid())); Core* core = Core::self(); if (!QProcessEnvironment::systemEnvironment().contains(QStringLiteral("KDEV_DISABLE_WELCOMEPAGE"))) { core->pluginController()->loadPlugin(QStringLiteral("KDevWelcomePage")); } const auto fetchUrlStrings = parser.values(QStringLiteral("fetch")); for (const auto& fetchUrlString : fetchUrlStrings) { core->projectControllerInternal()->fetchProjectFromUrl(QUrl::fromUserInput(fetchUrlString)); } 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() == QLatin1String("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() != QLatin1String("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 = static_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); for(const auto &urlinfo: initialDirectories) core->projectController()->openProjectForUrl(urlinfo.url); } #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 qCDebug(APP) << "Done startup" << "- took:" << timer.elapsed() << "ms"; timer.invalidate(); return app.exec(); } diff --git a/plugins/cppcheck/parser.cpp b/plugins/cppcheck/parser.cpp index 8ae2a72d39..02a7c6dfc6 100644 --- a/plugins/cppcheck/parser.cpp +++ b/plugins/cppcheck/parser.cpp @@ -1,307 +1,307 @@ /* This file is part of KDevelop Copyright 2013 Christoph Thielecke 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parser.h" #include "debug.h" #include #include #include #include #include #include namespace cppcheck { /** * Convert the value of \ attribute of \ element from cppcheck's * XML-output to 'good-looking' HTML-version. This is necessary because the * displaying of the original message is performed without line breaks - such * tooltips are uncomfortable to read, and large messages will not fit into the * screen. * * This function put the original message into \ tag that automatically * provides line wrapping by builtin capabilities of Qt library. The source text * also can contain tokens '\012' (line break) - they are present in the case of * source code examples. In such cases, the entire text between the first and * last tokens (i.e. source code) is placed into \ tag. * * @param[in] input the original value of \ attribute * @return HTML version for displaying in problem's tooltip */ QString verboseMessageToHtml( const QString & input ) { QString output(QStringLiteral("%1").arg(input.toHtmlEscaped())); output.replace(QLatin1String("\\012"), QLatin1String("\n")); if (output.count('\n') >= 2) { output.replace(output.indexOf('\n'), 1, QStringLiteral("
") );
         output.replace(output.lastIndexOf('\n'), 1, QStringLiteral("

") ); } return output; } CppcheckParser::CppcheckParser() : m_errorInconclusive(false) { } CppcheckParser::~CppcheckParser() { } void CppcheckParser::clear() { m_stateStack.clear(); } bool CppcheckParser::startElement() { State newState = Unknown; qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: elem: " << qPrintable(name().toString()); if (name() == "results") { newState = Results; } else if (name() == "cppcheck") { newState = CppCheck; } else if (name() == "errors") { newState = Errors; } else if (name() == "location") { newState = Location; if (attributes().hasAttribute(QStringLiteral("file")) && attributes().hasAttribute(QStringLiteral("line"))) { QString errorFile = attributes().value(QStringLiteral("file")).toString(); // Usually when "file0" attribute exists it associated with source and // attribute "file" associated with header). // But sometimes cppcheck produces errors with "file" and "file0" attributes // both associated with same *source* file. In such cases attribute "file" contains // only file name, without full path. Therefore we should use "file0" instead "file". if (!QFile::exists(errorFile) && attributes().hasAttribute(QStringLiteral("file0"))) { errorFile = attributes().value(QStringLiteral("file0")).toString(); } m_errorFiles += errorFile; m_errorLines += attributes().value(QStringLiteral("line")).toString().toInt(); } } else if (name() == "error") { newState = Error; m_errorSeverity = QStringLiteral("unknown"); m_errorInconclusive = false; m_errorFiles.clear(); m_errorLines.clear(); m_errorMessage.clear(); m_errorVerboseMessage.clear(); if (attributes().hasAttribute(QStringLiteral("msg"))) { m_errorMessage = attributes().value(QStringLiteral("msg")).toString(); } if (attributes().hasAttribute(QStringLiteral("verbose"))) { m_errorVerboseMessage = verboseMessageToHtml(attributes().value(QStringLiteral("verbose")).toString()); } if (attributes().hasAttribute(QStringLiteral("severity"))) { m_errorSeverity = attributes().value(QStringLiteral("severity")).toString(); } if (attributes().hasAttribute(QStringLiteral("inconclusive"))) { m_errorInconclusive = true; } } else { m_stateStack.push(m_stateStack.top()); return true; } m_stateStack.push(newState); return true; } bool CppcheckParser::endElement(QVector& problems) { qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: elem: " << qPrintable(name().toString()); State state = m_stateStack.pop(); switch (state) { case CppCheck: if (attributes().hasAttribute(QStringLiteral("version"))) { qCDebug(KDEV_CPPCHECK) << "Cppcheck report version: " << attributes().value(QStringLiteral("version")); } break; case Errors: // errors finished break; case Error: qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: new error elem: line: " << (m_errorLines.isEmpty() ? QStringLiteral("?") : QString::number(m_errorLines.first())) << " at " << (m_errorFiles.isEmpty() ? QStringLiteral("?") : m_errorFiles.first()) << ", msg: " << m_errorMessage; storeError(problems); break; case Results: // results finished break; case Location: break; default: break; } return true; } QVector CppcheckParser::parse() { QVector problems; qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse!"; while (!atEnd()) { int readNextVal = readNext(); switch (readNextVal) { case StartDocument: clear(); break; case StartElement: startElement(); break; case EndElement: endElement(problems); break; case Characters: break; default: qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: case: " << readNextVal; break; } } qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse: end"; if (hasError()) { switch (error()) { case CustomError: case UnexpectedElementError: case NotWellFormedError: KMessageBox::error( qApp->activeWindow(), i18n("Cppcheck XML Parsing: error at line %1, column %2: %3", lineNumber(), columnNumber(), errorString()), i18n("Cppcheck Error")); break; case NoError: case PrematureEndOfDocumentError: break; } } return problems; } void CppcheckParser::storeError(QVector& problems) { // Construct problem with using first location element KDevelop::IProblem::Ptr problem = getProblem(); // Adds other elements as diagnostics. // This allows the user to track the problem. for (int locationIdx = 1; locationIdx < m_errorFiles.size(); ++locationIdx) { problem->addDiagnostic(getProblem(locationIdx)); } problems.push_back(problem); } KDevelop::IProblem::Ptr CppcheckParser::getProblem(int locationIdx) const { KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(i18n("Cppcheck"))); QStringList messagePrefix; QString errorMessage(m_errorMessage); if (m_errorSeverity == QLatin1String("error")) { problem->setSeverity(KDevelop::IProblem::Error); } else if (m_errorSeverity == QLatin1String("warning")) { problem->setSeverity(KDevelop::IProblem::Warning); } else { problem->setSeverity(KDevelop::IProblem::Hint); messagePrefix.push_back(m_errorSeverity); } if (m_errorInconclusive) { messagePrefix.push_back(QStringLiteral("inconclusive")); } if (!messagePrefix.isEmpty()) { - errorMessage = QStringLiteral("(%1) %2").arg(messagePrefix.join(QStringLiteral(", "))).arg(m_errorMessage); + errorMessage = QStringLiteral("(%1) %2").arg(messagePrefix.join(QStringLiteral(", ")), m_errorMessage); } problem->setDescription(errorMessage); problem->setExplanation(m_errorVerboseMessage); KDevelop::DocumentRange range; if (locationIdx < 0 || locationIdx >= m_errorFiles.size()) { range = KDevelop::DocumentRange::invalid(); } else { range.document = KDevelop::IndexedString(m_errorFiles.at(locationIdx)); range.setBothLines(m_errorLines.at(locationIdx) - 1); range.setBothColumns(0); } problem->setFinalLocation(range); problem->setFinalLocationMode(KDevelop::IProblem::TrimmedLine); return problem; } } diff --git a/plugins/custom-buildsystem/custombuildjob.cpp b/plugins/custom-buildsystem/custombuildjob.cpp index b6574bb9a1..021c47ff1f 100644 --- a/plugins/custom-buildsystem/custombuildjob.cpp +++ b/plugins/custom-buildsystem/custombuildjob.cpp @@ -1,208 +1,208 @@ /************************************************************************ * KDevelop4 Custom Buildsystem Support * * * * Copyright 2010 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 or version 3 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 "custombuildjob.h" #include #include #include #include #include #include #include #include #include #include "custombuildsystemplugin.h" #include "configconstants.h" using namespace KDevelop; CustomBuildJob::CustomBuildJob( CustomBuildSystem* plugin, KDevelop::ProjectBaseItem* item, CustomBuildSystemTool::ActionType t ) : OutputJob( plugin ) , type( t ) , exec(nullptr) , killed( false ) , enabled( false ) { setCapabilities( Killable ); QString subgrpname; switch( type ) { case CustomBuildSystemTool::Build: subgrpname = ConfigConstants::toolGroupPrefix() + QLatin1String("Build"); break; case CustomBuildSystemTool::Clean: subgrpname = ConfigConstants::toolGroupPrefix() + QLatin1String("Clean"); break; case CustomBuildSystemTool::Install: subgrpname = ConfigConstants::toolGroupPrefix() + QLatin1String("Install"); break; case CustomBuildSystemTool::Configure: subgrpname = ConfigConstants::toolGroupPrefix() + QLatin1String("Configure"); break; case CustomBuildSystemTool::Prune: subgrpname = ConfigConstants::toolGroupPrefix() + QLatin1String("Prune"); break; case CustomBuildSystemTool::Undefined: return; } projectName = item->project()->name(); builddir = plugin->buildDirectory( item ).toLocalFile(); KConfigGroup g = plugin->configuration( item->project() ); if(g.isValid()) { KConfigGroup grp = g.group( subgrpname ); enabled = grp.readEntry(ConfigConstants::toolEnabled(), false); cmd = grp.readEntry(ConfigConstants::toolExecutable(), QUrl()).toLocalFile(); environment = grp.readEntry(ConfigConstants::toolEnvironment(), QString()); arguments = grp.readEntry(ConfigConstants::toolArguments(), QString()); } QString title; switch (type) { case CustomBuildSystemTool::Build: title = i18nc("Building: ", "Building: %1 %2", cmd, item->text()); break; case CustomBuildSystemTool::Clean: title = i18nc("Cleaning: ", "Cleaning: %1 %2", cmd, item->text()); break; case CustomBuildSystemTool::Install: title = installPrefix.isEmpty() ? i18nc("Installing: ", "Installing: %1 %2", cmd, item->text()) : i18nc("Installing: ", "Installing: %1 %2 %3", cmd, item->text(), installPrefix.toDisplayString(QUrl::PreferLocalFile)); break; case CustomBuildSystemTool::Configure: title = i18nc("Configuring: ", "Configuring: %1 %2", cmd, item->text()); break; case CustomBuildSystemTool::Prune: title = i18nc("Pruning: ", "Pruning: %1 %2", cmd, item->text()); break; default: title = QStringLiteral("Internal Error: CustomBuildJob"); break; } setTitle(title); setObjectName(title); setDelegate( new KDevelop::OutputDelegate ); } void CustomBuildJob::start() { if( type == CustomBuildSystemTool::Undefined ) { setError( UndefinedBuildType ); setErrorText( i18n( "Undefined Build type" ) ); emitResult(); } else if( cmd.isEmpty() ) { setError( NoCommand ); setErrorText(i18n("No command given for custom %1 tool in project \"%2\".", CustomBuildSystemTool::toolName(type), projectName)); emitResult(); } else if( !enabled ) { setError( ToolDisabled ); setErrorText(i18n("The custom %1 tool in project \"%2\" is disabled", CustomBuildSystemTool::toolName(type), projectName)); emitResult(); } else { // prepend the command name to the argument string // so that splitArgs works correctly const QString allargv = KShell::quoteArg(cmd) + QLatin1Char(' ') + arguments; KShell::Errors err; QStringList strargs = KShell::splitArgs( allargv, KShell::AbortOnMeta, &err ); if( err != KShell::NoError ) { setError( WrongArgs ); setErrorText( i18n( "The given arguments would need a real shell, this is not supported currently." ) ); emitResult(); return; } // and remove the command name back out of the split argument list Q_ASSERT(!strargs.isEmpty()); strargs.removeFirst(); setStandardToolView( KDevelop::IOutputView::BuildView ); setBehaviours( KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); KDevelop::OutputModel* model = new KDevelop::OutputModel( QUrl::fromLocalFile(builddir) ); model->setFilteringStrategy( KDevelop::OutputModel::CompilerFilter ); setModel( model ); startOutput(); exec = new KDevelop::CommandExecutor( cmd, this ); auto env = KDevelop::EnvironmentProfileList(KSharedConfig::openConfig()).createEnvironment(environment, QProcess::systemEnvironment()); if (!installPrefix.isEmpty()) env.append("DESTDIR="+installPrefix.toDisplayString(QUrl::PreferLocalFile)); exec->setArguments( strargs ); exec->setEnvironment( env ); exec->setWorkingDirectory( builddir ); connect( exec, &CommandExecutor::completed, this, &CustomBuildJob::procFinished ); connect( exec, &CommandExecutor::failed, this, &CustomBuildJob::procError ); connect( exec, &CommandExecutor::receivedStandardError, model, &OutputModel::appendLines ); connect( exec, &CommandExecutor::receivedStandardOutput, model, &OutputModel::appendLines ); - model->appendLine( QStringLiteral("%1> %2 %3").arg( builddir ).arg( cmd ).arg( arguments ) ); + model->appendLine(QStringLiteral("%1> %2 %3").arg(builddir, cmd, arguments)); exec->start(); } } bool CustomBuildJob::doKill() { killed = true; exec->kill(); return true; } void CustomBuildJob::procError( QProcess::ProcessError err ) { if( !killed ) { if( err == QProcess::FailedToStart ) { setError( FailedToStart ); setErrorText( i18n( "Failed to start command." ) ); } else if( err == QProcess::Crashed ) { setError( Crashed ); setErrorText( i18n( "Command crashed." ) ); } else { setError( UnknownExecError ); setErrorText( i18n( "Unknown error executing command." ) ); } } emitResult(); } KDevelop::OutputModel* CustomBuildJob::model() { return qobject_cast( OutputJob::model() ); } void CustomBuildJob::procFinished(int code) { //TODO: Make this configurable when the first report comes in from a tool // where non-zero does not indicate error status if( code != 0 ) { setError( FailedShownError ); model()->appendLine( i18n( "*** Failed ***" ) ); } else { model()->appendLine( i18n( "*** Finished ***" ) ); } emitResult(); } diff --git a/plugins/debuggercommon/mivariable.cpp b/plugins/debuggercommon/mivariable.cpp index 59f39332ed..13e1ab6576 100644 --- a/plugins/debuggercommon/mivariable.cpp +++ b/plugins/debuggercommon/mivariable.cpp @@ -1,360 +1,360 @@ /* * 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. */ #include "mivariable.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include "stringhelpers.h" #include #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; bool MIVariable::sessionIsAlive() const { if (!m_debugSession) return false; IDebugSession::DebuggerState s = m_debugSession->state(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState && !m_debugSession->debuggerStateIsOn(s_shuttingDown); } MIVariable::MIVariable(MIDebugSession *session, TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : Variable(model, parent, expression, display) , m_debugSession(session) { } MIVariable *MIVariable::createChild(const Value& child) { if (!m_debugSession) return nullptr; auto var = static_cast(m_debugSession->variableController()->createVariable(model(), this, child[QStringLiteral("exp")].literal())); var->setTopLevel(false); var->setVarobj(child[QStringLiteral("name")].literal()); bool hasMore = child[QStringLiteral("numchild")].toInt() != 0 || ( child.hasField(QStringLiteral("dynamic")) && child[QStringLiteral("dynamic")].toInt()!=0 ); var->setHasMoreInitial(hasMore); // *this must be parent's child before we can set type and value appendChild(var); var->setType(child[QStringLiteral("type")].literal()); var->setValue(formatValue(child[QStringLiteral("value")].literal())); var->setChanged(true); return var; } MIVariable::~MIVariable() { if (!m_varobj.isEmpty()) { // Delete only top-level variable objects. if (topLevel()) { if (sessionIsAlive()) { m_debugSession->addCommand(VarDelete, QStringLiteral("\"%1\"").arg(m_varobj)); } } if (m_debugSession) m_debugSession->variableMapping().remove(m_varobj); } } void MIVariable::setVarobj(const QString& v) { if (!m_debugSession) { qCWarning(DEBUGGERCOMMON) << "MIVariable::setVarobj called when its session died"; return; } if (!m_varobj.isEmpty()) { // this should not happen // but apperently it does when attachMaybe is called a second time before // the first -var-create call returned m_debugSession->variableMapping().remove(m_varobj); } m_varobj = v; m_debugSession->variableMapping()[m_varobj] = this; } static int nextId = 0; class CreateVarobjHandler : public MICommandHandler { public: CreateVarobjHandler(MIVariable *variable, QObject *callback, const char *callbackMethod) : m_variable(variable), m_callback(callback), m_callbackMethod(callbackMethod) {} void handle(const ResultRecord &r) override { if (!m_variable) return; bool hasValue = false; MIVariable* variable = m_variable.data(); variable->deleteChildren(); variable->setInScope(true); if (r.reason == QLatin1String("error")) { variable->setShowError(true); } else { variable->setVarobj(r[QStringLiteral("name")].literal()); bool hasMore = false; if (r.hasField(QStringLiteral("has_more")) && r[QStringLiteral("has_more")].toInt()) // GDB swears there are more children. Trust it hasMore = true; else // There are no more children in addition to what // numchild reports. But, in KDevelop, the variable // is not yet expanded, and those numchild are not // fetched yet. So, if numchild != 0, hasMore should // be true. hasMore = r[QStringLiteral("numchild")].toInt() != 0; variable->setHasMore(hasMore); variable->setType(r[QStringLiteral("type")].literal()); variable->setValue(variable->formatValue(r[QStringLiteral("value")].literal())); hasValue = !r[QStringLiteral("value")].literal().isEmpty(); if (variable->isExpanded() && r[QStringLiteral("numchild")].toInt()) { variable->fetchMoreChildren(); } if (variable->format() != KDevelop::Variable::Natural) { //TODO doesn't work for children as they are not yet loaded variable->formatChanged(); } } if (m_callback && m_callbackMethod) { QMetaObject::invokeMethod(m_callback, m_callbackMethod, Q_ARG(bool, hasValue)); } } bool handlesError() override { return true; } private: QPointer m_variable; QObject *m_callback; const char *m_callbackMethod; }; void MIVariable::attachMaybe(QObject *callback, const char *callbackMethod) { if (!m_varobj.isEmpty()) return; // Try find a current session and attach to it if (!ICore::self()->debugController()) return; //happens on shutdown m_debugSession = static_cast(ICore::self()->debugController()->currentSession()); if (sessionIsAlive()) { m_debugSession->addCommand(VarCreate, QStringLiteral("var%1 @ %2").arg(nextId++).arg(enquotedExpression()), new CreateVarobjHandler(this, callback, callbackMethod)); } } void MIVariable::markAsDead() { m_varobj.clear(); } class FetchMoreChildrenHandler : public MICommandHandler { public: FetchMoreChildrenHandler(MIVariable *variable, MIDebugSession *session) : m_variable(variable), m_session(session), m_activeCommands(1) {} void handle(const ResultRecord &r) override { if (!m_variable) return; --m_activeCommands; MIVariable* variable = m_variable.data(); if (r.hasField(QStringLiteral("children"))) { const Value& children = r[QStringLiteral("children")]; for (int i = 0; i < children.size(); ++i) { const Value& child = children[i]; const QString& exp = child[QStringLiteral("exp")].literal(); if (exp == QLatin1String("public") || exp == QLatin1String("protected") || exp == QLatin1String("private")) { ++m_activeCommands; m_session->addCommand(VarListChildren, QStringLiteral("--all-values \"%1\"").arg(child[QStringLiteral("name")].literal()), this/*use again as handler*/); } else { variable->createChild(child); // it's automatically appended to variable's children list } } } /* Note that we don't set hasMore to true if there are still active commands. The reason is that we don't want the user to have even theoretical ability to click on "..." item and confuse us. */ bool hasMore = false; if (r.hasField(QStringLiteral("has_more"))) hasMore = r[QStringLiteral("has_more")].toInt(); variable->setHasMore(hasMore); if (m_activeCommands == 0) { variable->emitAllChildrenFetched(); delete this; } } bool handlesError() override { // FIXME: handle error? return false; } bool autoDelete() override { // we delete ourselve return false; } private: QPointer m_variable; MIDebugSession *m_session; int m_activeCommands; }; void MIVariable::fetchMoreChildren() { int c = childItems.size(); // FIXME: should not even try this if app is not started. // Probably need to disable open, or something if (sessionIsAlive()) { m_debugSession->addCommand(VarListChildren, QStringLiteral("--all-values \"%1\" %2 %3") // fetch from .. to .. .arg(m_varobj).arg(c).arg(c + s_fetchStep), new FetchMoreChildrenHandler(this, m_debugSession)); } } void MIVariable::handleUpdate(const Value& var) { if (var.hasField(QStringLiteral("type_changed")) && var[QStringLiteral("type_changed")].literal() == QLatin1String("true")) { deleteChildren(); // FIXME: verify that this check is right. setHasMore(var[QStringLiteral("new_num_children")].toInt() != 0); fetchMoreChildren(); } if (var.hasField(QStringLiteral("in_scope")) && var[QStringLiteral("in_scope")].literal() == QLatin1String("false")) { setInScope(false); } else { setInScope(true); if (var.hasField(QStringLiteral("new_num_children"))) { int nc = var[QStringLiteral("new_num_children")].toInt(); Q_ASSERT(nc != -1); setHasMore(false); while (childCount() > nc) { TreeItem *c = child(childCount()-1); removeChild(childCount()-1); delete c; } } if (var.hasField(QStringLiteral("new_children"))) { const Value& children = var[QStringLiteral("new_children")]; if (m_debugSession) { for (int i = 0; i < children.size(); ++i) { createChild(children[i]); // it's automatically appended to this's children list } } } if (var.hasField(QStringLiteral("type_changed")) && var[QStringLiteral("type_changed")].literal() == QLatin1String("true")) { setType(var[QStringLiteral("new_type")].literal()); } setValue(formatValue(var[QStringLiteral("value")].literal())); setChanged(true); setHasMore(var.hasField(QStringLiteral("has_more")) && var[QStringLiteral("has_more")].toInt()); } } const QString& MIVariable::varobj() const { return m_varobj; } QString MIVariable::enquotedExpression() const { return Utils::quoteExpression(expression()); } class SetFormatHandler : public MICommandHandler { public: explicit SetFormatHandler(MIVariable *var) : m_variable(var) {} void handle(const ResultRecord &r) override { if(m_variable && r.hasField(QStringLiteral("value"))) m_variable->setValue(m_variable->formatValue(r[QStringLiteral("value")].literal())); } private: QPointer m_variable; }; void MIVariable::formatChanged() { if(childCount()) { foreach(TreeItem* item, childItems) { Q_ASSERT(dynamic_cast(item)); if( MIVariable* var=dynamic_cast(item)) var->setFormat(format()); } } else { if (sessionIsAlive()) { m_debugSession->addCommand(VarSetFormat, - QStringLiteral(" %1 %2 ").arg(m_varobj).arg(format2str(format())), + QStringLiteral(" %1 %2 ").arg(m_varobj, format2str(format())), new SetFormatHandler(this)); } } } QString MIVariable::formatValue(const QString &rawValue) const { return rawValue; } diff --git a/plugins/debuggercommon/registers/registercontroller.cpp b/plugins/debuggercommon/registers/registercontroller.cpp index 34a1c11a5d..b9b4713102 100644 --- a/plugins/debuggercommon/registers/registercontroller.cpp +++ b/plugins/debuggercommon/registers/registercontroller.cpp @@ -1,406 +1,406 @@ /* * Class to fetch/change/send registers to the debugger. * 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 "registercontroller.h" #include "converters.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/mi.h" #include "mi/micommand.h" #include #include using namespace KDevMI::MI; using namespace KDevMI; void IRegisterController::setSession(MIDebugSession* debugSession) { m_debugSession = debugSession; } void IRegisterController::updateRegisters(const GroupsName& group) { if (!m_debugSession || m_debugSession->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } if (m_pendingGroups.contains(group)) { qCDebug(DEBUGGERCOMMON) << "Already updating " << group.name(); return; } if (group.name().isEmpty()) { foreach (const GroupsName & g, namesOfRegisterGroups()) { IRegisterController::updateRegisters(g); } return; } else { qCDebug(DEBUGGERCOMMON) << "Updating: " << group.name(); m_pendingGroups << group; } QString registers; Format currentFormat = formats(group).first(); switch (currentFormat) { case Binary: registers = QStringLiteral("t "); break; case Octal: registers = QStringLiteral("o "); break; case Decimal : registers = QStringLiteral("d "); break; case Hexadecimal: registers = QStringLiteral("x "); break; case Raw: registers = QStringLiteral("r "); break; case Unsigned: registers = QStringLiteral("u "); break; default: break; } //float point registers have only two reasonable format. Mode currentMode = modes(group).first(); if (((currentMode >= v4_float && currentMode <= v2_double) || (currentMode >= f32 && currentMode <= f64) || group.type() == floatPoint) && currentFormat != Raw) { registers = QStringLiteral("N "); } if (group.type() == flag) { registers += numberForName(group.flagName()); } else { foreach (const QString & name, registerNamesForGroup(group)) { registers += numberForName(name) + ' '; } } //Not initialized yet. They'll be updated afterwards. if (registers.contains(QLatin1String("-1"))) { qCDebug(DEBUGGERCOMMON) << "Will update later"; m_pendingGroups.clear(); return; } void (IRegisterController::* handler)(const ResultRecord&); if (group.type() == structured && currentFormat != Raw) { handler = &IRegisterController::structuredRegistersHandler; } else { handler = &IRegisterController::generalRegistersHandler; } m_debugSession->addCommand(DataListRegisterValues, registers, this, handler); } void IRegisterController::registerNamesHandler(const ResultRecord& r) { const Value& names = r[QStringLiteral("register-names")]; m_rawRegisterNames.clear(); for (int i = 0; i < names.size(); ++i) { const Value& entry = names[i]; m_rawRegisterNames.push_back(entry.literal()); } //When here probably request for updating registers was sent, but m_rawRegisterNames were not initialized yet, so it wasn't successful. Update everything once again. updateRegisters(); } void IRegisterController::generalRegistersHandler(const ResultRecord& r) { Q_ASSERT(!m_rawRegisterNames.isEmpty()); QString registerName; const Value& values = r[QStringLiteral("register-values")]; for (int i = 0; i < values.size(); ++i) { const Value& entry = values[i]; int number = entry[QStringLiteral("number")].literal().toInt(); Q_ASSERT(m_rawRegisterNames.size() > number); if (!m_rawRegisterNames[number].isEmpty()) { if (registerName.isEmpty()) { registerName = m_rawRegisterNames[number]; } const QString value = entry[QStringLiteral("value")].literal(); m_registers.insert(m_rawRegisterNames[number], value); } } GroupsName group = groupForRegisterName(registerName); if (m_pendingGroups.contains(group)) { emit registersChanged(registersFromGroup(group)); m_pendingGroups.remove(m_pendingGroups.indexOf(group)); } } void IRegisterController::setRegisterValue(const Register& reg) { Q_ASSERT(!m_registers.isEmpty()); const GroupsName group = groupForRegisterName(reg.name); if (!group.name().isEmpty()) { setRegisterValueForGroup(group, reg); } } QString IRegisterController::registerValue(const QString& name) const { QString value; if (!name.isEmpty()) { if (m_registers.contains(name)) { value = m_registers.value(name); } } return value; } bool IRegisterController::initializeRegisters() { if (!m_debugSession || m_debugSession->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return false; } m_debugSession->addCommand(DataListRegisterNames, QLatin1String(""), this, &IRegisterController::registerNamesHandler); return true; } GroupsName IRegisterController::groupForRegisterName(const QString& name) const { foreach (const GroupsName & group, namesOfRegisterGroups()) { const QStringList registersInGroup = registerNamesForGroup(group); if (group.flagName() == name) { return group; } foreach (const QString & n, registersInGroup) { if (n == name) { return group; } } } return GroupsName(); } void IRegisterController::updateValuesForRegisters(RegistersGroup* registers) const { Q_ASSERT(!m_registers.isEmpty()); for (int i = 0; i < registers->registers.size(); i++) { if (m_registers.contains(registers->registers[i].name)) { registers->registers[i].value = m_registers.value(registers->registers[i].name); } } } void IRegisterController::setFlagRegister(const Register& reg, const FlagRegister& flag) { quint32 flagsValue = registerValue(flag.registerName).toUInt(nullptr, 16); const int idx = flag.flags.indexOf(reg.name); if (idx != -1) { flagsValue ^= static_cast(qPow(2, flag.bits[idx].toUInt())); setGeneralRegister(Register(flag.registerName, QStringLiteral("0x%1").arg(flagsValue, 0, 16)), flag.groupName); } else { updateRegisters(flag.groupName); qCDebug(DEBUGGERCOMMON) << reg.name << ' ' << reg.value << "is incorrect flag name/value"; } } void IRegisterController::setGeneralRegister(const Register& reg, const GroupsName& group) { if (!m_debugSession || m_debugSession->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } - const QString command = QStringLiteral("set var $%1=%2").arg(reg.name).arg(reg.value); + const QString command = QStringLiteral("set var $%1=%2").arg(reg.name, reg.value); qCDebug(DEBUGGERCOMMON) << "Setting register: " << command; m_debugSession->addCommand(NonMI, command); updateRegisters(group); } IRegisterController::IRegisterController(MIDebugSession* debugSession, QObject* parent) : QObject(parent), m_debugSession(debugSession) {} IRegisterController::~IRegisterController() {} void IRegisterController::updateFlagValues(RegistersGroup* flagsGroup, const FlagRegister& flagRegister) const { const quint32 flagsValue = registerValue(flagRegister.registerName).toUInt(nullptr, 16); for (int idx = 0; idx < flagRegister.flags.count(); idx++) { flagsGroup->registers[idx].value = ((flagsValue >> flagRegister.bits[idx].toInt()) & 1) ? "1" : "0"; } } QVector IRegisterController::formats(const GroupsName& group) { int idx = -1; foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { idx = g.index(); } } Q_ASSERT(idx != -1); return m_formatsModes[idx].formats; } GroupsName IRegisterController::createGroupName(const QString& name, int idx, RegisterType t, const QString& flag) const { return GroupsName(name, idx, t, flag); } void IRegisterController::setFormat(Format f, const GroupsName& group) { foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { int i = m_formatsModes[g.index()].formats.indexOf(f); if (i != -1) { m_formatsModes[g.index()].formats.remove(i); m_formatsModes[g.index()].formats.prepend(f); } } } } QString IRegisterController::numberForName(const QString& name) const { //Requests for number come in order(if the previous was, let's say 10, then most likely the next one will be 11) static int previousNumber = -1; if (m_rawRegisterNames.isEmpty()) { previousNumber = -1; return QString::number(previousNumber); } if (previousNumber != -1 && m_rawRegisterNames.size() > ++previousNumber) { if (m_rawRegisterNames[previousNumber] == name) { return QString::number(previousNumber); } } for (int number = 0; number < m_rawRegisterNames.size(); number++) { if (name == m_rawRegisterNames[number]) { previousNumber = number; return QString::number(number); } } previousNumber = -1; return QString::number(previousNumber); } void IRegisterController::setStructuredRegister(const Register& reg, const GroupsName& group) { Register r = reg; r.value = r.value.trimmed(); r.value.replace(' ', ','); if (r.value.contains(',')) { r.value.append('}'); r.value.prepend('{'); } r.name += '.' + Converters::modeToString(m_formatsModes[group.index()].modes.first()); setGeneralRegister(r, group); } void IRegisterController::structuredRegistersHandler(const ResultRecord& r) { //Parsing records in format like: //{u8 = {0, 0, 128, 146, 0, 48, 197, 65}, u16 = {0, 37504, 12288, 16837}, u32 = {2457862144, 1103441920}, u64 = 4739246961893310464, f32 = {-8.07793567e-28, 24.6484375}, f64 = 710934821} //{u8 = {0 }, u16 = {0, 0, 0, 0, 0, 0, 0, 0}, u32 = {0, 0, 0, 0}, u64 = {0, 0}, f32 = {0, 0, 0, 0}, f64 = {0, 0}} QRegExp rx("^\\s*=\\s*\\{(.*)\\}"); rx.setMinimal(true); QString registerName; Mode currentMode = LAST_MODE; GroupsName group; const Value& values = r[QStringLiteral("register-values")]; Q_ASSERT(!m_rawRegisterNames.isEmpty()); for (int i = 0; i < values.size(); ++i) { const Value& entry = values[i]; int number = entry[QStringLiteral("number")].literal().toInt(); registerName = m_rawRegisterNames[number]; if (currentMode == LAST_MODE) { group = groupForRegisterName(registerName); currentMode = modes(group).first(); } QString record = entry[QStringLiteral("value")].literal(); int start = record.indexOf(Converters::modeToString(currentMode)); Q_ASSERT(start != -1); start += Converters::modeToString(currentMode).size(); QString value = record.right(record.size() - start); int idx = rx.indexIn(value); value = rx.cap(1); if (idx == -1) { //if here then value without braces: u64 = 4739246961893310464, f32 = {-8.07793567e-28, 24.6484375}, f64 = 710934821} QRegExp rx2("=\\s+(.*)(\\}|,)"); rx2.setMinimal(true); rx2.indexIn(record, start); value = rx2.cap(1); } value = value.trimmed().remove(','); m_registers.insert(registerName, value); } if (m_pendingGroups.contains(group)) { emit registersChanged(registersFromGroup(group)); m_pendingGroups.remove(m_pendingGroups.indexOf(group)); } } QVector< Mode > IRegisterController::modes(const GroupsName& group) { int idx = -1; foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { idx = g.index(); } } Q_ASSERT(idx != -1); return m_formatsModes[idx].modes; } void IRegisterController::setMode(Mode m, const GroupsName& group) { foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { int i = m_formatsModes[g.index()].modes.indexOf(m); if (i != -1) { m_formatsModes[g.index()].modes.remove(i); m_formatsModes[g.index()].modes.prepend(m); } } } } diff --git a/plugins/debuggercommon/widgets/disassemblewidget.cpp b/plugins/debuggercommon/widgets/disassemblewidget.cpp index 138650918d..6552f8fa89 100644 --- a/plugins/debuggercommon/widgets/disassemblewidget.cpp +++ b/plugins/debuggercommon/widgets/disassemblewidget.cpp @@ -1,539 +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 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(QStringLiteral("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(QStringLiteral("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() << QLatin1String("") << 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(QStringLiteral("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[QStringLiteral("asm_insns")]; const Value& pc = content[0]; if( pc.hasField(QStringLiteral("address")) ){ QString addr = pc[QStringLiteral("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, QStringLiteral("-s \"$pc\" -e \"$pc+1\" -- 0"), this, &DisassembleWidget::updateExecutionAddressHandler); }else{ QString cmd = (to.isEmpty())? QStringLiteral("-s %1 -e \"%1 + 256\" -- 0").arg(from ): - QStringLiteral("-s %1 -e %2+1 -- 0").arg(from).arg(to); // if both addr set + QStringLiteral("-s %1 -e %2+1 -- 0").arg(from, to); // if both addr set s->addCommand(DataDisassemble, cmd, this, &DisassembleWidget::disassembleMemoryHandler); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryHandler(const ResultRecord& r) { const Value& content = r[QStringLiteral("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(QStringLiteral("address")) ) addr = line[QStringLiteral("address")].literal(); if( line.hasField(QStringLiteral("func-name")) ) fct = line[QStringLiteral("func-name")].literal(); if( line.hasField(QStringLiteral("offset")) ) offs = line[QStringLiteral("offset")].literal(); if( line.hasField(QStringLiteral("inst")) ) inst = line[QStringLiteral("inst")].literal(); //We use offset at the same column where function is. if(currentFunction == fct){ if(!fct.isEmpty()){ fct = QStringLiteral("+") + 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 == QLatin1String("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[QStringLiteral("value")]; qCDebug(DEBUGGERCOMMON) << "Disassemble widget disassembly flavor" << value.literal(); DisassemblyFlavor disassemblyFlavor = DisassemblyFlavorUnknown; if (value.literal() == QLatin1String("att")) { disassemblyFlavor = DisassemblyFlavorATT; } else if (value.literal() == QLatin1String("intel")) { disassemblyFlavor = DisassemblyFlavorIntel; } else if (value.literal() == QLatin1String("default")) { disassemblyFlavor = DisassemblyFlavorATT; } m_disassembleWindow->setDisassemblyFlavor(disassemblyFlavor); } diff --git a/plugins/gdb/memviewdlg.cpp b/plugins/gdb/memviewdlg.cpp index 69861837d8..eed802aef9 100644 --- a/plugins/gdb/memviewdlg.cpp +++ b/plugins/gdb/memviewdlg.cpp @@ -1,472 +1,471 @@ /*************************************************************************** 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 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 = QStringLiteral("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, QStringLiteral("%1 x 1 1 %2") - .arg(m_rangeSelector->startAddressLineEdit->text()) - .arg(size), + .arg(m_rangeSelector->startAddressLineEdit->text(), size), this, &MemoryView::memoryRead); } void MemoryView::memoryRead(const MI::ResultRecord& r) { const MI::Value& content = r[QStringLiteral("memory")][0][QStringLiteral("data")]; bool startStringConverted; m_memStart = r[QStringLiteral("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, QStringLiteral("*(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(this); bool app_running = !(m_debuggerState & s_appNotStarted); QAction* reload = menu.addAction(i18n("&Reload")); reload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); reload->setEnabled(app_running && !m_memData.isEmpty() ); QActionGroup* formatGroup = nullptr; QActionGroup* groupingGroup = nullptr; if (m_memViewModel && m_memViewView) { // make Format menu with action group QMenu* formatMenu = menu.addMenu(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); } // make Grouping menu with action group QMenu* groupingMenu = menu.addMenu(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); } } QAction* write = menu.addAction(i18n("Write changes")); write->setIcon(QIcon::fromTheme(QStringLiteral("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(QStringLiteral("document-edit"))); QAction* close = menu.addAction(i18n("Close this view")); close->setIcon(QIcon::fromTheme(QStringLiteral("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, QStringLiteral("%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) deleteLater(); } 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(QStringLiteral("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(QStringLiteral("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('&', QLatin1String("&&")); m_toolBox->setItemText(m_toolBox->indexOf(ncs), cap); } } // end of namespace GDB } // end of namespace KDevMI diff --git a/plugins/lldb/controllers/variable.cpp b/plugins/lldb/controllers/variable.cpp index 82c967d4af..eb5be2f9fc 100644 --- a/plugins/lldb/controllers/variable.cpp +++ b/plugins/lldb/controllers/variable.cpp @@ -1,121 +1,121 @@ /* * LLDB-specific variable * 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 "variable.h" #include "debuglog.h" #include "debugsession.h" #include "mi/micommand.h" #include "stringhelpers.h" #include using namespace KDevelop; using namespace KDevMI::LLDB; using namespace KDevMI::MI; LldbVariable::LldbVariable(DebugSession *session, TreeModel *model, TreeItem *parent, const QString& expression, const QString& display) : MIVariable(session, model, parent, expression, display) { } void LldbVariable::refetch() { if (!topLevel() || varobj().isEmpty()) { return; } if (!sessionIsAlive()) { return; } // update the value itself QPointer guarded_this(this); m_debugSession->addCommand(VarEvaluateExpression, varobj(), [guarded_this](const ResultRecord &r){ if (guarded_this && r.reason == QLatin1String("done") && r.hasField(QStringLiteral("value"))) { guarded_this->setValue(guarded_this->formatValue(r[QStringLiteral("value")].literal())); } }); // update children // remove all children first, this will cause some gliches in the UI, but there's no good way // that we can know if there's anything changed if (isExpanded() || !childCount()) { deleteChildren(); fetchMoreChildren(); } } void LldbVariable::handleRawUpdate(const ResultRecord& r) { qCDebug(DEBUGGERLLDB) << "handleRawUpdate for variable" << varobj(); const Value& changelist = r[QStringLiteral("changelist")]; Q_ASSERT_X(changelist.size() <= 1, "LldbVariable::handleRawUpdate", "should only be used with one variable VarUpdate"); if (changelist.size() == 1) handleUpdate(changelist[0]); } void LldbVariable::formatChanged() { if(childCount()) { foreach(TreeItem* item, childItems) { Q_ASSERT(dynamic_cast(item)); if( MIVariable* var=dynamic_cast(item)) var->setFormat(format()); } } else { if (sessionIsAlive()) { QPointer guarded_this(this); m_debugSession->addCommand( VarSetFormat, - QStringLiteral(" %1 %2 ").arg(varobj()).arg(format2str(format())), + QStringLiteral(" %1 %2 ").arg(varobj(), format2str(format())), [guarded_this](const ResultRecord &r){ if(guarded_this && r.hasField(QStringLiteral("changelist"))) { if (r[QStringLiteral("changelist")].size() > 0) { guarded_this->handleRawUpdate(r); } } }); } } } QString LldbVariable::formatValue(const QString& value) const { // Data formatter emits value with unicode escape sequence for string and char, // translate them back. // Only check with first char is enough, as unquote will do the rest check if (value.startsWith('"')) { return Utils::quote(Utils::unquote(value, true)); } else if (value.startsWith('\'')) { return Utils::quote(Utils::unquote(value, true, '\''), '\''); } else if (value.startsWith('b')) { // this is a byte array, don't translate unicode, simply return without 'b' prefix return value.mid(1); } return value; }