diff --git a/app/main.cpp b/app/main.cpp index 85c1532696..7ec178f5b7 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,764 +1,764 @@ /*************************************************************************** * Copyright 2003-2009 Alexander Dymo * * Copyright 2007 Ralf Habacker * * Copyright 2006-2007 Matt Rogers * * Copyright 2006-2007 Hamish Rodda * * Copyright 2005-2007 Adam Treat * * Copyright 2003-2007 Jens Dagerbo * * Copyright 2001-2002 Bernd Gehrmann * * Copyright 2001-2002 Matthias Hoelzer-Kluepfel * * Copyright 2003 Roberto Raggi * * Copyright 2010 Niko Sams * * Copyright 2015 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include "urlinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(APP) Q_LOGGING_CATEGORY(APP, "kdevelop.app") #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdevideextension.h" #if KDEVELOP_SINGLE_APP #include "qtsingleapplication.h" #endif #include #ifdef Q_OS_MAC #include #endif using namespace KDevelop; namespace { #if KDEVELOP_SINGLE_APP QString serializeOpenFilesMessage(const QVector &infos) { QByteArray message; QDataStream stream(&message, QIODevice::WriteOnly); stream << QByteArrayLiteral("open"); stream << infos; return QString::fromLatin1(message.toHex()); } #endif void openFiles(const QVector& infos) { foreach (const UrlInfo& file, infos) { if (!ICore::self()->documentController()->openDocument(file.url, file.cursor)) { qWarning() << i18n("Could not open %1", file.url.toDisplayString(QUrl::PreferLocalFile)); } } } } class KDevelopApplication: #if KDEVELOP_SINGLE_APP public SharedTools::QtSingleApplication #else public QApplication #endif { public: explicit KDevelopApplication(int &argc, char **argv, bool GUIenabled = true) #if KDEVELOP_SINGLE_APP : SharedTools::QtSingleApplication(QStringLiteral("KDevelop"), argc, argv) #else : QApplication(argc, argv, GUIenabled) #endif { #if KDEVELOP_SINGLE_APP Q_UNUSED(GUIenabled); #endif connect(this, &QGuiApplication::saveStateRequest, this, &KDevelopApplication::saveState); } #if KDEVELOP_SINGLE_APP public Q_SLOTS: void remoteArguments(const QString &message, QObject *socket) { Q_UNUSED(socket); QByteArray ba = QByteArray::fromHex(message.toLatin1()); QDataStream stream(ba); QByteArray command; stream >> command; qCDebug(APP) << "Received remote command: " << command; if (command == "open") { QVector infos; stream >> infos; openFiles(infos); } else { qCWarning(APP) << "Unknown remote command: " << command; } } void fileOpenRequested(const QString &file) { openFiles({UrlInfo(file)}); } #endif private Q_SLOTS: void saveState( QSessionManager& sm ) { if (KDevelop::Core::self() && KDevelop::Core::self()->sessionController()) { QString x11SessionId = QString("%1_%2").arg(sm.sessionId()).arg(sm.sessionKey()); const auto activeSession = KDevelop::Core::self()->sessionController()->activeSession(); if (!activeSession) { qWarning() << "No active session, can't save state"; return; } QString kdevelopSessionId = activeSession->id().toString(); sm.setRestartCommand(QStringList() << 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 0; + 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 0; + return nullptr; } /// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid /// Returns the exit status static int openFilesInRunningInstance(const QVector& files, qint64 pid) { const QString service = QString("org.kdevelop.kdevelop-%1").arg(pid); QDBusInterface iface(service, "/org/kdevelop/DocumentController", "org.kdevelop.DocumentController"); QStringList urls; bool errors_occured = false; foreach ( const UrlInfo& file, files ) { QDBusReply result = iface.call("openDocumentSimple", file.url.toString(), file.cursor.line(), file.cursor.column()); if ( ! result.value() ) { QTextStream err(stderr); err << i18n("Could not open file %1.", file.url.toDisplayString(QUrl::PreferLocalFile)) << "\n"; errors_occured = true; } } // make the window visible QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, "/kdevelop/MainWindow", "org.kdevelop.MainWindow", "ensureVisible" ); QDBusConnection::sessionBus().asyncCall( makeVisible ); return errors_occured ? 1 : 0; } /// Gets the PID of a running KDevelop instance, eventually asking the user if there is more than one. /// Returns -1 in case there are no running sessions. static qint64 getRunningSessionPid() { SessionInfos candidates; foreach( const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos() ) { if( KDevelop::SessionController::isSessionRunning(si.uuid.toString()) ) { candidates << si; } } if ( candidates.isEmpty() ) { return -1; } QString sessionUuid; if ( candidates.size() == 1 ) { sessionUuid = candidates.first().uuid.toString(); } else { const QString title = i18n("Select the session to open the document in"); sessionUuid = KDevelop::SessionController::showSessionChooserDialog(title, true); } return KDevelop::SessionController::sessionRunInfo(sessionUuid).holderPid; } static QString findSessionId(const SessionInfos& availableSessionInfos, const QString& session) { //If there is a session and a project with the same name, always open the session //regardless of the order encountered QString projectAsSession; foreach(const KDevelop::SessionInfo& si, availableSessionInfos) { if ( session == si.name || session == si.uuid.toString() ) { return si.uuid.toString(); } else if (projectAsSession.isEmpty()) { foreach(const QUrl& k, si.projects) { QString fn(k.fileName()); fn = fn.left(fn.indexOf('.')); if ( session == fn ) { projectAsSession = si.uuid.toString(); } } } } if (projectAsSession.isEmpty()) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot open unknown session %1. See `--list-sessions` switch for available sessions or use `-n` to create a new one.", session) << endl; } return projectAsSession; } static qint64 findSessionPid(const QString &sessionId) { KDevelop::SessionRunInfo sessionInfo = KDevelop::SessionController::sessionRunInfo( sessionId ); return sessionInfo.holderPid; } int main( int argc, char *argv[] ) { QElapsedTimer timer; timer.start(); // TODO: Maybe generalize, add KDEVELOP_STANDALONE build option #if defined(Q_OS_WIN) || defined(Q_OS_MAC) qputenv("KDE_FORK_SLAVES", "1"); // KIO slaves will be forked off instead of being started via DBus #endif // Useful for valgrind runs, just `export KDEV_DISABLE_JIT=1` if (qEnvironmentVariableIsSet("KDEV_DISABLE_JIT")) { qputenv("KDEV_DISABLE_WELCOMEPAGE", "1"); qputenv("QT_ENABLE_REGEXP_JIT", "0"); } // Don't show any debug output by default. // If you need to enable additional logging for debugging use a rules file // as explained in the QLoggingCategory documentation: // http://qt-project.org/doc/qt-5/qloggingcategory.html#logging-rules QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\n")); KLocalizedString::setApplicationDomain("kdevelop"); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); #ifdef Q_OS_MAC CFBundleRef mainBundle = CFBundleGetMainBundle(); if (mainBundle) { // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist, // for regular executables it is obtained in another way. CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle); if (infoDict) { // Try to prevent App Nap on OS X. This can be tricky in practice, at least in 10.9 . CFDictionarySetValue(infoDict, CFSTR("NSAppSleepDisabled"), kCFBooleanTrue); CFDictionarySetValue(infoDict, CFSTR("NSSupportsAutomaticTermination"), kCFBooleanFalse); } } #endif static const char description[] = I18N_NOOP( "The KDevelop Integrated Development Environment" ); KAboutData aboutData( "kdevelop", i18n( "KDevelop" ), QByteArray(VERSION), i18n(description), KAboutLicense::GPL, i18n("Copyright 1999-2016, The KDevelop developers"), QString(), "http://www.kdevelop.org/"); aboutData.setDesktopFileName(QStringLiteral("org.kde.kdevelop.desktop")); aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support" ), "kfunk@kde.org" ); aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), "svenbrauch@gmail.com" ); aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), "aleixpol@gmail.com" ); aboutData.addAuthor( i18n("Milian Wolff"), i18n( "C++/Clang, Generic manager, Webdevelopment Plugins, Snippets, Performance" ), "mail@milianw.de" ); aboutData.addAuthor( i18n("Olivier JG"), i18n( "C++/Clang, DUChain, Bug Fixes" ), "olivier.jg@gmail.com" ); aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), "apaku@gmx.de" ); aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), "adymo@kdevelop.org" ); aboutData.addAuthor( i18n("David Nolden"), i18n( "Definition-Use Chain, C++ Support, Code Navigation, Code Completion, Coding Assistance, Refactoring" ), "david.nolden.kdevelop@art-master.de" ); aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), "ghost@cs.msu.su" ); aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), "rodda@kde.org" ); aboutData.addAuthor( i18n("Amilcar do Carmo Lucas"), i18n( "Website admin, API documentation, Doxygen and autoproject patches" ), "amilcar@kdevelop.org" ); aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), "niko.sams@gmail.com" ); aboutData.addCredit( i18n("Matt Rogers"), QString(), "mattr@kde.org"); aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), "cedric.pasteur@free.fr" ); aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), "powerfox@kde.ru" ); // QTest integration is separate in playground currently. //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integration"), "mbr.nxi@gmail.com" ); aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), "rgruber@users.sourceforge.net" ); aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), "dukjuahn@gmail.com" ); aboutData.addCredit( i18n("Harald Fernengel"), i18n( "Ported to Qt 3, patches, valgrind, diff and perforce support" ), "harry@kdevelop.org" ); aboutData.addCredit( i18n("Roberto Raggi"), i18n( "C++ parser" ), "roberto@kdevelop.org" ); aboutData.addCredit( i18n("The KWrite authors"), i18n( "Kate editor component" ), "kwrite-devel@kde.org" ); aboutData.addCredit( i18n("Nokia Corporation/Qt Software"), i18n( "Designer code" ), "qt-info@nokia.com" ); aboutData.addCredit( i18n("Contributors to older versions:"), QString(), "" ); aboutData.addCredit( i18n("Bernd Gehrmann"), i18n( "Initial idea, basic architecture, much initial source code" ), "bernd@kdevelop.org" ); aboutData.addCredit( i18n("Caleb Tennis"), i18n( "KTabBar, bugfixes" ), "caleb@aei-tech.com" ); aboutData.addCredit( i18n("Richard Dale"), i18n( "Java & Objective C support" ), "Richard_Dale@tipitina.demon.co.uk" ); aboutData.addCredit( i18n("John Birch"), i18n( "Debugger frontend" ), "jbb@kdevelop.org" ); aboutData.addCredit( i18n("Sandy Meier"), i18n( "PHP support, context menu stuff" ), "smeier@kdevelop.org" ); aboutData.addCredit( i18n("Kurt Granroth"), i18n( "KDE application templates" ), "kurth@granroth.org" ); aboutData.addCredit( i18n("Ian Reinhart Geiser"), i18n( "Dist part, bash support, application templates" ), "geiseri@yahoo.com" ); aboutData.addCredit( i18n("Matthias Hoelzer-Kluepfel"), i18n( "Several components, htdig indexing" ), "hoelzer@kde.org" ); aboutData.addCredit( i18n("Victor Roeder"), i18n( "Help with Automake manager and persistent class store" ), "victor_roeder@gmx.de" ); aboutData.addCredit( i18n("Simon Hausmann"), i18n( "Help with KParts infrastructure" ), "hausmann@kde.org" ); aboutData.addCredit( i18n("Oliver Kellogg"), i18n( "Ada support" ), "okellogg@users.sourceforge.net" ); aboutData.addCredit( i18n("Jakob Simon-Gaarde"), i18n( "QMake projectmanager" ), "jsgaarde@tdcspace.dk" ); aboutData.addCredit( i18n("Falk Brettschneider"), i18n( "MDI modes, QEditor, bugfixes" ), "falkbr@kdevelop.org" ); aboutData.addCredit( i18n("Mario Scalas"), i18n( "PartExplorer, redesign of CvsPart, patches, bugs(fixes)" ), "mario.scalas@libero.it" ); aboutData.addCredit( i18n("Jens Dagerbo"), i18n( "Replace, Bookmarks, FileList and CTags2 plugins. Overall improvements and patches" ), "jens.dagerbo@swipnet.se" ); aboutData.addCredit( i18n("Julian Rockey"), i18n( "Filecreate part and other bits and patches" ), "linux@jrockey.com" ); aboutData.addCredit( i18n("Ajay Guleria"), i18n( "ClearCase support" ), "ajay_guleria@yahoo.com" ); aboutData.addCredit( i18n("Marek Janukowicz"), i18n( "Ruby support" ), "child@t17.ds.pwr.wroc.pl" ); aboutData.addCredit( i18n("Robert Moniot"), i18n( "Fortran documentation" ), "moniot@fordham.edu" ); aboutData.addCredit( i18n("Ka-Ping Yee"), i18n( "Python documentation utility" ), "ping@lfw.org" ); aboutData.addCredit( i18n("Dimitri van Heesch"), i18n( "Doxygen wizard" ), "dimitri@stack.nl" ); aboutData.addCredit( i18n("Hugo Varotto"), i18n( "Fileselector component" ), "hugo@varotto-usa.com" ); aboutData.addCredit( i18n("Matt Newell"), i18n( "Fileselector component" ), "newellm@proaxis.com" ); aboutData.addCredit( i18n("Daniel Engelschalt"), i18n( "C++ code completion, persistent class store" ), "daniel.engelschalt@gmx.net" ); aboutData.addCredit( i18n("Stephane Ancelot"), i18n( "Patches" ), "sancelot@free.fr" ); aboutData.addCredit( i18n("Jens Zurheide"), i18n( "Patches" ), "jens.zurheide@gmx.de" ); aboutData.addCredit( i18n("Luc Willems"), i18n( "Help with Perl support" ), "Willems.luc@pandora.be" ); aboutData.addCredit( i18n("Marcel Turino"), i18n( "Documentation index view" ), "M.Turino@gmx.de" ); aboutData.addCredit( i18n("Yann Hodique"), i18n( "Patches" ), "Yann.Hodique@lifl.fr" ); aboutData.addCredit( i18n("Tobias Gl\303\244\303\237er") , i18n( "Documentation Finder, qmake projectmanager patches, usability improvements, bugfixes ... " ), "tobi.web@gmx.de" ); aboutData.addCredit( i18n("Andreas Koepfle") , i18n( "QMake project manager patches" ), "koepfle@ti.uni-mannheim.de" ); aboutData.addCredit( i18n("Sascha Cunz") , i18n( "Cleanup and bugfixes for qEditor, AutoMake and much other stuff" ), "mail@sacu.de" ); aboutData.addCredit( i18n("Zoran Karavla"), i18n( "Artwork for the ruby language" ), "webmaster@the-error.net", "http://the-error.net" ); //we can't use KCmdLineArgs as it doesn't allow arguments for the debugee //so lookup the --debug switch and eat everything behind by decrementing argc //debugArgs is filled with args after --debug QStringList debugArgs; QString debugeeName; { bool debugFound = false; int c = argc; for (int i=0; i < c; ++i) { if (debugFound) { debugArgs << argv[i]; } else if (qstrcmp(argv[i], "--debug") == 0 || qstrcmp(argv[i], "-d") == 0) { if (argc <= i+1) { argc = i + 1; } else { i++; argc = i + 1; } debugFound = true; } else if (QString(argv[i]).startsWith("--debug=")) { argc = i + 1; debugFound = true; } } } KDevelopApplication app(argc, argv); KCrash::initialize(); Kdelibs4ConfigMigrator migrator(QStringLiteral("kdevelop")); migrator.setConfigFiles({QStringLiteral("kdeveloprc")}); migrator.setUiFiles({QStringLiteral("kdevelopui.rc")}); migrator.migrate(); // High DPI support app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); qCDebug(APP) << "Startup"; QCommandLineParser parser; KAboutData::setApplicationData(aboutData); parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); parser.addOption(QCommandLineOption{QStringList{"n", "new-session"}, i18n("Open KDevelop with a new session using the given name."), "name"}); parser.addOption(QCommandLineOption{QStringList{"s", "open-session"}, i18n("Open KDevelop with the given session.\n" "You can pass either hash or the name of the session." ), "session"}); parser.addOption(QCommandLineOption{QStringList{"rm", "remove-session"}, i18n("Delete the given session.\n" "You can pass either hash or the name of the session." ), "session"}); parser.addOption(QCommandLineOption{QStringList{"ps", "pick-session"}, i18n("Shows all available sessions and lets you select one to open.")}); parser.addOption(QCommandLineOption{QStringList{"pss", "pick-session-shell"}, i18n("List all available sessions on shell and lets you select one to open.")}); parser.addOption(QCommandLineOption{QStringList{"l", "list-sessions"}, i18n("List available sessions and quit.")}); parser.addOption(QCommandLineOption{QStringList{"p", "project"}, i18n("Open KDevelop and load the given project."), "project"}); parser.addOption(QCommandLineOption{QStringList{"d", "debug"}, i18n("Start debugging an application in KDevelop with the given debugger.\n" "The binary that should be debugged must follow - including arguments.\n" "Example: kdevelop --debug gdb myapp --foo bar"), "debugger"}); // this is used by the 'kdevelop!' script to retrieve the pid of a KDEVELOP // instance. When this is called, then we should just print the PID on the // standard-output. If a session is specified through open-session, then // we should return the PID of that session. Otherwise, if only a single // session is running, then we should just return the PID of that session. // Otherwise, we should print a command-line session-chooser dialog ("--pss"), // which only shows the running sessions, and the user can pick one. parser.addOption(QCommandLineOption{QStringList{"pid"}}); parser.addPositionalArgument("files", i18n( "Files to load" ), "[FILE...]"); // The session-controller needs to arguments to eventually pass them to newly opened sessions KDevelop::SessionController::setArguments(argc, argv); parser.process(app); aboutData.processCommandLine(&parser); if(parser.isSet("list-sessions")) { QTextStream qout(stdout); qout << endl << ki18n("Available sessions (use '-s HASH' or '-s NAME' to open a specific one):").toString() << endl << endl; qout << QString("%1").arg(ki18n("Hash").toString(), -38) << '\t' << ki18n("Name: Opened Projects").toString() << endl; foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } qout << si.uuid.toString() << '\t' << si.description; if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) qout << " " << i18n("[running]"); qout << endl; } return 0; } // Handle extra arguments, which stand for files to open QVector initialFiles; foreach (const QString &file, parser.positionalArguments()) { initialFiles.append(UrlInfo(file)); } const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos(); if (!initialFiles.isEmpty() && !parser.isSet("new-session")) { #if KDEVELOP_SINGLE_APP if (app.isRunning()) { bool success = app.sendMessage(serializeOpenFilesMessage(initialFiles)); if (success) { return 0; } } #else qint64 pid = -1; if (parser.isSet("open-session")) { const QString session = findSessionId(availableSessionInfos, parser.value("open-session")); if (session.isEmpty()) { return 1; } else if (KDevelop::SessionController::isSessionRunning(session)) { pid = findSessionPid(session); } } else { pid = getRunningSessionPid(); } if ( pid > 0 ) { return openFilesInRunningInstance(initialFiles, pid); } // else there are no running sessions, and the generated list of files will be opened below. #endif } // if empty, restart kdevelop with last active session, see SessionController::defaultSessionId QString session; uint nRunningSessions = 0; foreach(const KDevelop::SessionInfo& si, availableSessionInfos) if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) ++nRunningSessions; // also show the picker dialog when a pid shall be retrieved and multiple // sessions are running. if(parser.isSet("pss") || (parser.isSet("pid") && !parser.isSet("open-session") && !parser.isSet("ps") && nRunningSessions > 1)) { QTextStream qerr(stderr); SessionInfos candidates; foreach(const KDevelop::SessionInfo& si, availableSessionInfos) if( (!si.name.isEmpty() || !si.projects.isEmpty() || parser.isSet("pid")) && (!parser.isSet("pid") || KDevelop::SessionController::isSessionRunning(si.uuid.toString()))) candidates << si; if(candidates.size() == 0) { qerr << "no session available" << endl; return 1; } if(candidates.size() == 1 && parser.isSet("pid")) { session = candidates[0].uuid.toString(); }else{ for(int i = 0; i < candidates.size(); ++i) qerr << "[" << i << "]: " << candidates[i].description << endl; int chosen; std::cin >> chosen; if(std::cin.good() && (chosen >= 0 && chosen < candidates.size())) { session = candidates[chosen].uuid.toString(); }else{ qerr << "invalid selection" << endl; return 1; } } } if(parser.isSet("ps")) { bool onlyRunning = parser.isSet("pid"); session = KDevelop::SessionController::showSessionChooserDialog(i18n("Select the session you would like to use"), onlyRunning); if(session.isEmpty()) return 1; } if ( parser.isSet("debug") ) { if ( debugArgs.isEmpty() ) { QTextStream qerr(stderr); qerr << endl << i18nc("@info:shell", "Specify the binary you want to debug.") << endl; return 1; } QFileInfo binaryInfo(debugArgs.first()); if (!binaryInfo.exists()) { binaryInfo = QStandardPaths::findExecutable(debugArgs.first()); if (!binaryInfo.exists()) { QTextStream qerr(stderr); qerr << endl << i18nc("@info:shell", "Specified binary does not exist.") << endl; return 1; } } debugArgs.first() = binaryInfo.absoluteFilePath(); debugeeName = i18n("Debug %1", binaryInfo.fileName()); session = debugeeName; } else if ( parser.isSet("new-session") ) { session = parser.value("new-session"); foreach(const KDevelop::SessionInfo& si, availableSessionInfos) { if ( session == si.name ) { QTextStream qerr(stderr); qerr << endl << i18n("A session with the name %1 exists already. Use the -s switch to open it.", session) << endl; return 1; } } // session doesn't exist, we can create it } else if ( parser.isSet("open-session") ) { session = findSessionId(availableSessionInfos, parser.value("open-session")); if (session.isEmpty()) { return 1; } } else if ( parser.isSet("remove-session") ) { session = parser.value("remove-session"); auto si = findSessionInList(availableSessionInfos, session); if (!si) { QTextStream qerr(stderr); qerr << endl << i18n("No session with the name %1 exists.", session) << endl; return 1; } auto sessionLock = KDevelop::SessionController::tryLockSession(si->uuid.toString()); if (!sessionLock.lock) { QTextStream qerr(stderr); qerr << endl << i18n("Could not lock session %1 for deletion.", session) << endl; return 1; } KDevelop::SessionController::deleteSessionFromDisk(sessionLock.lock); QTextStream qout(stdout); qout << endl << i18n("Session with name %1 was successfully removed.", session) << endl; return 0; } if(parser.isSet("pid")) { if (session.isEmpty()) { // just pick the first running session foreach(const KDevelop::SessionInfo& si, availableSessionInfos) if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) session = si.uuid.toString(); } const KDevelop::SessionInfo* sessionData = findSessionInList(availableSessionInfos, session); if( !sessionData ) { qCritical() << "session not given or does not exist"; return 5; } const auto pid = findSessionPid(sessionData->uuid.toString()); if (pid > 0) { // Print the PID and we're ready std::cout << pid << std::endl; return 0; } else { qCritical() << sessionData->uuid.toString() << sessionData->name << "is not running"; return 5; } } KDevIDEExtension::init(); if(!Core::initialize(nullptr, Core::Default, session)) return 5; // register a DBUS service for this process, so that we can open files in it from other invocations QDBusConnection::sessionBus().registerService(QString("org.kdevelop.kdevelop-%1").arg(app.applicationPid())); // TODO: port to kf5 // KGlobal::locale()->insertCatalog( Core::self()->componentData().catalogName() ); Core* core = Core::self(); if (!QProcessEnvironment::systemEnvironment().contains("KDEV_DISABLE_WELCOMEPAGE")) { core->pluginController()->loadPlugin("KDevWelcomePage"); } QStringList projectNames = parser.values("project"); if(!projectNames.isEmpty()) { foreach(const QString& p, projectNames) { QFileInfo info( p ); if( info.suffix() == "kdev4" ) { // make sure the project is not already opened by the session controller bool shouldOpen = true; Path path(info.absoluteFilePath()); foreach(KDevelop::IProject* p, core->projectController()->projects()) { if (p->projectFile() == path) { shouldOpen = false; break; } } if (shouldOpen) { core->projectController()->openProject( path.toUrl() ); } } } } if ( parser.isSet("debug") ) { Q_ASSERT( !debugeeName.isEmpty() ); QString launchName = debugeeName; - KDevelop::LaunchConfiguration* launch = 0; + 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 = 0; + KDevelop::LaunchConfigurationType *type = nullptr; foreach (KDevelop::LaunchConfigurationType *t, core->runController()->launchConfigurationTypes()) { qCDebug(APP) << t->id(); if (t->id() == "Native Application") { type = t; break; } } if (!type) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot find native launch configuration type") << endl; return 1; } - if (launch && launch->type()->id() != "Native Application") launch = 0; - if (launch && launch->launcherForMode("debug") != parser.value("debug")) launch = 0; + if (launch && launch->type()->id() != "Native Application") launch = nullptr; + if (launch && launch->launcherForMode("debug") != parser.value("debug")) launch = nullptr; if (!launch) { qCDebug(APP) << launchName << "not found, creating a new one"; QPair launcher; launcher.first = "debug"; foreach (KDevelop::ILauncher *l, type->launchers()) { if (l->id() == parser.value("debug")) { if (l->supportedModes().contains("debug")) { launcher.second = l->id(); } } } if (launcher.second.isEmpty()) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot find launcher %1", parser.value("debug")) << endl; return 1; } - KDevelop::ILaunchConfiguration* ilaunch = core->runController()->createLaunchConfiguration(type, launcher, 0, launchName); + KDevelop::ILaunchConfiguration* ilaunch = core->runController()->createLaunchConfiguration(type, launcher, nullptr, launchName); launch = dynamic_cast(ilaunch); } type->configureLaunchFromCmdLineArguments(launch->config(), debugArgs); launch->config().writeEntry("Break on Start", true); core->runControllerInternal()->setDefaultLaunch(launch); core->runControllerInternal()->execute("debug", launch); } else { openFiles(initialFiles); } #if KDEVELOP_SINGLE_APP // Set up remote arguments. QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived, &app, &KDevelopApplication::remoteArguments); QObject::connect(&app, &SharedTools::QtSingleApplication::fileOpenRequest, &app, &KDevelopApplication::fileOpenRequested); #endif #ifdef WITH_WELCOMEPAGE // make it possible to disable the welcome page, useful for valgrind runs e.g. if (!qEnvironmentVariableIsSet("KDEV_DISABLE_WELCOMEPAGE")) { trySetupWelcomePageView(); } #endif qCDebug(APP) << "Done startup" << "- took:" << timer.elapsed() << "ms"; timer.invalidate(); return app.exec(); } diff --git a/app/plasma/dataengine/kdevelopsessionsengine.cpp b/app/plasma/dataengine/kdevelopsessionsengine.cpp index 4821760d0e..b84b588f9e 100644 --- a/app/plasma/dataengine/kdevelopsessionsengine.cpp +++ b/app/plasma/dataengine/kdevelopsessionsengine.cpp @@ -1,161 +1,161 @@ /***************************************************************************** * Copyright (C) 2012 by Eike Hein * * Copyright (C) 2011 by Shaun Reich * * Copyright (C) 2008 by Montel Laurent * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *****************************************************************************/ #include "kdevelopsessionsengine.h" #include "kdevelopsessionsservice.h" #include #include #include #include #include #include #include KDevelopSessionsEngine::KDevelopSessionsEngine(QObject *parent, const QVariantList &args) : Plasma::DataEngine(parent, args), - m_dirWatch(0) + m_dirWatch(nullptr) { init(); } KDevelopSessionsEngine::~KDevelopSessionsEngine() { } void KDevelopSessionsEngine::init() { m_dirWatch = new KDirWatch( this ); const QStringList sessionDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevelop/sessions", QStandardPaths::LocateDirectory ); for ( int i = 0; i < sessionDirs.count(); ++i ) m_dirWatch->addDir( sessionDirs[i], KDirWatch::WatchSubDirs ); connect(m_dirWatch, &KDirWatch::dirty, this, &KDevelopSessionsEngine::updateSessions); updateSessions(); } Plasma::Service *KDevelopSessionsEngine::serviceForSource(const QString &source) { return new KDevelopSessionsService( this, source ); } QStringList findSessions() { QStringList sessionDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevelop/sessions", QStandardPaths::LocateDirectory); QStringList sessionrcs; Q_FOREACH(const QString& dir, sessionDirs) { QDir d(dir); Q_FOREACH(const QString& sessionDir, d.entryList(QDir::Dirs)) { QDir sd(d.absoluteFilePath(sessionDir)); QString path(sd.filePath("sessionrc")); if(QFile::exists(path)) { sessionrcs += path; } } } return sessionrcs; } void KDevelopSessionsEngine::updateSessions() { QStringList sessionrcs = findSessions(); QHash sessions; QStringList::const_iterator it; for (it = sessionrcs.constBegin(); it != sessionrcs.constEnd(); ++it) { KConfig cfg( *it, KConfig::SimpleConfig ); // Only consider sessions that have open projects. if ( cfg.hasGroup( "General Options" ) && !cfg.group( "General Options" ).readEntry( "Open Projects", "" ).isEmpty() ) { Session session; session.hash = QFileInfo( *it ).dir().dirName(); session.name = cfg.group( "" ).readEntry( "SessionName", "" ); session.description = cfg.group( "" ).readEntry( "SessionPrettyContents", "" ); sessions.insert(session.hash, session); } } QHash::const_iterator it2; for (it2 = sessions.constBegin(); it2 != sessions.constEnd(); ++it2) { const Session& session = it2.value(); if ( !m_currentSessions.contains( session.hash ) ) { // Publish new session. m_currentSessions.insert( session.hash, session ); setData( session.hash, "sessionName", session.name ); setData( session.hash, "sessionString", session.description ); } else { // Publish data changes for older sessions. Session oldSession( m_currentSessions.value(session.hash) ); bool modified = false; if ( session.name != oldSession.name ) { oldSession.name = session.name; modified = true; setData( session.hash, "sessionName", session.name ); } if ( session.description != oldSession.description ) { oldSession.description = session.description; modified = true; setData( session.hash, "sessionString", session.description ); } if ( modified ) m_currentSessions.insert( oldSession.hash, oldSession ); } } QHash::iterator it3 = m_currentSessions.begin(); while ( it3 != m_currentSessions.end() ) { const Session& session = it3.value(); if ( !sessions.contains( session.hash ) ) { removeSource( session.hash ); it3 = m_currentSessions.erase( it3 ); } else ++it3; } } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(kdevelopsessionsengine, KDevelopSessionsEngine, "plasma-dataengine-kdevelopsessions.json") #include "kdevelopsessionsengine.moc" diff --git a/debuggers/common/dialogs/processselection.h b/debuggers/common/dialogs/processselection.h index d7a087a0be..e443057c04 100644 --- a/debuggers/common/dialogs/processselection.h +++ b/debuggers/common/dialogs/processselection.h @@ -1,50 +1,50 @@ /* KDevelop GDB Support * * Copyright 2009 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef PROCESSSELECTION_H #define PROCESSSELECTION_H #include class KSysGuardProcessList; class QPushButton; namespace KDevMI { class ProcessSelectionDialog : public QDialog { Q_OBJECT public: - ProcessSelectionDialog( QWidget *parent=0 ); + ProcessSelectionDialog( QWidget *parent=nullptr ); ~ProcessSelectionDialog() override; long int pidSelected(); QSize sizeHint() const override; private slots: void selectionChanged(); private: KSysGuardProcessList* m_processList; QPushButton* m_okButton; }; } // end of namespace KDevMI #endif diff --git a/debuggers/common/mi/micommand.cpp b/debuggers/common/mi/micommand.cpp index d07bf673a8..8bd5b51bf8 100644 --- a/debuggers/common/mi/micommand.cpp +++ b/debuggers/common/mi/micommand.cpp @@ -1,489 +1,489 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "micommand.h" #include using namespace KDevMI::MI; FunctionCommandHandler::FunctionCommandHandler(const FunctionCommandHandler::Function& callback, CommandFlags flags) : _flags(flags) , _callback(callback) { } bool FunctionCommandHandler::handlesError() { return _flags & CmdHandlesError; } void FunctionCommandHandler::handle(const ResultRecord& r) { _callback(r); } MICommand::MICommand(CommandType type, const QString& command, CommandFlags flags) : type_(type) , flags_(flags) , command_(command) , commandHandler_(nullptr) , stateReloading_(false) , m_thread(-1) , m_frame(-1) { } MICommand::~MICommand() { if (commandHandler_ && commandHandler_->autoDelete()) { delete commandHandler_; } commandHandler_ = nullptr; } QString MICommand::cmdToSend() { return initialString() + '\n'; } QString MICommand::initialString() const { QString result = QString::number(token()); if (type() == NonMI) { result += command_; } else { result += miCommand(); if (m_thread != -1) result = result + QString(" --thread %1").arg(m_thread); if (m_frame != -1) result = result + QString(" --frame %1").arg(m_frame); if (!command_.isEmpty()) result += ' ' + command_; } return result; } bool MICommand::isUserCommand() const { return false; } void MICommand::setHandler(MICommandHandler* handler) { if (commandHandler_ && commandHandler_->autoDelete()) delete commandHandler_; commandHandler_ = handler; if (!commandHandler_) { flags_ = flags_ & ~CmdHandlesError; } } void MICommand::setHandler(const FunctionCommandHandler::Function& callback) { setHandler(new FunctionCommandHandler(callback, flags())); } bool MICommand::invokeHandler(const ResultRecord& r) { if (commandHandler_) { //ask before calling handler as it might deleted itself in handler bool autoDelete = commandHandler_->autoDelete(); commandHandler_->handle(r); if (autoDelete) { delete commandHandler_; } - commandHandler_ = 0; + commandHandler_ = nullptr; return true; } else { return false; } } void MICommand::newOutput(const QString& line) { lines.push_back(line); } const QStringList& MICommand::allStreamOutput() const { return lines; } bool MICommand::handlesError() const { return commandHandler_ ? commandHandler_->handlesError() : false; } UserCommand::UserCommand(CommandType type, const QString& s) : MICommand(type, s, CmdMaybeStartsRunning) { } bool UserCommand::isUserCommand() const { return true; } QString MICommand::miCommand() const { QString command; switch (type()) { case NonMI: command = ""; break; case BreakAfter: command = "break-after";//"ignore" break; case BreakCommands: command = "break-commands"; break; case BreakCondition: command = "break-condition";//"cond" break; case BreakDelete: command = "break-delete";//"delete breakpoint" break; case BreakDisable: command = "break-disable";//"disable breakpoint" break; case BreakEnable: command = "break-enable";//"enable breakpoint" break; case BreakInfo: command = "break-info";//"info break" break; case BreakInsert: command = "break-insert -f"; break; case BreakList: command = "break-list";//"info break" break; case BreakWatch: command = "break-watch"; break; case DataDisassemble: command = "data-disassemble"; break; case DataEvaluateExpression: command = "data-evaluate-expression"; break; case DataListChangedRegisters: command = "data-list-changed-registers"; break; case DataListRegisterNames: command = "data-list-register-names"; break; case DataListRegisterValues: command = "data-list-register-values"; break; case DataReadMemory: command = "data-read-memory"; break; case DataWriteMemory: command = "data-write-memory"; break; case DataWriteRegisterVariables: command = "data-write-register-values"; break; case EnablePrettyPrinting: command = "enable-pretty-printing"; break; case EnableTimings: command = "enable-timings"; break; case EnvironmentCd: command = "environment-cd"; break; case EnvironmentDirectory: command = "environment-directory"; break; case EnvironmentPath: command = "environment-path"; break; case EnvironmentPwd: command = "environment-pwd"; break; case ExecAbort: command = "exec-abort"; break; case ExecArguments: command = "exec-arguments";//"set args" break; case ExecContinue: command = "exec-continue"; break; case ExecFinish: command = "exec-finish"; break; case ExecInterrupt: command = "exec-interrupt"; break; case ExecNext: command = "exec-next"; break; case ExecNextInstruction: command = "exec-next-instruction"; break; case ExecRun: command = "exec-run"; break; case ExecStep: command = "exec-step"; break; case ExecStepInstruction: command = "exec-step-instruction"; break; case ExecUntil: command = "exec-until"; break; case FileExecAndSymbols: command = "file-exec-and-symbols";//"file" break; case FileExecFile: command = "file-exec-file";//"exec-file" break; case FileListExecSourceFile: command = "file-list-exec-source-file"; break; case FileListExecSourceFiles: command = "file-list-exec-source-files"; break; case FileSymbolFile: command = "file-symbol-file";//"symbol-file" break; case GdbExit: command = "gdb-exit"; break; case GdbSet: command = "gdb-set";//"set" break; case GdbShow: command = "gdb-show";//"show" break; case GdbVersion: command = "gdb-version";//"show version" break; case InferiorTtySet: command = "inferior-tty-set"; break; case InferiorTtyShow: command = "inferior-tty-show"; break; case InterpreterExec: command = "interpreter-exec"; break; case ListFeatures: command = "list-features"; break; case SignalHandle: return "handle"; //command = "signal-handle"; break; case StackInfoDepth: command = "stack-info-depth"; break; case StackInfoFrame: command = "stack-info-frame"; break; case StackListArguments: command = "stack-list-arguments"; break; case StackListFrames: command = "stack-list-frames"; break; case StackListLocals: command = "stack-list-locals"; break; case StackSelectFrame: command = "stack-select-frame"; break; case SymbolListLines: command = "symbol-list-lines"; break; case TargetAttach: command = "target-attach"; break; case TargetDetach: command = "target-detach";//"detach" break; case TargetDisconnect: command = "target-disconnect";//"disconnect" break; case TargetDownload: command = "target-download"; break; case TargetSelect: command = "target-select"; break; case ThreadInfo: command = "thread-info"; break; case ThreadListIds: command = "thread-list-ids"; break; case ThreadSelect: command = "thread-select"; break; case TraceFind: command = "trace-find"; break; case TraceStart: command = "trace-start"; break; case TraceStop: command = "trace-stop"; break; case VarAssign: command = "var-assign"; break; case VarCreate: command = "var-create"; break; case VarDelete: command = "var-delete"; break; case VarEvaluateExpression: command = "var-evaluate-expression"; break; case VarInfoPathExpression: command = "var-info-path-expression"; break; case VarInfoNumChildren: command = "var-info-num-children"; break; case VarInfoType: command = "var-info-type"; break; case VarListChildren: command = "var-list-children"; break; case VarSetFormat: command = "var-set-format"; break; case VarSetFrozen: command = "var-set-frozen"; break; case VarShowAttributes: command = "var-show-attributes"; break; case VarShowFormat: command = "var-show-format"; break; case VarUpdate: command = "var-update"; break; default: command = "unknown"; break; } return '-' + command; } CommandType MICommand::type() const { return type_; } int MICommand::thread() const { return m_thread; } void MICommand::setThread(int thread) { m_thread = thread; } int MICommand::frame() const { return m_frame; } void MICommand::setFrame(int frame) { m_frame = frame; } QString MICommand::command() const { return command_; } void MICommand::setStateReloading(bool f) { stateReloading_ = f; } bool MICommand::stateReloading() const { return stateReloading_; } void MICommand::markAsEnqueued() { m_enqueueTimestamp = QDateTime::currentMSecsSinceEpoch(); } void MICommand::markAsSubmitted() { m_submitTimestamp = QDateTime::currentMSecsSinceEpoch(); } void MICommand::markAsCompleted() { m_completeTimestamp = QDateTime::currentMSecsSinceEpoch(); } qint64 MICommand::gdbProcessingTime() const { return m_completeTimestamp - m_submitTimestamp; } qint64 MICommand::queueTime() const { return m_submitTimestamp - m_enqueueTimestamp; } qint64 MICommand::totalProcessingTime() const { return m_completeTimestamp - m_enqueueTimestamp; } diff --git a/debuggers/common/mi/miparser.cpp b/debuggers/common/mi/miparser.cpp index 165af5f863..ded2061931 100644 --- a/debuggers/common/mi/miparser.cpp +++ b/debuggers/common/mi/miparser.cpp @@ -1,380 +1,380 @@ /*************************************************************************** * Copyright (C) 2004 by Roberto Raggi * * roberto@kdevelop.org * * Copyright (C) 2005-2006 by Vladimir Prus * * ghost@cs.msu.su * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "miparser.h" #include "tokens.h" using namespace KDevMI::MI; #define MATCH(tok) \ do { \ if (m_lex->lookAhead(0) != (tok)) \ return false; \ } while (0) #define ADVANCE(tok) \ do { \ MATCH(tok); \ m_lex->nextToken(); \ } while (0) #define MATCH_PTR(tok) \ do { \ if (m_lex->lookAhead(0) != (tok)) \ return {}; \ } while (0) #define ADVANCE_PTR(tok) \ do { \ MATCH_PTR(tok); \ m_lex->nextToken(); \ } while (0) MIParser::MIParser() - : m_lex(0) + : m_lex(nullptr) { } MIParser::~MIParser() { } std::unique_ptr MIParser::parse(FileSymbol *file) { - m_lex = 0; + m_lex = nullptr; TokenStream *tokenStream = m_lexer.tokenize(file); if (!tokenStream) - return 0; + return nullptr; m_lex = file->tokenStream = tokenStream; uint32_t token = 0; if (m_lex->lookAhead() == Token_number_literal) { token = QString::fromUtf8(m_lex->currentTokenText()).toUInt(); m_lex->nextToken(); } std::unique_ptr record; switch (m_lex->lookAhead()) { case '~': case '@': case '&': record = parseStreamRecord(); break; case '(': record = parsePrompt(); break; case '^': case '*': case '=': case '+': record = parseResultOrAsyncRecord(); break; default: break; } if (record && record->kind == Record::Result) { ResultRecord * result = static_cast(record.get()); result->token = token; } else { Q_ASSERT(token == 0); } return record; } std::unique_ptr MIParser::parsePrompt() { ADVANCE_PTR('('); MATCH_PTR(Token_identifier); if (m_lex->currentTokenText() != "gdb") return {}; m_lex->nextToken(); ADVANCE_PTR(')'); return std::unique_ptr(new PromptRecord); } std::unique_ptr MIParser::parseStreamRecord() { StreamRecord::Subkind subkind; switch (m_lex->lookAhead()) { case '~': subkind = StreamRecord::Console; break; case '@': subkind = StreamRecord::Target; break; case '&': subkind = StreamRecord::Log; break; default: Q_ASSERT(false); return {}; } std::unique_ptr stream(new StreamRecord(subkind)); m_lex->nextToken(); MATCH_PTR(Token_string_literal); stream->message = parseStringLiteral(); return std::move(stream); } std::unique_ptr MIParser::parseResultOrAsyncRecord() { std::unique_ptr result; char c = m_lex->lookAhead(); m_lex->nextToken(); MATCH_PTR(Token_identifier); QString reason = m_lex->currentTokenText(); m_lex->nextToken(); if (c == '^') { result.reset(new ResultRecord(reason)); } else { AsyncRecord::Subkind subkind; switch (c) { case '*': subkind = AsyncRecord::Exec; break; case '=': subkind = AsyncRecord::Notify; break; case '+': subkind = AsyncRecord::Status; break; default: Q_ASSERT(false); return {}; } result.reset(new AsyncRecord(subkind, reason)); } if (m_lex->lookAhead() == ',') { m_lex->nextToken(); if (!parseCSV(*result)) return {}; } return std::move(result); } bool MIParser::parseResult(Result *&result) { // be less strict about the format, see e.g.: // https://bugs.kde.org/show_bug.cgi?id=304730 // http://sourceware.org/bugzilla/show_bug.cgi?id=9659 std::unique_ptr res(new Result); if (m_lex->lookAhead() == Token_identifier) { res->variable = m_lex->currentTokenText(); m_lex->nextToken(); if (m_lex->lookAhead() != '=') { result = res.release(); return true; } m_lex->nextToken(); } - Value *value = 0; + Value *value = nullptr; if (!parseValue(value)) return false; res->value = value; result = res.release(); return true; } bool MIParser::parseValue(Value *&value) { - value = 0; + value = nullptr; switch (m_lex->lookAhead()) { case Token_string_literal: { value = new StringLiteralValue(parseStringLiteral()); } return true; case '{': return parseTuple(value); case '[': return parseList(value); default: break; } return false; } bool MIParser::parseTuple(Value *&value) { TupleValue* val; if (!parseCSV(&val, '{', '}')) return false; value = val; return true; } bool MIParser::parseList(Value *&value) { ADVANCE('['); std::unique_ptr lst(new ListValue); // Note: can't use parseCSV here because of nested // "is this Value or Result" guessing. Too lazy to factor // that out too using function pointers. int tok = m_lex->lookAhead(); while (tok && tok != ']') { - Result *result = 0; - Value *val = 0; + Result *result = nullptr; + Value *val = nullptr; if (tok == Token_identifier) { if (!parseResult(result)) return false; } else if (!parseValue(val)) return false; Q_ASSERT(result || val); if (!result) { result = new Result; result->value = val; } lst->results.append(result); if (m_lex->lookAhead() == ',') m_lex->nextToken(); tok = m_lex->lookAhead(); } ADVANCE(']'); value = lst.release(); return true; } bool MIParser::parseCSV(TupleValue** value, char start, char end) { std::unique_ptr tuple(new TupleValue); if (!parseCSV(*tuple, start, end)) return false; *value = tuple.get(); tuple.release(); return true; } bool MIParser::parseCSV(TupleValue& value, char start, char end) { if (start) ADVANCE(start); int tok = m_lex->lookAhead(); while (tok) { if (end && tok == end) break; Result *result; if (!parseResult(result)) return false; value.results.append(result); value.results_by_name.insert(result->variable, result); if (m_lex->lookAhead() == ',') m_lex->nextToken(); tok = m_lex->lookAhead(); } if (end) ADVANCE(end); return true; } QString MIParser::parseStringLiteral() { QByteArray messageByteArray = m_lex->currentTokenText(); QString message = QString::fromUtf8(messageByteArray.constData()); int length = message.length(); QString message2; message2.reserve(length); // The [1,length-1] range removes quotes without extra // call to 'mid' int target_index = 0; for(int i = 1, e = length-1; i != e; ++i) { int translated = -1; if (message[i] == '\\') { if (i+1 < length) { // TODO: implement all the other escapes, maybe if (message[i+1] == 'n') { translated = '\n'; } else if (message[i+1] == '\\') { translated = '\\'; } else if (message[i+1] == '"') { translated = '"'; } else if (message[i+1] == 't') { translated = '\t'; } else if (message[i+1] == 'r') { translated = '\r'; } } } if (translated != -1) { message2[target_index++] = translated; ++i; } else { message2[target_index++] = message[i]; } } m_lex->nextToken(); return message2; } diff --git a/debuggers/common/mibreakpointcontroller.cpp b/debuggers/common/mibreakpointcontroller.cpp index 35e64288f7..7ea7408979 100644 --- a/debuggers/common/mibreakpointcontroller.cpp +++ b/debuggers/common/mibreakpointcontroller.cpp @@ -1,761 +1,761 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "mibreakpointcontroller.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include "stringhelpers.h" #include #include #include #include #include using namespace KDevMI; using namespace KDevMI::MI; using namespace KDevelop; struct MIBreakpointController::Handler : public MICommandHandler { Handler(MIBreakpointController* controller, const BreakpointDataPtr& b, BreakpointModel::ColumnFlags columns) : controller(controller) , breakpoint(b) , columns(columns) { breakpoint->sent |= columns; breakpoint->dirty &= ~columns; } void handle(const ResultRecord& r) override { breakpoint->sent &= ~columns; if (r.reason == "error") { breakpoint->errors |= columns; int row = controller->breakpointRow(breakpoint); if (row >= 0) { controller->updateErrorText(row, r["msg"].literal()); qWarning() << r["msg"].literal(); } } else { if (breakpoint->errors & columns) { breakpoint->errors &= ~columns; if (breakpoint->errors) { // Since at least one error column cleared, it's possible that any remaining // error bits were collateral damage; try resending the corresponding columns // to see whether errors remain. breakpoint->dirty |= (breakpoint->errors & ~breakpoint->sent); } } } } bool handlesError() override { return true; } MIBreakpointController* controller; BreakpointDataPtr breakpoint; BreakpointModel::ColumnFlags columns; }; struct MIBreakpointController::UpdateHandler : public MIBreakpointController::Handler { UpdateHandler(MIBreakpointController* c, const BreakpointDataPtr& b, BreakpointModel::ColumnFlags columns) : Handler(c, b, columns) {} void handle(const ResultRecord &r) override { Handler::handle(r); int row = controller->breakpointRow(breakpoint); if (row >= 0) { // Note: send further updates even if we got an error; who knows: perhaps // these additional updates will "unstuck" the error condition. if (breakpoint->sent == 0 && breakpoint->dirty != 0) { controller->sendUpdates(row); } controller->recalculateState(row); } } }; struct MIBreakpointController::InsertedHandler : public MIBreakpointController::Handler { InsertedHandler(MIBreakpointController* c, const BreakpointDataPtr& b, BreakpointModel::ColumnFlags columns) : Handler(c, b, columns) {} void handle(const ResultRecord &r) override { Handler::handle(r); int row = controller->breakpointRow(breakpoint); if (r.reason != "error") { QString bkptKind; for (auto kind : {"bkpt", "wpt", "hw-rwpt", "hw-awpt"}) { if (r.hasField(kind)) { bkptKind = kind; break; } } if (bkptKind.isEmpty()) { qWarning() << "Gdb sent unknown breakpoint kind"; return; } const Value& miBkpt = r[bkptKind]; breakpoint->debuggerId = miBkpt["number"].toInt(); if (row >= 0) { controller->updateFromDebugger(row, miBkpt); if (breakpoint->dirty != 0) controller->sendUpdates(row); } else { // breakpoint was deleted while insertion was in flight controller->debugSession()->addCommand(BreakDelete, QString::number(breakpoint->debuggerId), CmdImmediately); } } if (row >= 0) { controller->recalculateState(row); } } }; struct MIBreakpointController::DeleteHandler : MIBreakpointController::Handler { DeleteHandler(MIBreakpointController* c, const BreakpointDataPtr& b) - : Handler(c, b, 0) {} + : Handler(c, b, nullptr) {} void handle(const ResultRecord&) override { controller->m_pendingDeleted.removeAll(breakpoint); } }; struct MIBreakpointController::IgnoreChanges { IgnoreChanges(MIBreakpointController& controller) : controller(controller) { ++controller.m_ignoreChanges; } ~IgnoreChanges() { --controller.m_ignoreChanges; } MIBreakpointController& controller; }; MIBreakpointController::MIBreakpointController(MIDebugSession * parent) : IBreakpointController(parent) { Q_ASSERT(parent); connect(parent, &MIDebugSession::inferiorStopped, this, &MIBreakpointController::programStopped); int numBreakpoints = breakpointModel()->breakpoints().size(); for (int row = 0; row < numBreakpoints; ++row) breakpointAdded(row); } MIDebugSession *MIBreakpointController::debugSession() const { Q_ASSERT(QObject::parent()); return static_cast(const_cast(QObject::parent())); } int MIBreakpointController::breakpointRow(const BreakpointDataPtr& breakpoint) { return m_breakpoints.indexOf(breakpoint); } void MIBreakpointController::setDeleteDuplicateBreakpoints(bool enable) { m_deleteDuplicateBreakpoints = enable; } void MIBreakpointController::initSendBreakpoints() { for (int row = 0; row < m_breakpoints.size(); ++row) { BreakpointDataPtr breakpoint = m_breakpoints[row]; if (breakpoint->debuggerId < 0 && breakpoint->sent == 0) { createBreakpoint(row); } } } void MIBreakpointController::breakpointAdded(int row) { if (m_ignoreChanges > 0) return; auto breakpoint = BreakpointDataPtr::create(); m_breakpoints.insert(row, breakpoint); const Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); if (!modelBreakpoint->enabled()) breakpoint->dirty |= BreakpointModel::EnableColumnFlag; if (!modelBreakpoint->condition().isEmpty()) breakpoint->dirty |= BreakpointModel::ConditionColumnFlag; if (modelBreakpoint->ignoreHits() != 0) breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag; if (!modelBreakpoint->address().isEmpty()) breakpoint->dirty |= BreakpointModel::LocationColumnFlag; createBreakpoint(row); } void MIBreakpointController::breakpointModelChanged(int row, BreakpointModel::ColumnFlags columns) { if (m_ignoreChanges > 0) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); breakpoint->dirty |= columns & (BreakpointModel::EnableColumnFlag | BreakpointModel::LocationColumnFlag | BreakpointModel::ConditionColumnFlag | BreakpointModel::IgnoreHitsColumnFlag); if (breakpoint->sent != 0) { // Throttle the amount of commands we send to GDB; the response handler of currently // in-flight commands will take care of sending the update. // This also prevents us from sending updates while a break-create command is in-flight. return; } if (breakpoint->debuggerId < 0) { createBreakpoint(row); } else { sendUpdates(row); } } void MIBreakpointController::breakpointAboutToBeDeleted(int row) { if (m_ignoreChanges > 0) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); m_breakpoints.removeAt(row); if (breakpoint->debuggerId < 0) { // Two possibilities: // (1) Breakpoint has never been sent to GDB, so we're done // (2) Breakpoint has been sent to GDB, but we haven't received // the response yet; the response handler will delete the // breakpoint. return; } if (debugSession()->debuggerStateIsOn(s_dbgNotStarted)) return; debugSession()->addCommand( BreakDelete, QString::number(breakpoint->debuggerId), new DeleteHandler(this, breakpoint), CmdImmediately); m_pendingDeleted << breakpoint; } // Note: despite the name, this is in fact session state changed. void MIBreakpointController::debuggerStateChanged(IDebugSession::DebuggerState state) { IgnoreChanges ignoreChanges(*this); if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { for (int row = 0; row < m_breakpoints.size(); ++row) { updateState(row, Breakpoint::NotStartedState); } } else if (state == IDebugSession::StartingState) { for (int row = 0; row < m_breakpoints.size(); ++row) { updateState(row, Breakpoint::DirtyState); } } } void MIBreakpointController::createBreakpoint(int row) { if (debugSession()->debuggerStateIsOn(s_dbgNotStarted)) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); Q_ASSERT(breakpoint->debuggerId < 0 && breakpoint->sent == 0); if (modelBreakpoint->location().isEmpty()) return; if (modelBreakpoint->kind() == Breakpoint::CodeBreakpoint) { QString location; if (modelBreakpoint->line() != -1) { location = QString("%0:%1") .arg(modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash)) .arg(modelBreakpoint->line() + 1); } else { location = modelBreakpoint->location(); } if (location == "catch throw") { location = "exception throw"; } // Note: We rely on '-f' to be automatically added by the MICommand logic QString arguments; if (!modelBreakpoint->enabled()) arguments += "-d "; if (!modelBreakpoint->condition().isEmpty()) arguments += QString("-c %0 ").arg(Utils::quoteExpression(modelBreakpoint->condition())); if (modelBreakpoint->ignoreHits() != 0) arguments += QString("-i %0 ").arg(modelBreakpoint->ignoreHits()); arguments += Utils::quoteExpression(location); BreakpointModel::ColumnFlags sent = BreakpointModel::EnableColumnFlag | BreakpointModel::ConditionColumnFlag | BreakpointModel::IgnoreHitsColumnFlag | BreakpointModel::LocationColumnFlag; debugSession()->addCommand(BreakInsert, arguments, new InsertedHandler(this, breakpoint, sent), CmdImmediately); } else { QString opt; if (modelBreakpoint->kind() == Breakpoint::ReadBreakpoint) opt = "-r "; else if (modelBreakpoint->kind() == Breakpoint::AccessBreakpoint) opt = "-a "; debugSession()->addCommand(BreakWatch, opt + Utils::quoteExpression(modelBreakpoint->location()), new InsertedHandler(this, breakpoint, BreakpointModel::LocationColumnFlag), CmdImmediately); } recalculateState(row); } void MIBreakpointController::sendUpdates(int row) { if (debugSession()->debuggerStateIsOn(s_dbgNotStarted)) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); Q_ASSERT(breakpoint->debuggerId >= 0 && breakpoint->sent == 0); if (breakpoint->dirty & BreakpointModel::LocationColumnFlag) { // Gdb considers locations as fixed, so delete and re-create the breakpoint debugSession()->addCommand(BreakDelete, QString::number(breakpoint->debuggerId), CmdImmediately); breakpoint->debuggerId = -1; createBreakpoint(row); return; } if (breakpoint->dirty & BreakpointModel::EnableColumnFlag) { debugSession()->addCommand(modelBreakpoint->enabled() ? BreakEnable : BreakDisable, QString::number(breakpoint->debuggerId), new UpdateHandler(this, breakpoint, BreakpointModel::EnableColumnFlag), CmdImmediately); } if (breakpoint->dirty & BreakpointModel::IgnoreHitsColumnFlag) { debugSession()->addCommand(BreakAfter, QString("%0 %1").arg(breakpoint->debuggerId) .arg(modelBreakpoint->ignoreHits()), new UpdateHandler(this, breakpoint, BreakpointModel::IgnoreHitsColumnFlag), CmdImmediately); } if (breakpoint->dirty & BreakpointModel::ConditionColumnFlag) { debugSession()->addCommand(BreakCondition, QString("%0 %1").arg(breakpoint->debuggerId) .arg(modelBreakpoint->condition()), new UpdateHandler(this, breakpoint, BreakpointModel::ConditionColumnFlag), CmdImmediately); } recalculateState(row); } void MIBreakpointController::recalculateState(int row) { BreakpointDataPtr breakpoint = m_breakpoints.at(row); if (breakpoint->errors == 0) updateErrorText(row, QString()); Breakpoint::BreakpointState newState = Breakpoint::NotStartedState; if (debugSession()->state() != IDebugSession::EndedState && debugSession()->state() != IDebugSession::NotStartedState) { if (!debugSession()->debuggerStateIsOn(s_dbgNotStarted)) { if (breakpoint->dirty == 0 && breakpoint->sent == 0) { if (breakpoint->pending) { newState = Breakpoint::PendingState; } else { newState = Breakpoint::CleanState; } } else { newState = Breakpoint::DirtyState; } } } updateState(row, newState); } int MIBreakpointController::rowFromDebuggerId(int gdbId) const { for (int row = 0; row < m_breakpoints.size(); ++row) { if (gdbId == m_breakpoints[row]->debuggerId) return row; } return -1; } void MIBreakpointController::notifyBreakpointCreated(const AsyncRecord& r) { const Value& miBkpt = r["bkpt"]; // Breakpoints with multiple locations are represented by a parent breakpoint (e.g. 1) // and multiple child breakpoints (e.g. 1.1, 1.2, 1.3, ...). // We ignore the child breakpoints here in the current implementation; this can lead to dubious // results in the UI when breakpoints are marked in document views (e.g. when a breakpoint // applies to multiple overloads of a C++ function simultaneously) and in disassembly // (e.g. when a breakpoint is set in an inlined functions). if (miBkpt["number"].literal().contains('.')) return; createFromDebugger(miBkpt); } void MIBreakpointController::notifyBreakpointModified(const AsyncRecord& r) { const Value& miBkpt = r["bkpt"]; const int gdbId = miBkpt["number"].toInt(); const int row = rowFromDebuggerId(gdbId); if (row < 0) { for (const auto& breakpoint : m_pendingDeleted) { if (breakpoint->debuggerId == gdbId) { // Received a modification of a breakpoint whose deletion is currently // in-flight; simply ignore it. return; } } qCWarning(DEBUGGERCOMMON) << "Received a modification of an unknown breakpoint"; createFromDebugger(miBkpt); } else { updateFromDebugger(row, miBkpt); } } void MIBreakpointController::notifyBreakpointDeleted(const AsyncRecord& r) { const int gdbId = r["id"].toInt(); const int row = rowFromDebuggerId(gdbId); if (row < 0) { // The user may also have deleted the breakpoint via the UI simultaneously return; } IgnoreChanges ignoreChanges(*this); breakpointModel()->removeRow(row); m_breakpoints.removeAt(row); } void MIBreakpointController::createFromDebugger(const Value& miBkpt) { const QString type = miBkpt["type"].literal(); Breakpoint::BreakpointKind gdbKind; if (type == "breakpoint") { gdbKind = Breakpoint::CodeBreakpoint; } else if (type == "watchpoint" || type == "hw watchpoint") { gdbKind = Breakpoint::WriteBreakpoint; } else if (type == "read watchpoint") { gdbKind = Breakpoint::ReadBreakpoint; } else if (type == "acc watchpoint") { gdbKind = Breakpoint::AccessBreakpoint; } else { qCWarning(DEBUGGERCOMMON) << "Unknown breakpoint type " << type; return; } // During debugger startup, we want to avoid creating duplicate breakpoints when the same breakpoint // appears both in our model and in a init file e.g. .gdbinit BreakpointModel* model = breakpointModel(); const int numRows = model->rowCount(); for (int row = 0; row < numRows; ++row) { BreakpointDataPtr breakpoint = m_breakpoints.at(row); const bool breakpointSent = breakpoint->debuggerId >= 0 || breakpoint->sent != 0; if (breakpointSent && !m_deleteDuplicateBreakpoints) continue; Breakpoint* modelBreakpoint = model->breakpoint(row); if (modelBreakpoint->kind() != gdbKind) continue; if (gdbKind == Breakpoint::CodeBreakpoint) { bool sameLocation = false; if (miBkpt.hasField("fullname") && miBkpt.hasField("line")) { const QString location = Utils::unquoteExpression(miBkpt["fullname"].literal()); const int line = miBkpt["line"].toInt() - 1; if (location == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) && line == modelBreakpoint->line()) { sameLocation = true; } } if (!sameLocation && miBkpt.hasField("original-location")) { const QString location = miBkpt["original-location"].literal(); if (location == modelBreakpoint->location()) { sameLocation = true; } else { QRegExp rx("^(.+):(\\d+)$"); if (rx.indexIn(location) != -1 && Utils::unquoteExpression(rx.cap(1)) == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) && rx.cap(2).toInt() - 1 == modelBreakpoint->line()) { sameLocation = true; } } } if (!sameLocation && miBkpt.hasField("what") && miBkpt["what"].literal() == "exception throw") { if (modelBreakpoint->expression() == "catch throw" || modelBreakpoint->expression() == "exception throw") { sameLocation = true; } } if (!sameLocation) continue; } else { if (Utils::unquoteExpression(miBkpt["original-location"].literal()) != modelBreakpoint->expression()) { continue; } } QString condition; if (miBkpt.hasField("cond")) { condition = miBkpt["cond"].literal(); } if (condition != modelBreakpoint->condition()) continue; // Breakpoint is equivalent if (!breakpointSent) { breakpoint->debuggerId = miBkpt["number"].toInt(); // Reasonable people can probably have different opinions about what the "correct" behavior // should be for the "enabled" and "ignore hits" column. // Here, we let the status in KDevelop's UI take precedence, which we suspect to be // marginally more useful. Dirty data will be sent during the initial sending of the // breakpoint list. const bool gdbEnabled = miBkpt["enabled"].literal() == "y"; if (gdbEnabled != modelBreakpoint->enabled()) breakpoint->dirty |= BreakpointModel::EnableColumnFlag; int gdbIgnoreHits = 0; if (miBkpt.hasField("ignore")) gdbIgnoreHits = miBkpt["ignore"].toInt(); if (gdbIgnoreHits != modelBreakpoint->ignoreHits()) breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag; updateFromDebugger(row, miBkpt, BreakpointModel::EnableColumnFlag | BreakpointModel::IgnoreHitsColumnFlag); return; } // Breakpoint from the model has already been sent, but we want to delete duplicates // It is not entirely clear _which_ breakpoint ought to be deleted, and reasonable people // may have different opinions. // We suspect that it is marginally more useful to delete the existing model breakpoint; // after all, this only happens when a user command creates a breakpoint, and perhaps the // user intends to modify the breakpoint they created manually. In any case, // this situation should only happen rarely (in particular, when a user sets a breakpoint // inside the remote run script). model->removeRows(row, 1); break; // fall through to pick up the manually created breakpoint in the model } // No equivalent breakpoint found, or we have one but want to be consistent with GDB's // behavior of allowing multiple equivalent breakpoint. IgnoreChanges ignoreChanges(*this); const int row = m_breakpoints.size(); Q_ASSERT(row == model->rowCount()); switch (gdbKind) { case Breakpoint::WriteBreakpoint: model->addWatchpoint(); break; case Breakpoint::ReadBreakpoint: model->addReadWatchpoint(); break; case Breakpoint::AccessBreakpoint: model->addAccessWatchpoint(); break; case Breakpoint::CodeBreakpoint: model->addCodeBreakpoint(); break; default: Q_ASSERT(false); return; } // Since we are in ignore-changes mode, we have to add the BreakpointData manually. auto breakpoint = BreakpointDataPtr::create(); m_breakpoints << breakpoint; breakpoint->debuggerId = miBkpt["number"].toInt(); updateFromDebugger(row, miBkpt); } // This method is required for the legacy interface which will be removed void MIBreakpointController::sendMaybe(KDevelop::Breakpoint*) { Q_ASSERT(false); } void MIBreakpointController::updateFromDebugger(int row, const Value& miBkpt, BreakpointModel::ColumnFlags lockedColumns) { IgnoreChanges ignoreChanges(*this); BreakpointDataPtr breakpoint = m_breakpoints[row]; Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); // Commands that are currently in flight will overwrite the modification we have received, // so do not update the corresponding data lockedColumns |= breakpoint->sent | breakpoint->dirty; // TODO: // Gdb has a notion of "original-location", which is the "expression" or "location" used // to set the breakpoint, and notions of the actual location of the breakpoint (function name, // address, source file and line). The breakpoint model currently does not map well to this // (though it arguably should), and does not support multi-location breakpoints at all. // We try to do the best we can until the breakpoint model gets cleaned up. if (miBkpt.hasField("fullname") && miBkpt.hasField("line")) { modelBreakpoint->setLocation( QUrl::fromLocalFile(Utils::unquoteExpression(miBkpt["fullname"].literal())), miBkpt["line"].toInt() - 1); } else if (miBkpt.hasField("original-location")) { QRegExp rx("^(.+):(\\d+)$"); QString location = miBkpt["original-location"].literal(); if (rx.indexIn(location) != -1) { modelBreakpoint->setLocation(QUrl::fromLocalFile(Utils::unquoteExpression(rx.cap(1))), rx.cap(2).toInt()-1); } else { modelBreakpoint->setData(Breakpoint::LocationColumn, Utils::unquoteExpression(location)); } } else if (miBkpt.hasField("what")) { modelBreakpoint->setExpression(miBkpt["what"].literal()); } else { qWarning() << "Breakpoint doesn't contain required location/expression data"; } if (!(lockedColumns & BreakpointModel::EnableColumnFlag)) { bool enabled = true; if (miBkpt.hasField("enabled")) { if (miBkpt["enabled"].literal() == "n") enabled = false; } modelBreakpoint->setData(Breakpoint::EnableColumn, enabled ? Qt::Checked : Qt::Unchecked); breakpoint->dirty &= ~BreakpointModel::EnableColumnFlag; } if (!(lockedColumns & BreakpointModel::ConditionColumnFlag)) { QString condition; if (miBkpt.hasField("cond")) { condition = miBkpt["cond"].literal(); } modelBreakpoint->setCondition(condition); breakpoint->dirty &= ~BreakpointModel::ConditionColumnFlag; } if (!(lockedColumns & BreakpointModel::IgnoreHitsColumnFlag)) { int ignoreHits = 0; if (miBkpt.hasField("ignore")) { ignoreHits = miBkpt["ignore"].toInt(); } modelBreakpoint->setIgnoreHits(ignoreHits); breakpoint->dirty &= ~BreakpointModel::IgnoreHitsColumnFlag; } breakpoint->pending = false; if (miBkpt.hasField("addr") && miBkpt["addr"].literal() == "") { breakpoint->pending = true; } int hitCount = 0; if (miBkpt.hasField("times")) { hitCount = miBkpt["times"].toInt(); } updateHitCount(row, hitCount); recalculateState(row); } void MIBreakpointController::programStopped(const AsyncRecord& r) { if (!r.hasField("reason")) return; const QString reason = r["reason"].literal(); int debuggerId = -1; if (reason == "breakpoint-hit") { debuggerId = r["bkptno"].toInt(); } else if (reason == "watchpoint-trigger") { debuggerId = r["wpt"]["number"].toInt(); } else if (reason == "read-watchpoint-trigger") { debuggerId = r["hw-rwpt"]["number"].toInt(); } else if (reason == "access-watchpoint-trigger") { debuggerId = r["hw-awpt"]["number"].toInt(); } if (debuggerId < 0) return; int row = rowFromDebuggerId(debuggerId); if (row < 0) return; QString msg; if (r.hasField("value")) { if (r["value"].hasField("old")) { msg += i18n("
Old value: %1", r["value"]["old"].literal()); } if (r["value"].hasField("new")) { msg += i18n("
New value: %1", r["value"]["new"].literal()); } } notifyHit(row, msg); } diff --git a/debuggers/common/midebugger.cpp b/debuggers/common/midebugger.cpp index c1d20b1775..ff09e381ae 100644 --- a/debuggers/common/midebugger.cpp +++ b/debuggers/common/midebugger.cpp @@ -1,350 +1,350 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "midebugger.h" #include "debuglog.h" #include "mi/micommand.h" #include #include #include #include #include #include #include // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown using namespace KDevMI; using namespace KDevMI::MI; MIDebugger::MIDebugger(QObject* parent) : QObject(parent) , process_(nullptr) , currentCmd_(nullptr) { process_ = new KProcess(this); process_->setOutputChannelMode(KProcess::SeparateChannels); connect(process_, &KProcess::readyReadStandardOutput, this, &MIDebugger::readyReadStandardOutput); connect(process_, &KProcess::readyReadStandardError, this, &MIDebugger::readyReadStandardError); connect(process_, static_cast(&KProcess::finished), this, &MIDebugger::processFinished); connect(process_, static_cast(&KProcess::error), this, &MIDebugger::processErrored); } MIDebugger::~MIDebugger() { // prevent Qt warning: QProcess: Destroyed while process is still running. if (process_ && process_->state() == QProcess::Running) { disconnect(process_, static_cast(&KProcess::error), this, &MIDebugger::processErrored); process_->kill(); process_->waitForFinished(10); } } void MIDebugger::execute(MICommand* command) { currentCmd_ = command; QString commandText = currentCmd_->cmdToSend(); qCDebug(DEBUGGERCOMMON) << "SEND:" << commandText.trimmed(); QByteArray commandUtf8 = commandText.toUtf8(); process_->write(commandUtf8, commandUtf8.length()); command->markAsSubmitted(); QString prettyCmd = currentCmd_->cmdToSend(); prettyCmd.remove( QRegExp("set prompt \032.\n") ); prettyCmd = "(gdb) " + prettyCmd; if (currentCmd_->isUserCommand()) emit userCommandOutput(prettyCmd); else emit internalCommandOutput(prettyCmd); } bool MIDebugger::isReady() const { - return currentCmd_ == 0; + return currentCmd_ == nullptr; } void MIDebugger::interrupt() { //TODO:win32 Porting needed int pid = process_->pid(); if (pid != 0) { ::kill(pid, SIGINT); } } MICommand* MIDebugger::currentCommand() const { return currentCmd_; } void MIDebugger::kill() { process_->kill(); } void MIDebugger::readyReadStandardOutput() { process_->setReadChannel(QProcess::StandardOutput); buffer_ += process_->readAll(); for (;;) { /* In MI mode, all messages are exactly one line. See if we have any complete lines in the buffer. */ int i = buffer_.indexOf('\n'); if (i == -1) break; QByteArray reply(buffer_.left(i)); buffer_ = buffer_.mid(i+1); processLine(reply); } } void MIDebugger::readyReadStandardError() { process_->setReadChannel(QProcess::StandardError); emit debuggerInternalOutput(QString::fromUtf8(process_->readAll())); } void MIDebugger::processLine(const QByteArray& line) { qCDebug(DEBUGGERCOMMON) << "Debugger (" << process_->pid() <<") output: " << line; FileSymbol file; file.contents = line; std::unique_ptr r(mi_parser_.parse(&file)); if (!r) { // FIXME: Issue an error! qCDebug(DEBUGGERCOMMON) << "Invalid MI message:" << line; // We don't consider the current command done. // So, if a command results in unparseable reply, // we'll just wait for the "right" reply, which might // never come. However, marking the command as // done in this case is even more risky. // It's probably possible to get here if we're debugging // natively without PTY, though this is uncommon case. return; } #ifndef DEBUG_NO_TRY try { #endif switch(r->kind) { case MI::Record::Result: { MI::ResultRecord& result = static_cast(*r); // it's still possible for the user to issue a MI command, // emit correct signal if (currentCmd_ && currentCmd_->isUserCommand()) { emit userCommandOutput(QString::fromUtf8(line) + '\n'); } else { emit internalCommandOutput(QString::fromUtf8(line) + '\n'); } // GDB doc: "running" and "exit" are status codes equivalent to "done" if (result.reason == "done" || result.reason == "running" || result.reason == "exit") { if (!currentCmd_) { qCDebug(DEBUGGERCOMMON) << "Received a result without a pending command"; } else { qCDebug(DEBUGGERCOMMON) << "Result token is" << result.token; Q_ASSERT(currentCmd_->token() == result.token); currentCmd_->markAsCompleted(); qCDebug(DEBUGGERCOMMON) << "Command successful, times " << currentCmd_->totalProcessingTime() << currentCmd_->queueTime() << currentCmd_->gdbProcessingTime(); currentCmd_->invokeHandler(result); } } else if (result.reason == "error") { qCDebug(DEBUGGERCOMMON) << "Handling error"; currentCmd_->markAsCompleted(); qCDebug(DEBUGGERCOMMON) << "Command error, times" << currentCmd_->totalProcessingTime() << currentCmd_->queueTime() << currentCmd_->gdbProcessingTime(); // Some commands want to handle errors themself. if (currentCmd_->handlesError() && currentCmd_->invokeHandler(result)) { qCDebug(DEBUGGERCOMMON) << "Invoked custom handler\n"; // Done, nothing more needed } else emit error(result); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled result code: " << result.reason; } delete currentCmd_; currentCmd_ = nullptr; emit ready(); break; } case MI::Record::Async: { MI::AsyncRecord& async = dynamic_cast(*r); switch (async.subkind) { case MI::AsyncRecord::Exec: { // Prefix '*'; asynchronous state changes of the target if (async.reason == "stopped") { emit programStopped(async); } else if (async.reason == "running") { emit programRunning(); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled exec notification: " << async.reason; } break; } case MI::AsyncRecord::Notify: { // Prefix '='; supplementary information that we should handle (new breakpoint etc.) emit notification(async); break; } case MI::AsyncRecord::Status: { // Prefix '+'; GDB documentation: // On-going status information about progress of a slow operation; may be ignored break; } default: Q_ASSERT(false); } break; } case MI::Record::Stream: { MI::StreamRecord& s = dynamic_cast(*r); if (s.subkind == MI::StreamRecord::Target) { emit applicationOutput(s.message); } else if (s.subkind == MI::StreamRecord::Console) { if (currentCmd_ && currentCmd_->isUserCommand()) emit userCommandOutput(s.message); else emit internalCommandOutput(s.message); if (currentCmd_) currentCmd_->newOutput(s.message); } else { emit debuggerInternalOutput(s.message); } emit streamRecord(s); break; } case MI::Record::Prompt: break; } #ifndef DEBUG_NO_TRY } catch(const std::exception& e) { KMessageBox::detailedSorry( qApp->activeWindow(), i18nc("Internal debugger error", "

The debugger component encountered internal error while " "processing reply from gdb. Please submit a bug report."), i18n("The exception is: %1\n" "The MI response is: %2", e.what(), QString::fromLatin1(line)), i18n("Internal debugger error")); } #endif } void MIDebugger::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(DEBUGGERCOMMON) << "Debugger FINISHED\n"; bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit; emit userCommandOutput("Process exited\n"); emit exited(abnormal, i18n("Process exited")); } void MIDebugger::processErrored(QProcess::ProcessError error) { qCDebug(DEBUGGERCOMMON) << "Debugger ERRORED" << error; if(error == QProcess::FailedToStart) { KMessageBox::information( qApp->activeWindow(), i18n("Could not start debugger." "

Could not run '%1'. " "Make sure that the path name is specified correctly.", debuggerBinary_), i18n("Could not start debugger")); emit userCommandOutput("Process failed to start\n"); emit exited(true, i18n("Process failed to start")); } else if (error == QProcess::Crashed) { KMessageBox::error( qApp->activeWindow(), i18n("Debugger crashed." "

The debugger process '%1' crashed.
" "Because of that the debug session has to be ended.
" "Try to reproduce the crash without KDevelop and report a bug.
", debuggerBinary_), i18n("Debugger crashed")); emit userCommandOutput("Process crashed\n"); emit exited(true, i18n("Process crashed")); } } diff --git a/debuggers/common/midebugger.h b/debuggers/common/midebugger.h index 521cea8fc7..aa62bce6a6 100644 --- a/debuggers/common/midebugger.h +++ b/debuggers/common/midebugger.h @@ -1,152 +1,152 @@ /* * Low level Debugger MI interface. * * Copyright 2007 Vladimir Prus * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MIDEBUGGER_H #define MIDEBUGGER_H #include "mi/mi.h" #include "mi/miparser.h" #include #include #include class KConfigGroup; class KProcess; namespace KDevMI { namespace MI { class MICommand; } class MIDebugger : public QObject { Q_OBJECT public: - explicit MIDebugger(QObject* parent = 0); + explicit MIDebugger(QObject* parent = nullptr); ~MIDebugger() override; /** Starts the debugger. This should be done after connecting to all signals the client is interested in. */ virtual bool start(KConfigGroup& config, const QStringList& extraArguments = {}) = 0; /** Executes a command. This method may be called at most once each time 'ready' is emitted. When the debugger instance is just constructed, one should wait for 'ready' as well. The ownership of 'command' is transferred to the debugger. */ void execute(MI::MICommand* command); /** Returns true if 'execute' can be called immediately. */ bool isReady() const; /** FIXME: temporary, to be eliminated. */ MI::MICommand* currentCommand() const; /** Arrange to debugger to stop doing whatever it's doing, and start waiting for a command. FIXME: probably should make sure that 'ready' is emitted, or something. */ void interrupt(); /** Kills the debugger. */ void kill(); Q_SIGNALS: /** Emitted when debugger becomes ready -- i.e. when isReady call will return true. */ void ready(); /** Emitted when the debugger itself exits. This could happen because it just crashed due to internal bug, or we killed it explicitly. */ void exited(bool abnormal, const QString &msg); /** Emitted when debugger reports stop, with 'r' being the data provided by the debugger. */ void programStopped(const MI::AsyncRecord& r); /** Emitted when debugger believes that the program is running. */ void programRunning(); /** Emitted for each MI stream record found. Presently only used to recognize some CLI messages that mean that the program has died. FIXME: connect to parseCliLine */ void streamRecord(const MI::StreamRecord& s); /** Reports an async notification record. */ void notification(const MI::AsyncRecord& n); /** Emitted for error that is not handled by the command being executed. */ void error(const MI::ResultRecord& s); /** Reports output from the running application. Generally output will only be available when using remote debugger targets. When running locally, the output will either appear on debugger stdout, and ignored, or routed via pty. */ void applicationOutput(const QString& s); /** Reports output of a command explicitly typed by the user, or output from .gdbinit commands. */ void userCommandOutput(const QString& s); /** Reports output of a command issued internally by KDevelop. */ void internalCommandOutput(const QString& s); /** Reports debugger interal output, including stderr output from debugger and the 'log' MI channel */ void debuggerInternalOutput(const QString& s); protected Q_SLOTS: void readyReadStandardOutput(); void readyReadStandardError(); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); void processErrored(QProcess::ProcessError); protected: void processLine(const QByteArray& line); protected: QString debuggerBinary_; KProcess* process_; MI::MICommand* currentCmd_; MI::MIParser mi_parser_; /** The unprocessed output from debugger. Output is processed as soon as we see newline. */ QByteArray buffer_; }; } #endif diff --git a/debuggers/common/midebuggerplugin.h b/debuggers/common/midebuggerplugin.h index 0883cc64c9..58611e2327 100644 --- a/debuggers/common/midebuggerplugin.h +++ b/debuggers/common/midebuggerplugin.h @@ -1,156 +1,156 @@ /* * Common code for MI debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2007 Hamish Rodda * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef MIDEBUGGERPLUGIN_H #define MIDEBUGGERPLUGIN_H #include "config.h" #include #include #include #include #include class QDBusInterface; class QSignalMapper; class QUrl; namespace KDevelop { class Context; } namespace KDevMI { class MIDebugSession; class MIDebuggerPlugin : public KDevelop::IPlugin, public KDevelop::IStatus { Q_OBJECT Q_INTERFACES(KDevelop::IStatus) public: MIDebuggerPlugin(const QString& componentName, QObject *parent); ~MIDebuggerPlugin() override; void unload() override; KDevelop::ContextMenuExtension contextMenuExtension( KDevelop::Context* ) override; virtual MIDebugSession *createSession() = 0; virtual void setupToolviews() = 0; /** * The implementation should be sure it's safe to call * even when tool views are already unloaded. */ virtual void unloadToolviews() = 0; //BEGIN IStatus public: QString statusName() const override; Q_SIGNALS: void clearMessage(KDevelop::IStatus*) override; void showMessage(KDevelop::IStatus*, const QString & message, int timeout = 0) override; void hideProgress(KDevelop::IStatus*) override; void showProgress(KDevelop::IStatus*, int minimum, int maximum, int value) override; void showErrorMessage(const QString&, int) override; //END IStatus Q_SIGNALS: void reset(); void stopDebugger(); void attachTo(int pid); void coreFile(const QString& core); void runUntil(const QUrl &url, int line); void jumpTo(const QUrl &url, int line); void addWatchVariable(const QString& var); void evaluateExpression(const QString& expr); void raiseDebuggerConsoleViews(); protected Q_SLOTS: void slotDebugExternalProcess(QObject* interface); void slotExamineCore(); #if KF5SysGuard_FOUND void slotAttachProcess(); #endif void slotDBusServiceRegistered(const QString& service); void slotDBusServiceUnregistered(const QString& service); void slotCloseDrKonqi(); protected: void setupActions(); void setupDBus(); void attachProcess(int pid); void showStatusMessage(const QString& msg, int timeout); private: QHash m_drkonqis; QSignalMapper* m_drkonqiMap; QString m_drkonqi; }; template class DebuggerToolFactory : public KDevelop::IToolViewFactory { public: DebuggerToolFactory(Plugin * plugin, const QString &id, Qt::DockWidgetArea defaultArea) : m_plugin(plugin), m_id(id), m_defaultArea(defaultArea) {} - QWidget* create(QWidget *parent = 0) override + QWidget* create(QWidget *parent = nullptr) override { return new T(m_plugin, parent); } QString id() const override { return m_id; } Qt::DockWidgetArea defaultPosition() override { return m_defaultArea; } void viewCreated(Sublime::View* view) override { if (view->widget()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("requestRaise()")) != -1) QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise())); } private: Plugin * m_plugin; QString m_id; Qt::DockWidgetArea m_defaultArea; }; } // end of namespace KDevMI #endif // MIDEBUGGERPLUGIN_H diff --git a/debuggers/common/midebugjobs.h b/debuggers/common/midebugjobs.h index 581537048d..c09778c1da 100644 --- a/debuggers/common/midebugjobs.h +++ b/debuggers/common/midebugjobs.h @@ -1,104 +1,104 @@ /* * Common Code for Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MIDEBUGJOBS_H #define MIDEBUGJOBS_H #include class IExecutePlugin; namespace KDevelop { class OutputModel; class ILaunchConfiguration; } namespace KDevMI { class MIDebuggerPlugin; class MIDebugSession; class MIDebugJob : public KDevelop::OutputJob { Q_OBJECT public: MIDebugJob(MIDebuggerPlugin* p, KDevelop::ILaunchConfiguration* launchcfg, IExecutePlugin* plugin, - QObject* parent = 0); + QObject* parent = nullptr); void start() override; protected: bool doKill() override; private Q_SLOTS: void stdoutReceived(const QStringList&); void stderrReceived(const QStringList&); void done(); private: KDevelop::OutputModel* model(); MIDebugSession* m_session; KDevelop::ILaunchConfiguration* m_launchcfg; IExecutePlugin* m_execute; }; class MIExamineCoreJob : public KJob { Q_OBJECT public: MIExamineCoreJob(MIDebuggerPlugin *plugin, QObject *parent = nullptr); void start() override; protected: bool doKill() override; private Q_SLOTS: void done(); private: MIDebugSession *m_session; }; class MIAttachProcessJob : public KJob { Q_OBJECT public: MIAttachProcessJob(MIDebuggerPlugin *plugin, int pid, QObject *parent = nullptr); void start() override; protected: bool doKill() override; private Q_SLOTS: void done(); private: int m_pid; MIDebugSession *m_session; }; } // end of namespace KDevMI #endif // MIDEBUGJOBS_H diff --git a/debuggers/common/midebugsession.cpp b/debuggers/common/midebugsession.cpp index bf60e10775..b8be4cf39b 100644 --- a/debuggers/common/midebugsession.cpp +++ b/debuggers/common/midebugsession.cpp @@ -1,1316 +1,1316 @@ /* * Common code for debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "midebugsession.h" #include "debuglog.h" #include "midebugger.h" #include "mivariable.h" #include "mi/mi.h" #include "mi/micommand.h" #include "mi/micommandqueue.h" #include "stty.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; MIDebugSession::MIDebugSession(MIDebuggerPlugin *plugin) : m_procLineMaker(new ProcessLineMaker(this)) , m_commandQueue(new CommandQueue) , m_sessionState(NotStartedState) , m_debugger(nullptr) , m_debuggerState(s_dbgNotStarted | s_appNotStarted) , m_stateReloadInProgress(false) , m_stateReloadNeeded(false) , m_tty(nullptr) , m_hasCrashed(false) , m_sourceInitFile(true) , m_plugin(plugin) { // setup signals connect(m_procLineMaker, &ProcessLineMaker::receivedStdoutLines, this, &MIDebugSession::inferiorStdoutLines); connect(m_procLineMaker, &ProcessLineMaker::receivedStderrLines, this, &MIDebugSession::inferiorStderrLines); // forward tty output to process line maker connect(this, &MIDebugSession::inferiorTtyStdout, m_procLineMaker, &ProcessLineMaker::slotReceivedStdout); connect(this, &MIDebugSession::inferiorTtyStderr, m_procLineMaker, &ProcessLineMaker::slotReceivedStderr); // FIXME: see if this still works //connect(statusBarIndicator, SIGNAL(doubleClicked()), // controller, SLOT(explainDebuggerStatus())); // FIXME: reimplement / re-enable //connect(this, SIGNAL(addWatchVariable(QString)), controller->variables(), SLOT(slotAddWatchVariable(QString))); //connect(this, SIGNAL(evaluateExpression(QString)), controller->variables(), SLOT(slotEvaluateExpression(QString))); } MIDebugSession::~MIDebugSession() { qCDebug(DEBUGGERCOMMON) << "Destroying MIDebugSession"; // Deleting the session involves shutting down gdb nicely. // When were attached to a process, we must first detach so that the process // can continue running as it was before being attached. gdb is quite slow to // detach from a process, so we must process events within here to get a "clean" // shutdown. if (!debuggerStateIsOn(s_dbgNotStarted)) { stopDebugger(); } } IDebugSession::DebuggerState MIDebugSession::state() const { return m_sessionState; } QMap & MIDebugSession::variableMapping() { return m_allVariables; } MIVariable* MIDebugSession::findVariableByVarobjName(const QString &varobjName) const { if (m_allVariables.count(varobjName) == 0) return nullptr; return m_allVariables.value(varobjName); } void MIDebugSession::markAllVariableDead() { for (auto i = m_allVariables.begin(), e = m_allVariables.end(); i != e; ++i) { i.value()->markAsDead(); } m_allVariables.clear(); } bool MIDebugSession::restartAvaliable() const { if (debuggerStateIsOn(s_attached) || debuggerStateIsOn(s_core)) { return false; } else { return true; } } bool MIDebugSession::startDebugger(ILaunchConfiguration *cfg) { qCDebug(DEBUGGERCOMMON) << "Starting new debugger instance"; if (m_debugger) { qCWarning(DEBUGGERCOMMON) << "m_debugger object still exists"; delete m_debugger; m_debugger = nullptr; } m_debugger = createDebugger(); m_debugger->setParent(this); // output signals connect(m_debugger, &MIDebugger::applicationOutput, this, [this](const QString &output) { auto lines = output.split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts); for (auto &line : lines) { int p = line.length(); while (p >= 1 && (line[p-1] == '\r' || line[p-1] == '\n')) p--; if (p != line.length()) line.remove(p, line.length() - p); } emit inferiorStdoutLines(lines); }); connect(m_debugger, &MIDebugger::userCommandOutput, this, &MIDebugSession::debuggerUserCommandOutput); connect(m_debugger, &MIDebugger::internalCommandOutput, this, &MIDebugSession::debuggerInternalCommandOutput); connect(m_debugger, &MIDebugger::debuggerInternalOutput, this, &MIDebugSession::debuggerInternalOutput); // state signals connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::inferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::inferiorRunning); // internal handlers connect(m_debugger, &MIDebugger::ready, this, &MIDebugSession::slotDebuggerReady); connect(m_debugger, &MIDebugger::exited, this, &MIDebugSession::slotDebuggerExited); connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::slotInferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::slotInferiorRunning); connect(m_debugger, &MIDebugger::notification, this, &MIDebugSession::processNotification); // start the debugger. Do this after connecting all signals so that initial // debugger output, and important events like the debugger died are reported. QStringList extraArguments; if (!m_sourceInitFile) extraArguments << "--nx"; auto config = cfg ? cfg->config() // FIXME: this is only used when attachToProcess or examineCoreFile. // Change to use a global launch configuration when calling : KConfigGroup(KSharedConfig::openConfig(), "GDB Config"); if (!m_debugger->start(config, extraArguments)) { // debugger failed to start, ensure debugger and session state are correctly updated. setDebuggerStateOn(s_dbgFailedStart); return false; } // FIXME: here, we should wait until the debugger is up and waiting for input. // Then, clear s_dbgNotStarted // It's better to do this right away so that the state bit is always correct. setDebuggerStateOff(s_dbgNotStarted); // Initialise debugger. At this stage debugger is sitting wondering what to do, // and to whom. initializeDebugger(); qCDebug(DEBUGGERCOMMON) << "Debugger instance started"; return true; } bool MIDebugSession::startDebugging(ILaunchConfiguration* cfg, IExecutePlugin* iexec) { qCDebug(DEBUGGERCOMMON) << "Starting new debug session"; Q_ASSERT(cfg); Q_ASSERT(iexec); // Ensure debugger is started first if (debuggerStateIsOn(s_appNotStarted)) { emit showMessage(i18n("Running program"), 1000); } if (debuggerStateIsOn(s_dbgNotStarted)) { if (!startDebugger(cfg)) return false; } if (debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "Tried to run when debugger shutting down"; return false; } // Only dummy err here, actual erros have been checked already in the job and we don't get here if there were any QString err; QString executable = iexec->executable(cfg, err).toLocalFile(); configInferior(cfg, iexec, executable); // Set up the tty for the inferior bool config_useExternalTerminal = iexec->useTerminal(cfg); QString config_ternimalName = iexec->terminal(cfg); if (!config_ternimalName.isEmpty()) { // the external terminal cmd contains additional arguments, just get the terminal name config_ternimalName = KShell::splitArgs(config_ternimalName).first(); } m_tty.reset(new STTY(config_useExternalTerminal, config_ternimalName)); if (!config_useExternalTerminal) { connect(m_tty.get(), &STTY::OutOutput, this, &MIDebugSession::inferiorTtyStdout); connect(m_tty.get(), &STTY::ErrOutput, this, &MIDebugSession::inferiorTtyStderr); } QString tty(m_tty->getSlave()); if (tty.isEmpty()) { KMessageBox::information(qApp->activeWindow(), m_tty->lastError(), i18n("warning")); m_tty.reset(nullptr); return false; } addCommand(InferiorTtySet, tty); // Change the working directory to the correct one QString dir = iexec->workingDirectory(cfg).toLocalFile(); if (dir.isEmpty()) { dir = QFileInfo(executable).absolutePath(); } addCommand(EnvironmentCd, '"' + dir + '"'); // Set the run arguments QStringList arguments = iexec->arguments(cfg, err); if (!arguments.isEmpty()) addCommand(ExecArguments, KShell::joinArgs(arguments)); // Do other debugger specific config options and actually start the inferior program if (!execInferior(cfg, iexec, executable)) { return false; } QString config_startWith = cfg->config().readEntry(Config::StartWithEntry, QStringLiteral("ApplicationOutput")); if (config_startWith == "GdbConsole") { emit raiseDebuggerConsoleViews(); } else if (config_startWith == "FrameStack") { emit raiseFramestackViews(); } else { // ApplicationOutput is raised in DebugJob (by setting job to Verbose/Silent) } return true; } // FIXME: use same configuration process as startDebugging bool MIDebugSession::attachToProcess(int pid) { qCDebug(DEBUGGERCOMMON) << "Attach to process" << pid; emit showMessage(i18n("Attaching to process %1", pid), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } setDebuggerStateOn(s_attached); //set current state to running, after attaching we will get *stopped response setDebuggerStateOn(s_appRunning); addCommand(TargetAttach, QString::number(pid), this, &MIDebugSession::handleTargetAttach, CmdHandlesError); addCommand(new SentinelCommand(breakpointController(), &MIBreakpointController::initSendBreakpoints)); raiseEvent(connected_to_program); emit raiseFramestackViews(); return true; } void MIDebugSession::handleTargetAttach(const MI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not attach debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } bool MIDebugSession::examineCoreFile(const QUrl &debugee, const QUrl &coreFile) { emit showMessage(i18n("Examining core file %1", coreFile.toLocalFile()), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } // FIXME: support non-local URLs if (!loadCoreFile(nullptr, debugee.toLocalFile(), coreFile.toLocalFile())) { return false; } raiseEvent(program_state_changed); return true; } #define ENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) void MIDebugSession::setSessionState(DebuggerState state) { qCDebug(DEBUGGERCOMMON) << "Session state changed to" << ENUM_NAME(IDebugSession, DebuggerState, state) << "(" << state << ")"; if (state != m_sessionState) { m_sessionState = state; emit stateChanged(state); } } bool MIDebugSession::debuggerStateIsOn(DBGStateFlags state) const { return m_debuggerState & state; } DBGStateFlags MIDebugSession::debuggerState() const { return m_debuggerState; } void MIDebugSession::setDebuggerStateOn(DBGStateFlags stateOn) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, m_debuggerState | stateOn); m_debuggerState |= stateOn; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::setDebuggerStateOff(DBGStateFlags stateOff) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, m_debuggerState & ~stateOff); m_debuggerState &= ~stateOff; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::setDebuggerState(DBGStateFlags newState) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, newState); m_debuggerState = newState; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::debuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState) { int delta = oldState ^ newState; if (delta) { QString out; #define STATE_CHECK(name) \ do { \ if (delta & name) { \ out += ((newState & name) ? " +" : " -"); \ out += #name; \ delta &= ~name; \ } \ } while (0) STATE_CHECK(s_dbgNotStarted); STATE_CHECK(s_appNotStarted); STATE_CHECK(s_programExited); STATE_CHECK(s_attached); STATE_CHECK(s_core); STATE_CHECK(s_shuttingDown); STATE_CHECK(s_dbgBusy); STATE_CHECK(s_appRunning); STATE_CHECK(s_dbgNotListening); STATE_CHECK(s_automaticContinue); #undef STATE_CHECK for (unsigned int i = 0; delta != 0 && i < 32; ++i) { if (delta & (1 << i)) { delta &= ~(1 << i); out += ((1 << i) & newState) ? " +" : " -"; out += QString::number(i); } } qCDebug(DEBUGGERCOMMON) << "Debugger state change:" << out; } } void MIDebugSession::handleDebuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState) { QString message; DebuggerState oldSessionState = state(); DebuggerState newSessionState = oldSessionState; DBGStateFlags changedState = oldState ^ newState; if (newState & s_dbgNotStarted) { if (changedState & s_dbgNotStarted) { message = i18n("Debugger stopped"); emit finished(); } if (oldSessionState != NotStartedState || newState & s_dbgFailedStart) { newSessionState = EndedState; } } else { if (newState & s_appNotStarted) { if (oldSessionState == NotStartedState || oldSessionState == StartingState) { newSessionState = StartingState; } else { newSessionState = StoppedState; } } else if (newState & s_programExited) { if (changedState & s_programExited) { message = i18n("Process exited"); } newSessionState = StoppedState; } else if (newState & s_appRunning) { if (changedState & s_appRunning) { message = i18n("Application is running"); } newSessionState = ActiveState; } else { if (changedState & s_appRunning) { message = i18n("Application is paused"); } newSessionState = PausedState; } } // And now? :-) qCDebug(DEBUGGERCOMMON) << "Debugger state changed to: " << newState << message; if (!message.isEmpty()) emit showMessage(message, 3000); emit debuggerStateChanged(oldState, newState); // must be last, since it can lead to deletion of the DebugSession if (newSessionState != oldSessionState) { setSessionState(newSessionState); } } void MIDebugSession::restartDebugger() { // We implement restart as kill + slotRun, as opposed as plain "run" // command because kill + slotRun allows any special logic in slotRun // to apply for restart. // // That includes: // - checking for out-of-date project // - special setup for remote debugging. // // Had we used plain 'run' command, restart for remote debugging simply // would not work. if (!debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) { // FIXME: s_dbgBusy or m_debugger->isReady()? if (debuggerStateIsOn(s_dbgBusy)) { interruptDebugger(); } // The -exec-abort is not implemented in gdb // addCommand(ExecAbort); addCommand(NonMI, "kill"); } run(); } void MIDebugSession::stopDebugger() { if (debuggerStateIsOn(s_dbgNotStarted)) { // we are force to stop even before debugger started, just reset qCDebug(DEBUGGERCOMMON) << "Stopping debugger when it's not started"; return; } m_commandQueue->clear(); qCDebug(DEBUGGERCOMMON) << "try stopping debugger"; if (debuggerStateIsOn(s_shuttingDown) || !m_debugger) return; setDebuggerStateOn(s_shuttingDown); qCDebug(DEBUGGERCOMMON) << "stopping debugger"; // Get debugger's attention if it's busy. We need debugger to be at the // command line so we can stop it. if (!m_debugger->isReady()) { qCDebug(DEBUGGERCOMMON) << "debugger busy on shutdown - interruping"; interruptDebugger(); } // If the app is attached then we release it here. This doesn't stop // the app running. if (debuggerStateIsOn(s_attached)) { addCommand(TargetDetach); emit debuggerUserCommandOutput("(gdb) detach\n"); } // Now try to stop debugger running. addCommand(GdbExit); emit debuggerUserCommandOutput("(gdb) quit"); // We cannot wait forever, kill gdb after 5 seconds if it's not yet quit QPointer guarded_this(this); QTimer::singleShot(5000, [guarded_this](){ if (guarded_this) { if (!guarded_this->debuggerStateIsOn(s_programExited) && guarded_this->debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "debugger not shutdown - killing"; guarded_this->m_debugger->kill(); guarded_this->setDebuggerState(s_dbgNotStarted | s_appNotStarted); guarded_this->raiseEvent(debugger_exited); } } }); emit reset(); } void MIDebugSession::interruptDebugger() { Q_ASSERT(m_debugger); // Explicitly send the interrupt in case something went wrong with the usual // ensureGdbListening logic. m_debugger->interrupt(); addCommand(ExecInterrupt, QString(), CmdInterrupt); } void MIDebugSession::run() { if (debuggerStateIsOn(s_appNotStarted|s_dbgNotStarted|s_shuttingDown)) return; addCommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning); } void MIDebugSession::runToCursor() { if (IDocument* doc = ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) runUntil(doc->url(), cursor.line() + 1); } } void MIDebugSession::jumpToCursor() { if (IDocument* doc = ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) jumpTo(doc->url(), cursor.line() + 1); } } void MIDebugSession::stepOver() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecNext, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepIntoInstruction() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecStepInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepInto() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecStep, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepOverInstruction() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecNextInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepOut() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecFinish, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::runUntil(const QUrl& url, int line) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!url.isValid()) { addCommand(ExecUntil, QString::number(line), CmdMaybeStartsRunning | CmdTemporaryRun); } else { addCommand(ExecUntil, QString("%1:%2").arg(url.toLocalFile()).arg(line), CmdMaybeStartsRunning | CmdTemporaryRun); } } void MIDebugSession::runUntil(const QString& address) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { addCommand(ExecUntil, QString("*%1").arg(address), CmdMaybeStartsRunning | CmdTemporaryRun); } } void MIDebugSession::jumpTo(const QUrl& url, int line) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (url.isValid()) { addCommand(NonMI, QString("tbreak %1:%2").arg(url.toLocalFile()).arg(line)); addCommand(NonMI, QString("jump %1:%2").arg(url.toLocalFile()).arg(line)); } } void MIDebugSession::jumpToMemoryAddress(const QString& address) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { addCommand(NonMI, QString("tbreak *%1").arg(address)); addCommand(NonMI, QString("jump *%1").arg(address)); } } void MIDebugSession::addUserCommand(const QString& cmd) { auto usercmd = createUserCommand(cmd); if (!usercmd) return; queueCmd(usercmd); // User command can theoreticall modify absolutely everything, // so need to force a reload. // We can do it right now, and don't wait for user command to finish // since commands used to reload all view will be executed after // user command anyway. if (!debuggerStateIsOn(s_appNotStarted) && !debuggerStateIsOn(s_programExited)) raiseEvent(program_state_changed); } MICommand *MIDebugSession::createUserCommand(const QString &cmd) const { MICommand *res = nullptr; if (!cmd.isEmpty() && cmd[0].isDigit()) { // Add a space to the begining, so debugger won't get confused if the // command starts with a number (won't mix it up with command token added) res = new UserCommand(MI::NonMI, " " + cmd); } else { res = new UserCommand(MI::NonMI, cmd); } return res; } MICommand *MIDebugSession::createCommand(CommandType type, const QString& arguments, CommandFlags flags) const { return new MICommand(type, arguments, flags); } void MIDebugSession::addCommand(MICommand* cmd) { queueCmd(cmd); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, MI::CommandFlags flags) { queueCmd(createCommand(type, arguments, flags)); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, MI::MICommandHandler *handler, MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(handler); queueCmd(cmd); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, const MI::FunctionCommandHandler::Function& callback, MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(callback); queueCmd(cmd); } // Fairly obvious that we'll add whatever command you give me to a queue // Not quite so obvious though is that if we are going to run again. then any // information requests become redundent and must be removed. // We also try and run whatever command happens to be at the head of // the queue. void MIDebugSession::queueCmd(MICommand *cmd) { if (debuggerStateIsOn(s_dbgNotStarted)) { KMessageBox::information( qApp->activeWindow(), i18n("Gdb command sent when debugger is not running
" "The command was:
%1", cmd->initialString()), i18n("Internal error")); return; } if (m_stateReloadInProgress) cmd->setStateReloading(true); m_commandQueue->enqueue(cmd); qCDebug(DEBUGGERCOMMON) << "QUEUE: " << cmd->initialString() << (m_stateReloadInProgress ? "(state reloading)" : "") << m_commandQueue->count() << "pending"; bool varCommandWithContext= (cmd->type() >= MI::VarAssign && cmd->type() <= MI::VarUpdate && cmd->type() != MI::VarDelete); bool stackCommandWithContext = (cmd->type() >= MI::StackInfoDepth && cmd->type() <= MI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { if (cmd->thread() == -1) qCDebug(DEBUGGERCOMMON) << "\t--thread will be added on execution"; if (cmd->frame() == -1) qCDebug(DEBUGGERCOMMON) << "\t--frame will be added on execution"; } setDebuggerStateOn(s_dbgBusy); raiseEvent(debugger_busy); executeCmd(); } void MIDebugSession::executeCmd() { Q_ASSERT(m_debugger); if (debuggerStateIsOn(s_dbgNotListening) && m_commandQueue->haveImmediateCommand()) { // We may have to call this even while a command is currently executing, because // debugger can get into a state where a command such as ExecRun does not send a response // while the inferior is running. ensureDebuggerListening(); } if (!m_debugger->isReady()) return; MICommand* currentCmd = m_commandQueue->nextCommand(); if (!currentCmd) return; if (currentCmd->flags() & (CmdMaybeStartsRunning | CmdInterrupt)) { setDebuggerStateOff(s_automaticContinue); } if (currentCmd->flags() & CmdMaybeStartsRunning) { // GDB can be in a state where it is listening for commands while the program is running. // However, when we send a command such as ExecContinue in this state, GDB may return to // the non-listening state without acknowledging that the ExecContinue command has even // finished, let alone sending a new notification about the program's running state. // So let's be extra cautious about ensuring that we will wake GDB up again if required. setDebuggerStateOn(s_dbgNotListening); } bool varCommandWithContext= (currentCmd->type() >= MI::VarAssign && currentCmd->type() <= MI::VarUpdate && currentCmd->type() != MI::VarDelete); bool stackCommandWithContext = (currentCmd->type() >= MI::StackInfoDepth && currentCmd->type() <= MI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { // Most var commands should be executed in the context // of the selected thread and frame. if (currentCmd->thread() == -1) currentCmd->setThread(frameStackModel()->currentThread()); if (currentCmd->frame() == -1) currentCmd->setFrame(frameStackModel()->currentFrame()); } QString commandText = currentCmd->cmdToSend(); bool bad_command = false; QString message; int length = commandText.length(); // No i18n for message since it's mainly for debugging. if (length == 0) { // The command might decide it's no longer necessary to send // it. if (SentinelCommand* sc = dynamic_cast(currentCmd)) { qCDebug(DEBUGGERCOMMON) << "SEND: sentinel command, not sending"; sc->invokeHandler(); } else { qCDebug(DEBUGGERCOMMON) << "SEND: command " << currentCmd->initialString() << "changed its mind, not sending"; } delete currentCmd; executeCmd(); return; } else { if (commandText[length-1] != '\n') { bad_command = true; message = "Debugger command does not end with newline"; } } if (bad_command) { KMessageBox::information(qApp->activeWindow(), i18n("Invalid debugger command
%1", message), i18n("Invalid debugger command")); executeCmd(); return; } m_debugger->execute(currentCmd); } void MIDebugSession::ensureDebuggerListening() { Q_ASSERT(m_debugger); // Note: we don't use interruptDebugger() here since // we don't want to queue more commands before queuing a command m_debugger->interrupt(); setDebuggerStateOn(s_interruptSent); if (debuggerStateIsOn(s_appRunning)) setDebuggerStateOn(s_automaticContinue); setDebuggerStateOff(s_dbgNotListening); } void MIDebugSession::destroyCmds() { m_commandQueue->clear(); } // FIXME: I don't fully remember what is the business with // m_stateReloadInProgress and whether we can lift it to the // generic level. void MIDebugSession::raiseEvent(event_t e) { if (e == program_exited || e == debugger_exited) { m_stateReloadInProgress = false; } if (e == program_state_changed) { m_stateReloadInProgress = true; qCDebug(DEBUGGERCOMMON) << "State reload in progress\n"; } IDebugSession::raiseEvent(e); if (e == program_state_changed) { m_stateReloadInProgress = false; } } bool KDevMI::MIDebugSession::hasCrashed() const { return m_hasCrashed; } void MIDebugSession::slotDebuggerReady() { Q_ASSERT(m_debugger); m_stateReloadInProgress = false; executeCmd(); if (m_debugger->isReady()) { /* There is nothing in the command queue and no command is currently executing. */ if (debuggerStateIsOn(s_automaticContinue)) { if (!debuggerStateIsOn(s_appRunning)) { qCDebug(DEBUGGERCOMMON) << "Posting automatic continue"; addCommand(ExecContinue, QString(), CmdMaybeStartsRunning); } setDebuggerStateOff(s_automaticContinue); return; } if (m_stateReloadNeeded && !debuggerStateIsOn(s_appRunning)) { qCDebug(DEBUGGERCOMMON) << "Finishing program stop"; // Set to false right now, so that if 'actOnProgramPauseMI_part2' // sends some commands, we won't call it again when handling replies // from that commands. m_stateReloadNeeded = false; reloadProgramState(); } qCDebug(DEBUGGERCOMMON) << "No more commands"; setDebuggerStateOff(s_dbgBusy); raiseEvent(debugger_ready); } } void MIDebugSession::slotDebuggerExited(bool abnormal, const QString &msg) { /* Technically speaking, GDB is likely not to kill the application, and we should have some backup mechanism to make sure the application is killed by KDevelop. But even if application stays around, we no longer can control it in any way, so mark it as exited. */ setDebuggerStateOn(s_appNotStarted); setDebuggerStateOn(s_dbgNotStarted); setDebuggerStateOn(s_programExited); setDebuggerStateOff(s_shuttingDown); if (!msg.isEmpty()) emit showMessage(msg, 3000); if (abnormal) { /* The error is reported to user in MIDebugger now. KMessageBox::information( KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Debugger exited abnormally" "

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

Debugger reported the following error:" "

%1", result["msg"].literal()), i18n("Debugger error")); // Error most likely means that some change made in GUI // was not communicated to the gdb, so GUI is now not // in sync with gdb. Resync it. // // Another approach is to make each widget reload it content // on errors from commands that it sent, but that's too complex. // Errors are supposed to happen rarely, so full reload on error // is not a big deal. Well, maybe except for memory view, but // it's no auto-reloaded anyway. // // Also, don't reload state on errors appeared during state // reloading! if (!m_debugger->currentCommand()->stateReloading()) raiseEvent(program_state_changed); } void MIDebugSession::setSourceInitFile(bool enable) { m_sourceInitFile = enable; } diff --git a/debuggers/common/midebugsession.h b/debuggers/common/midebugsession.h index bf17c0c7af..d339a7818c 100644 --- a/debuggers/common/midebugsession.h +++ b/debuggers/common/midebugsession.h @@ -1,377 +1,377 @@ /* * Common code for debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef MIDEBUGSESSION_H #define MIDEBUGSESSION_H #include #include "dbgglobal.h" #include "mibreakpointcontroller.h" #include "mi/mi.h" #include "mi/micommand.h" #include #include class IExecutePlugin; namespace KDevelop { class ILaunchConfiguration; class ProcessLineMaker; } namespace KDevMI { namespace MI { class CommandQueue; } class MIDebugger; class MIDebuggerPlugin; class MIVariable; class STTY; class MIDebugSession : public KDevelop::IDebugSession { Q_OBJECT public: explicit MIDebugSession(MIDebuggerPlugin *plugin = nullptr); ~MIDebugSession() override; Q_SIGNALS: /** * Emits when received standard output lines from inferior */ void inferiorStdoutLines(const QStringList &lines) const; /** * Emits when received standard error lines from inferior */ void inferiorStderrLines(const QStringList &lines) const; void inferiorStopped(const MI::AsyncRecord &r) const; void inferiorRunning() const; /** * Emits when received standard output from debugger for user commands */ void debuggerUserCommandOutput(const QString &output) const; /** * Emits when received standard output from debugger for internal commands */ void debuggerInternalCommandOutput(const QString &output) const; /** * Emits when received internal output from debugger */ void debuggerInternalOutput(const QString &output) const; /** * Emits when received standard output from inferior's tty */ void inferiorTtyStdout(const QByteArray &output) const; /** * Emits when received standard output from inferior's tty */ void inferiorTtyStderr(const QByteArray &output) const; /** * Emits when the debugger instance state changes */ void debuggerStateChanged(DBGStateFlags oldState, DBGStateFlags newState) const; /** * Emits when there's message needed to be show to user. */ void showMessage(const QString& message, int timeout) const; /** * Emits when the debugger console view need to be raised. */ void raiseDebuggerConsoleViews() const; /** * Emits when need to reset */ void reset() const; public: bool debuggerStateIsOn(DBGStateFlags state) const; DBGStateFlags debuggerState() const; bool hasCrashed() const; // BEGIN IDebugSession overrides public: DebuggerState state() const override; bool restartAvaliable() const override; MIBreakpointController * breakpointController() const override = 0; public Q_SLOTS: void restartDebugger() override; void stopDebugger() override; void interruptDebugger() override; void run() override; void runToCursor() override; void jumpToCursor() override; void stepOver() override; void stepIntoInstruction() override; void stepInto() override; void stepOverInstruction() override; void stepOut() override; // END IDebugSession overrides public Q_SLOTS: /** * Run currently executing program to the given \a url and \a line. */ void runUntil(const QUrl& url, int line); /** * Run currently executing program to the given \a address */ void runUntil(const QString& address); /** * Move the execution point of the currently executing program to the given \a url and \a line. */ void jumpTo(const QUrl& url, int line); /** * Move the execution point of the currently executing program to the given \a address. *Note: It can be really very dangerous, so use jumpTo instead. */ void jumpToMemoryAddress(const QString& address); /** * Start the debugger, and execute the inferior program specified by \a cfg. */ bool startDebugging(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec); /** * Start the debugger, and examine the core file given by \a coreFile. */ bool examineCoreFile(const QUrl &debugee, const QUrl &coreFile); /** * Start the debugger, and attach to a currently running process with the given \a pid. */ bool attachToProcess(int pid); public: virtual MI::MICommand *createCommand(MI::CommandType type, const QString& arguments, - MI::CommandFlags flags = 0) const; + MI::CommandFlags flags = nullptr) const; virtual MI::MICommand *createUserCommand(const QString &cmd) const; /** Adds a command to the end of queue of commands to be executed by debugger. The command will be actually sent to debugger only when replies from all previous commands are received and full processed. The literal command sent to debugger is obtained by calling cmd->cmdToSend. The call is made immediately before sending the command, so it's possible to use results of prior commands when computing the exact command to send. */ void addUserCommand(const QString &cmd); void addCommand(MI::MICommand* cmd); /** Same as above, but internally constructs MICommand using createCommand() */ void addCommand(MI::CommandType type, const QString& arguments = QString(), - MI::CommandFlags flags = 0); + MI::CommandFlags flags = nullptr); void addCommand(MI::CommandType type, const QString& arguments, MI::MICommandHandler* handler, - MI::CommandFlags flags = 0); + MI::CommandFlags flags = nullptr); void addCommand(MI::CommandType type, const QString& arguments, const MI::FunctionCommandHandler::Function& callback, - MI::CommandFlags flags = 0); + MI::CommandFlags flags = nullptr); template void addCommand(MI::CommandType type, const QString& arguments, Handler* handler_this, void (Handler::* handler_method)(const MI::ResultRecord&), - MI::CommandFlags flags = 0); + MI::CommandFlags flags = nullptr); QMap & variableMapping(); MIVariable* findVariableByVarobjName(const QString &varobjName) const; void markAllVariableDead(); protected Q_SLOTS: virtual void slotDebuggerReady(); virtual void slotDebuggerExited(bool abnormal, const QString &msg); virtual void slotInferiorStopped(const MI::AsyncRecord &r); /** * Triggered every time program begins/continues it's execution. */ virtual void slotInferiorRunning(); /** * Handle MI async notifications. */ virtual void processNotification(const MI::AsyncRecord &n); /** Default handler for errors. Tries to guess is the error message is telling that target is gone, if so, informs the user. Otherwise, shows a dialog box and reloads view state. */ virtual void defaultErrorHandler(const MI::ResultRecord &result); /** * Update session state when debugger state changes, and show messages */ virtual void handleDebuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState); void handleNoInferior(const QString &msg); void handleInferiorFinished(const QString &msg); protected: void queueCmd(MI::MICommand *cmd); /** Try to execute next command in the queue. If GDB is not busy with previous command, and there's a command in the queue, sends it. */ void executeCmd(); void destroyCmds(); virtual void ensureDebuggerListening(); /** * Start the debugger instance */ bool startDebugger(KDevelop::ILaunchConfiguration *cfg); /** * MIDebugSession takes the ownership of the created instance. */ virtual MIDebugger *createDebugger() const = 0; /** * Initialize debugger and set default configurations. */ virtual void initializeDebugger() = 0; /** * Do per launch configuration. */ virtual void configInferior(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec, const QString &executable) = 0; /** * Start the inferior program (either local or remote). */ virtual bool execInferior(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec, const QString &executable) = 0; /** * Further config the debugger and load the core dump */ virtual bool loadCoreFile(KDevelop::ILaunchConfiguration *cfg, const QString &debugee, const QString &corefile) = 0; /** * Manipulate debugger instance state */ void setDebuggerStateOn(DBGStateFlags stateOn); void setDebuggerStateOff(DBGStateFlags stateOff); void setDebuggerState(DBGStateFlags newState); void debuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState); /** * Manipulate the session state */ void setSessionState(DebuggerState state); void raiseEvent(event_t e) override; /** Called when there are no pending commands and 'm_stateReloadNeeded' is true. Also can be used to immediately reload program state. Issues commands to completely reload all program state shown to the user. */ void reloadProgramState(); void programNoApp(const QString &msg); void programFinished(const QString &msg); // FIXME: Whether let the debugger source init files when starting, // only used in unit test currently, potentially could be made a user // configurable option void setSourceInitFile(bool enable); private Q_SLOTS: void handleTargetAttach(const MI::ResultRecord& r); // Pops up a dialog box with some hopefully // detailed information about which state debugger // is in, which commands were sent and so on. void explainDebuggerStatus(); protected: KDevelop::ProcessLineMaker *m_procLineMaker; std::unique_ptr m_commandQueue; // Though the misleading class name, this is the session level state. // see m_debuggerState for debugger instance state DebuggerState m_sessionState; MIDebugger *m_debugger; DBGStateFlags m_debuggerState; bool m_stateReloadInProgress; bool m_stateReloadNeeded; std::unique_ptr m_tty; bool m_hasCrashed; bool m_sourceInitFile; // Map from GDB varobj name to MIVariable. QMap m_allVariables; MIDebuggerPlugin *m_plugin; }; template void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, Handler* handler_this, void (Handler::* handler_method)(const MI::ResultRecord&), MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(handler_this, handler_method); queueCmd(cmd); } } // end of namespace KDevMI #endif // MIDEBUGSESSION_H diff --git a/debuggers/common/registers/modelsmanager.cpp b/debuggers/common/registers/modelsmanager.cpp index e4dce13c7a..4e795d007d 100644 --- a/debuggers/common/registers/modelsmanager.cpp +++ b/debuggers/common/registers/modelsmanager.cpp @@ -1,355 +1,355 @@ /* * Class to manage register models. * Copyright (C) 2013 Vlas Puhov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "modelsmanager.h" #include #include #include #include namespace KDevMI { struct Model { Model(); Model(const QString& name, QSharedPointer model, QAbstractItemView* view); bool operator==(const Model& m) const; QString name; QSharedPointer model; QAbstractItemView* view = nullptr; }; class Models { public: QStandardItemModel* addModel(const Model& m); void clear(); bool contains(const QString& name) const; bool contains(QAbstractItemView* view) const; bool contains(QStandardItemModel* model) const; QString nameForView(QAbstractItemView* view) const; ///Returns registered model for @p name, 0 if not registered. QStandardItemModel* modelForName(const QString& name) const; ///Returns registered model for @p view, 0 if not registered. QStandardItemModel* modelForView(QAbstractItemView* view) const; private: ///All models QVector m_models; }; } // end of namespace KDevMI using namespace KDevMI; -ModelsManager::ModelsManager(QObject* parent) : QObject(parent), m_models(new Models), m_controller(0), m_config(KSharedConfig::openConfig()->group("Register models")) {} +ModelsManager::ModelsManager(QObject* parent) : QObject(parent), m_models(new Models), m_controller(nullptr), m_config(KSharedConfig::openConfig()->group("Register models")) {} ModelsManager::~ModelsManager() {} QString ModelsManager::addView(QAbstractItemView* view) { if (m_models->contains(view)) { return m_models->nameForView(view); } Q_ASSERT(m_controller); QString name; foreach (const GroupsName & group, m_controller->namesOfRegisterGroups()) { if (!m_models->contains(group.name())) { QStandardItemModel* m = m_models->addModel(Model(group.name(), QSharedPointer(new QStandardItemModel()), view)); view->setModel(m); if (group.type() == flag) { connect(view, &QAbstractItemView::doubleClicked, this, &ModelsManager::flagChanged, Qt::UniqueConnection); } name = group.name(); load(group); break; } } return name; } void ModelsManager::updateModelForGroup(const RegistersGroup& group) { QStandardItemModel* model = m_models->modelForName(group.groupName.name()); if (!model) { return; } disconnect(model, &QStandardItemModel::itemChanged, this, &ModelsManager::itemChanged); model->setRowCount(group.registers.count()); model->setColumnCount(group.registers.first().value.split(' ').size() + 1); //set names and values separately as names don't change so often. if (!model->item(0, 0)) { for (int row = 0; row < group.registers.count(); row++) { const Register& r = group.registers[row]; QStandardItem* n = new QStandardItem(r.name); n->setFlags(Qt::ItemIsEnabled); model->setItem(row, 0, n); } } for (int row = 0; row < group.registers.count(); row++) { const Register& r = group.registers[row]; const QStringList& values = r.value.split(' '); //binary format workaround. Format currentFormat = formats(group.groupName.name()).first(); Mode currentMode = modes(group.groupName.name()).first(); QString prefix; if (currentFormat == Binary && ((currentMode < v4_float || currentMode > v2_double) && (currentMode < f32 || currentMode > f64) && group.groupName.type() != floatPoint)) { prefix = "0b"; } for (int column = 0; column < values.count(); column ++) { QStandardItem* v = new QStandardItem(prefix + values[column]); if (group.groupName.type() == flag) { v->setFlags(Qt::ItemIsEnabled); } model->setItem(row, column + 1, v); } } connect(model, &QStandardItemModel::itemChanged, this, &ModelsManager::itemChanged); } void ModelsManager::flagChanged(const QModelIndex& idx) { QAbstractItemView* view = static_cast(sender()); int row = idx.row(); QStandardItemModel* model = m_models->modelForView(view); QStandardItem* item = model->item(row, 0); Register r; r.name = item->text(); r.value = model->data(idx).toString(); emit registerChanged(r); } QStandardItemModel* Models::addModel(const Model& m) { if (!contains(m.name) && !contains(m.view) && !contains(m.model.data())) { m_models.append(m); return m.model.data(); } - return 0; + return nullptr; } bool Models::contains(const QString& name) const { foreach (const Model & m, m_models) { if (m.name == name) { return true; } } return false; } bool Models::contains(QAbstractItemView* view) const { foreach (const Model & m, m_models) { if (m.view == view) { return true; } } return false; } bool Models::contains(QStandardItemModel* model) const { foreach (const Model & m, m_models) { if (m.model.data() == model) { return true; } } return false; } QStandardItemModel* Models::modelForName(const QString& name) const { foreach (const Model & m, m_models) { if (m.name == name) { return m.model.data(); } } - return 0; + return nullptr; } QStandardItemModel* Models::modelForView(QAbstractItemView* view) const { foreach (const Model & m, m_models) { if (m.view == view) { return m.model.data(); } } - return 0; + return nullptr; } void ModelsManager::itemChanged(QStandardItem* i) { QStandardItemModel* model = static_cast(sender()); int row = i->row(); Register r; r.name = model->item(row, 0)->text(); for (int i = 1; i < model->columnCount(); i++) { r.value += model->item(row, i)->text() + ' '; } r.value = r.value.trimmed(); emit registerChanged(r); } QString Models::nameForView(QAbstractItemView* view) const { foreach (const Model & m, m_models) { if (m.view == view) { return m.name; } } return QString(); } void ModelsManager::setController(IRegisterController* rc) { m_controller = rc; if (!m_controller) { m_models->clear(); } else { connect(this, &ModelsManager::registerChanged, m_controller, &IRegisterController::setRegisterValue); connect(m_controller, &IRegisterController::registersChanged, this, &ModelsManager::updateModelForGroup); } } Model::Model() {} Model::Model(const QString& name, QSharedPointer model, QAbstractItemView* view) : name(name), model(model), view(view) {} bool Model::operator==(const Model& m) const { return m.model == model && m.view == view && m.name == name; } void ModelsManager::updateRegisters(const QString& group) { Q_ASSERT(m_controller); if (group.isEmpty()) { m_controller->updateRegisters(GroupsName()); } else { foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { m_controller->updateRegisters(g); break; } } } } void Models::clear() { m_models.clear(); } void ModelsManager::setFormat(const QString& group, Format format) { foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { m_controller->setFormat(format, g); save(g); break; } } } QVector ModelsManager::formats(const QString& group) const { QVector formats; formats << Raw; foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { formats = m_controller->formats(g); break; } } return formats; } void ModelsManager::save(const GroupsName& g) { KConfigGroup group = m_config.group(g.name()); group.writeEntry("format", static_cast(m_controller->formats(g).first())); group.writeEntry("mode", static_cast(m_controller->modes(g).first())); } void ModelsManager::load(const GroupsName& g) { KConfigGroup group = m_config.group(g.name()); Format format = static_cast(group.readEntry("format", static_cast(m_controller->formats(g).first()))); setFormat(g.name(), format); Mode mode = static_cast(group.readEntry("mode", static_cast(m_controller->modes(g).first()))); setMode(g.name(), mode); } QVector< Mode > ModelsManager::modes(const QString& group) const { QVector modes; foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { modes = m_controller->modes(g); break; } } return modes; } void ModelsManager::setMode(const QString& group, Mode mode) { foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { m_controller->setMode(mode, g); save(g); break; } } } diff --git a/debuggers/common/registers/modelsmanager.h b/debuggers/common/registers/modelsmanager.h index c49b8f530d..ad2315f198 100644 --- a/debuggers/common/registers/modelsmanager.h +++ b/debuggers/common/registers/modelsmanager.h @@ -1,101 +1,101 @@ /* * Class to manage register models. * Copyright (C) 2013 Vlas Puhov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef MODELSMANAGER_H #define MODELSMANAGER_H #include #include #include #include #include #include "registercontroller.h" class QAbstractItemView; class QStandardItemModel; class QStandardItem; class QModelIndex; namespace KDevMI { class Models; class IRegisterController; struct Register; struct RegistersGroup; class GroupsName; class ModelsManager : public QObject { Q_OBJECT public: - explicit ModelsManager(QObject* parent = 0); + explicit ModelsManager(QObject* parent = nullptr); ~ModelsManager() override; ///Adds new @p view with @p name, if not yet registered. ///All views removed after debug session ended. ///@return: Name of the new view. QString addView(QAbstractItemView* view); void setController(IRegisterController* rc); ///Sets @p format for the @p group, if format is valid. Does nothing otherwise. void setFormat(const QString& group, Format format); ///Returns all supported formats for @p group. The first one is current. QVector formats(const QString& group) const; ///Sets @p mode for the @p group, if mode is valid. Does nothing otherwise. void setMode(const QString& group, Mode mode); ///Returns all supported modes for @p group. The first one is current. QVector modes(const QString& group) const; Q_SIGNALS: ///Emitted when a register in a model changed. Updated value should be send to the debugger. void registerChanged(const Register&); public Q_SLOTS: void updateModelForGroup(const RegistersGroup& group); ///Forcedly updates registers in @p group. void updateRegisters(const QString& group = QString()); private: void save(const GroupsName&); void load(const GroupsName&); private Q_SLOTS: void flagChanged(const QModelIndex&); void itemChanged(QStandardItem*); private: QScopedPointer m_models; IRegisterController* m_controller; KConfigGroup m_config; }; } // end of namespace KDevMI #endif // MODELSMANAGER_H diff --git a/debuggers/common/registers/registercontroller.cpp b/debuggers/common/registers/registercontroller.cpp index fcb8fd901c..ae26b94ab5 100644 --- a/debuggers/common/registers/registercontroller.cpp +++ b/debuggers/common/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 = "t "; break; case Octal: registers = "o "; break; case Decimal : registers = "d "; break; case Hexadecimal: registers = "x "; break; case Raw: registers = "r "; break; case Unsigned: registers = "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 = "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("-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["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["register-values"]; for (int i = 0; i < values.size(); ++i) { const Value& entry = values[i]; int number = entry["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["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, "", 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(0, 16); + 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, QString("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 = QString("set var $%1=%2").arg(reg.name).arg(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(0, 16); + 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["register-values"]; Q_ASSERT(!m_rawRegisterNames.isEmpty()); for (int i = 0; i < values.size(); ++i) { const Value& entry = values[i]; int number = entry["number"].literal().toInt(); registerName = m_rawRegisterNames[number]; if (currentMode == LAST_MODE) { group = groupForRegisterName(registerName); currentMode = modes(group).first(); } QString record = entry["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/debuggers/common/registers/registercontroller.h b/debuggers/common/registers/registercontroller.h index 86ff7dae2b..69839fcbb9 100644 --- a/debuggers/common/registers/registercontroller.h +++ b/debuggers/common/registers/registercontroller.h @@ -1,247 +1,247 @@ /* * 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. * */ #ifndef _REGISTERCONTROLLER_H_ #define _REGISTERCONTROLLER_H_ #include #include #include #include #include namespace KDevMI { namespace MI { struct ResultRecord; } class MIDebugSession; enum RegisterType {general, structured, flag, floatPoint}; class GroupsName { public: QString name() const { return _name;} int index() const {return _index;} RegisterType type() const{return _type; } QString flagName() const{return _flagName;} bool operator==(const GroupsName& g) const {return _name == g.name();} GroupsName(): _index(-1), _type(general) {} private: GroupsName(const QString& name, int idx, RegisterType type = general, const QString flag = QString()): _name(name), _index(idx), _type(type), _flagName(flag) {} private: QString _name; int _index; ///Should be unique for each group for current architecture (0, 1...n). RegisterType _type; QString _flagName; ///Used only for flag registers. friend class IRegisterController; friend struct RegistersGroup; }; enum Format { Binary, Octal, Decimal, Hexadecimal, Raw, Unsigned, LAST_FORMAT }; enum Mode { natural, v4_float, v2_double, v4_int32, v2_int64, u32, u64, f32, f64, LAST_MODE }; struct FormatsModes { QVector formats; QVector modes; }; ///Register in format: @p name, @p value - space separated list of values struct Register { Register() {} Register(const QString& _name, const QString& _value): name(_name), value(_value) {} QString name; QString value; }; ///List of @p registers for @p groupName in @p format struct RegistersGroup { RegistersGroup() : format(Binary) , flag(false) {} GroupsName groupName; QVector registers; Format format; /// namesOfRegisterGroups() const = 0; ///Returns all supported formats for @p group (bin, dec, hex ...) QVector formats(const GroupsName& group); ///Sets current format for the @p group, if format is supported. Does nothing otherwise. void setFormat(Format f, const GroupsName& group); ///Returns all supported modes for @p group (i.e. how to display group: 2 int, 4 float or other number of columns) QVector modes(const GroupsName& group); ///Sets current mode for the @p group, if mode is supported. Does nothing otherwise. void setMode(Mode m, const GroupsName& group); signals: ///Emits @p group with updated registers. void registersChanged(const RegistersGroup& g); public slots: ///Updates registers in @p group. If @p group is empty - updates all registers. virtual void updateRegisters(const GroupsName& group = GroupsName()); ///Sends updated register's @p reg value to the debugger. virtual void setRegisterValue(const Register& reg); protected: - IRegisterController(MIDebugSession* debugSession = 0, QObject* parent = 0); + IRegisterController(MIDebugSession* debugSession = nullptr, QObject* parent = nullptr); ///Returns registers from the @p group, or empty registers group if @p group is invalid. virtual RegistersGroup registersFromGroup(const GroupsName& group) const = 0; ///Sets value for @p register from @p group. virtual void setRegisterValueForGroup(const GroupsName& group, const Register& reg) = 0; ///Returns names of all registers for @p group. virtual QStringList registerNamesForGroup(const GroupsName& group) const = 0; /**Updates value for each register in the group. * @param [out] registers Registers which values should be updated. */ virtual void updateValuesForRegisters(RegistersGroup* registers) const; ///Returns value for the given @p name, empty string if the name is incorrect or there is no registers yet. QString registerValue(const QString& name) const; /** Sets a flag register. * @param reg register to set * @param flag flag register @p reg belongs to. */ void setFlagRegister(const Register& reg, const FlagRegister& flag); ///Sets new value for register @p reg, from group @p group. void setGeneralRegister(const Register& reg, const GroupsName& group); ///Sets new value for structured register(XMM, VFP quad and other) @p reg, from group @p group. void setStructuredRegister(const Register& reg, const GroupsName& group); ///Updates values in @p flagsGroup for @p flagRegister. void updateFlagValues(RegistersGroup* flagsGroup, const FlagRegister& flagRegister) const; ///Returns group that given register belongs to. GroupsName groupForRegisterName(const QString& name) const; ///Initializes registers, that is gets names of all available registers. Returns true is succeed. bool initializeRegisters(); GroupsName createGroupName(const QString& name, int idx, RegisterType t = general, const QString flag = QString()) const; ///Returns register's number for @p name. QString numberForName(const QString& name) const; public: ~IRegisterController() override; private : ///Handles initialization of register's names. void registerNamesHandler(const MI::ResultRecord& r); ///Parses new values for general registers from @p r and updates it in m_registers. ///Emits registersChanged signal. void generalRegistersHandler(const MI::ResultRecord& r); ///Parses new values for structured registers from @p r and updates it in m_registers. ///Emits registersChanged signal. virtual void structuredRegistersHandler(const MI::ResultRecord& r); private: ///Groups that should be updated(emitted @p registersInGroupChanged signal), if empty - all. QVector m_pendingGroups; protected: ///Register names as it sees debugger (in format: number, name). QVector m_rawRegisterNames; ///Registers in format: name, value QHash m_registers; ///Supported formats and modes for each register's group. First format/mode is current. QVector m_formatsModes; ///Current debug session; MIDebugSession* m_debugSession; }; } // end of namespace KDevMI Q_DECLARE_TYPEINFO(KDevMI::Register, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevMI::RegistersGroup, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevMI::FlagRegister, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevMI::GroupsName, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevMI::FormatsModes, Q_MOVABLE_TYPE); #endif diff --git a/debuggers/common/registers/registersmanager.cpp b/debuggers/common/registers/registersmanager.cpp index fca791996f..3ea5c86348 100644 --- a/debuggers/common/registers/registersmanager.cpp +++ b/debuggers/common/registers/registersmanager.cpp @@ -1,171 +1,171 @@ /* * Creates RegistersView and RegisterController based on current architecture. * 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 "registersmanager.h" #include "registercontroller_arm.h" #include "registercontroller_x86.h" #include "registersview.h" #include "dbgglobal.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include "modelsmanager.h" using namespace KDevMI::MI; using namespace KDevMI; void ArchitectureParser::parseArchitecture() { Architecture arch = other; foreach (const QString & reg, m_registerNames) { if (reg == "rax") { arch = x86_64; break; } else if (reg == "r0") { arch = arm; break; } else if (reg == "eax") { arch = x86; //we don't break because x86_64 contains eax too. } } emit architectureParsed(arch); } void ArchitectureParser::registerNamesHandler(const ResultRecord& r) { const Value& names = r["register-names"]; m_registerNames.clear(); for (int i = 0; i < names.size(); ++i) { const Value& entry = names[i]; if (!entry.literal().isEmpty()) { m_registerNames << entry.literal(); } } parseArchitecture(); } void ArchitectureParser::determineArchitecture(MIDebugSession* debugSession) { if (!debugSession || debugSession->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } debugSession->addCommand(DataListRegisterNames, "", this, &ArchitectureParser::registerNamesHandler); } RegistersManager::RegistersManager(QWidget* parent) -: QObject(parent), m_registersView(new RegistersView(parent)), m_registerController(0), m_architectureParser(new ArchitectureParser(this)), m_debugSession(0), m_modelsManager(new ModelsManager(this)), m_currentArchitecture(undefined), m_needToCheckArch(false) +: QObject(parent), m_registersView(new RegistersView(parent)), m_registerController(nullptr), m_architectureParser(new ArchitectureParser(this)), m_debugSession(nullptr), m_modelsManager(new ModelsManager(this)), m_currentArchitecture(undefined), m_needToCheckArch(false) { connect(m_architectureParser, &ArchitectureParser::architectureParsed, this, &RegistersManager::architectureParsedSlot); m_registersView->setModel(m_modelsManager); - setController(0); + setController(nullptr); } void RegistersManager::architectureParsedSlot(Architecture arch) { qCDebug(DEBUGGERCOMMON) << " Current controller: " << m_registerController << "Current arch " << m_currentArchitecture; if (m_registerController || m_currentArchitecture != undefined) { return; } switch (arch) { case x86: m_registerController.reset(new RegisterController_x86(m_debugSession)) ; qCDebug(DEBUGGERCOMMON) << "Found x86 architecture"; break; case x86_64: m_registerController.reset(new RegisterController_x86_64(m_debugSession)); qCDebug(DEBUGGERCOMMON) << "Found x86_64 architecture"; break; case arm: m_registerController.reset(new RegisterController_Arm(m_debugSession)); qCDebug(DEBUGGERCOMMON) << "Found Arm architecture"; break; default: m_registerController.reset(); qWarning() << "Unsupported architecture. Registers won't be available."; break; } m_currentArchitecture = arch; setController(m_registerController.data()); if (m_registerController) { updateRegisters(); } } void RegistersManager::setSession(MIDebugSession* debugSession) { qCDebug(DEBUGGERCOMMON) << "Change session " << debugSession; m_debugSession = debugSession; if (m_registerController) { m_registerController->setSession(debugSession); } if (!m_debugSession) { qCDebug(DEBUGGERCOMMON) << "Will reparse arch"; m_needToCheckArch = true; - setController(0); + setController(nullptr); } } void RegistersManager::updateRegisters() { if (!m_debugSession || m_debugSession->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } qCDebug(DEBUGGERCOMMON) << "Updating registers"; if (m_needToCheckArch) { m_needToCheckArch = false; m_currentArchitecture = undefined; - setController(0); + setController(nullptr); } if (m_currentArchitecture == undefined) { m_architectureParser->determineArchitecture(m_debugSession); } if (m_registerController) { m_registersView->updateRegisters(); } else { qCDebug(DEBUGGERCOMMON) << "No registerController, yet?"; } } ArchitectureParser::ArchitectureParser(QObject* parent) : QObject(parent) {} void RegistersManager::setController(IRegisterController* c) { m_registerController.reset(c); m_modelsManager->setController(c); m_registersView->enable(c ? true : false); } diff --git a/debuggers/common/registers/registersview.cpp b/debuggers/common/registers/registersview.cpp index d16de1c7a4..48d8f90097 100644 --- a/debuggers/common/registers/registersview.cpp +++ b/debuggers/common/registers/registersview.cpp @@ -1,248 +1,248 @@ /* * Displays registers. * 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 "registersview.h" #include "converters.h" #include "debuglog.h" #include "modelsmanager.h" #include #include #include #include using namespace KDevMI; namespace { const int TABLES_COUNT = 5; } RegistersView::RegistersView(QWidget* p) - : QWidget(p), m_menu(new QMenu(this)), m_mapper(new QSignalMapper(this)), m_modelsManager(0) + : QWidget(p), m_menu(new QMenu(this)), m_mapper(new QSignalMapper(this)), m_modelsManager(nullptr) { setupUi(this); setupActions(); connect(m_mapper, static_cast(&QSignalMapper::mapped), this, &RegistersView::menuTriggered); connect(tabWidget, &QTabWidget::currentChanged, this, &RegistersView::updateRegisters); } void RegistersView::contextMenuEvent(QContextMenuEvent* e) { //Don't remove update action. const QList actions = m_menu->actions(); for (int i = 1; i < actions.count(); i++) { m_menu->removeAction(actions[i]); } QString group = activeViews().first(); foreach (QAction * act, m_actions) { act->setChecked(false); } const QVector formats = m_modelsManager->formats(group); if (formats.size() > 1) { QMenu* m = m_menu->addMenu(i18n("Format")); foreach (Format fmt, formats) { m->addAction(findAction(Converters::formatToString(fmt))); } findAction(Converters::formatToString(formats.first()))->setChecked(true); } const QVector modes = m_modelsManager->modes(group); if (modes.size() > 1) { QMenu* m = m_menu->addMenu(i18n("Mode")); foreach (Mode mode, modes) { m->addAction(findAction(Converters::modeToString(mode))); } findAction(Converters::modeToString(modes.first()))->setChecked(true); } m_menu->exec(e->globalPos()); } void RegistersView::updateRegisters() { changeAvaliableActions(); foreach (const QString & v, activeViews()) { m_modelsManager->updateRegisters(v); } } void RegistersView::menuTriggered(const QString& formatOrMode) { Format f = Converters::stringToFormat(formatOrMode); if (f != LAST_FORMAT) { m_modelsManager->setFormat(activeViews().first(), f); } else { m_modelsManager->setMode(activeViews().first(), Converters::stringToMode(formatOrMode)); } updateRegisters(); } void RegistersView::changeAvaliableActions() { const QString view = activeViews().first(); if (view.isEmpty()) { return; } const QVector formats = m_modelsManager->formats(view) ; const QVector modes = m_modelsManager->modes(view); foreach (QAction * a, m_actions) { bool enable = false; foreach (Format f, formats) { if (a->text() == Converters::formatToString(f)) { enable = true; break; } } if (!enable) { foreach (Mode m, modes) { if (a->text() == Converters::modeToString(m)) { enable = true; break; } } } a->setVisible(enable); a->setEnabled(enable); } } QAction* RegistersView::findAction(const QString& name) { foreach (QAction * a, m_actions) { if (a->text() == name) { return a; } } - return 0; + return nullptr; } void RegistersView::addView(QTableView* view, int idx) { view->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); view->horizontalHeader()->hide(); view->verticalHeader()->hide(); view->setSelectionMode(QAbstractItemView::SingleSelection); view->setMinimumWidth(10); view->verticalHeader()->setDefaultSectionSize(15); QString name = m_modelsManager->addView(view); setNameForTable(idx, name); } void RegistersView::enable(bool enabled) { setEnabled(enabled); if (enabled) { clear(); addView(registers, 0); addView(flags, 0); addView(table_1, 1); addView(table_2, 2); addView(table_3, 3); changeAvaliableActions(); } } void RegistersView::setNameForTable(int idx, const QString& name) { qCDebug(DEBUGGERCOMMON) << name << " " << idx; const QString text = tabWidget->tabText(idx); if (!text.contains(name)) { tabWidget->setTabText(idx, text.isEmpty() ? name : text + '/' + name); } } void RegistersView::setModel(ModelsManager* m) { m_modelsManager = m; } QStringList RegistersView::activeViews() { return tabWidget->tabText(tabWidget->currentIndex()).split('/'); } void RegistersView::clear() { for (int i = 0; i < TABLES_COUNT; i++) { tabWidget->setTabText(i, ""); } } void RegistersView::setupActions() { QAction* updateAction = new QAction(this); updateAction->setShortcut(Qt::Key_U); updateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); updateAction->setText(i18n("Update")); connect(updateAction, &QAction::triggered, this, &RegistersView::updateRegisters); addAction(updateAction); m_menu->addAction(updateAction); insertAction(Converters::formatToString(Binary), Qt::Key_B); insertAction(Converters::formatToString(Octal), Qt::Key_O); insertAction(Converters::formatToString(Decimal), Qt::Key_D); insertAction(Converters::formatToString(Hexadecimal), Qt::Key_H); insertAction(Converters::formatToString(Raw), Qt::Key_R); insertAction(Converters::formatToString(Unsigned), Qt::Key_N); insertAction(Converters::modeToString(u32), Qt::Key_I); insertAction(Converters::modeToString(u64), Qt::Key_L); insertAction(Converters::modeToString(f32), Qt::Key_F); insertAction(Converters::modeToString(f64), Qt::Key_P); insertAction(Converters::modeToString(v2_double), Qt::Key_P); insertAction(Converters::modeToString(v2_int64), Qt::Key_L); insertAction(Converters::modeToString(v4_float), Qt::Key_F); insertAction(Converters::modeToString(v4_int32), Qt::Key_I); } void RegistersView::insertAction(const QString& name, Qt::Key k) { QAction* a = new QAction(this); a->setCheckable(true); a->setShortcut(k); a->setText(name); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_actions.append(a); addAction(a); m_mapper->setMapping(a, a->text()); connect(a, &QAction::triggered, m_mapper, static_cast(&QSignalMapper::map)); } diff --git a/debuggers/common/registers/registersview.h b/debuggers/common/registers/registersview.h index b3de22589a..6641c94a1c 100644 --- a/debuggers/common/registers/registersview.h +++ b/debuggers/common/registers/registersview.h @@ -1,93 +1,93 @@ /* * Displays registers. * 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. * */ #ifndef REGISTERSVIEW_H #define REGISTERSVIEW_H #include "ui_registersview.h" #include class QMenu; class QSignalMapper; namespace KDevMI { class ModelsManager; /** @brief Class for displaying registers content */ class RegistersView : public QWidget, private Ui::RegistersView { Q_OBJECT public: - RegistersView(QWidget* p = 0); + RegistersView(QWidget* p = nullptr); void enable(bool enabled); void setModel(ModelsManager* m); public slots: ///Updates registers for active views. void updateRegisters(); protected: ///Allows to choose register formates/modes. void contextMenuEvent(QContextMenuEvent* e) override; private: ///Returns list of active views. QStringList activeViews(); ///Adds @p v to the list of views with assigning it a name. void addView(QTableView* view, int idx); ///Clears names of all views. void clear(); ///Sets name for the table with index @p idx to the @p name. void setNameForTable(int idx, const QString& name); private slots: ///Changes register formates/modes to @p formatOrMode. void menuTriggered(const QString& formatOrMode); private: void setupActions(); ///Adds new action into m_actions and to this widget's list of actions. void insertAction(const QString& name, Qt::Key k); ///Returns action for given @p name. QAction* findAction(const QString& name); private Q_SLOTS: ///Enables/disables actions based on current view. void changeAvaliableActions(); private: QMenu* m_menu; QSignalMapper* m_mapper; ModelsManager* m_modelsManager; QVector m_actions; }; } // end of namespace KDevMI #endif diff --git a/debuggers/common/stty.cpp b/debuggers/common/stty.cpp index 2a9de26dc9..c348cb22e0 100644 --- a/debuggers/common/stty.cpp +++ b/debuggers/common/stty.cpp @@ -1,347 +1,347 @@ /*************************************************************************** begin : Mon Sep 13 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org This code was originally written by Judin Maxim, from the KDEStudio project. It was then updated with later code from konsole (KDE). It has also been enhanced with an idea from the code in kdbg written by Johannes Sixt ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #ifdef __osf__ #define _XOPEN_SOURCE_EXTENDED #define O_NDELAY O_NONBLOCK #endif #include #include #include #include #include #include #ifdef HAVE_SYS_STROPTS_H #include #define _NEW_TTY_CTRL #endif #include #include #include #include #include #include #include #include #include #include #if defined (_HPUX_SOURCE) #define _TERMIOS_INCLUDED #include #endif #include #include #include #include #include #include #include #include #include "stty.h" #include "debuglog.h" #define PTY_FILENO 3 #define BASE_CHOWN "konsole_grantpty" #include using namespace KDevMI; static int chownpty(int fd, int grant) // param fd: the fd of a master pty. // param grant: 1 to grant, 0 to revoke // returns 1 on success 0 on fail { void(*tmp)(int) = signal(SIGCHLD,SIG_DFL); pid_t pid = fork(); if (pid < 0) { signal(SIGCHLD,tmp); return 0; } if (pid == 0) { /* We pass the master pseudo terminal as file descriptor PTY_FILENO. */ if (fd != PTY_FILENO && dup2(fd, PTY_FILENO) < 0) ::exit(1); QString path = QStandardPaths::findExecutable(BASE_CHOWN); - execle(QFile::encodeName(path), BASE_CHOWN, grant?"--grant":"--revoke", (void *)0, NULL); + execle(QFile::encodeName(path), BASE_CHOWN, grant?"--grant":"--revoke", (void *)nullptr, NULL); ::exit(1); // should not be reached } if (pid > 0) { int w; // retry: int rc = waitpid (pid, &w, 0); if (rc != pid) ::exit(1); // { // signal from other child, behave like catchChild. // // guess this gives quite some control chaos... // Shell* sh = shells.indexOf(rc); // if (sh) { shells.remove(rc); sh->doneShell(w); } // goto retry; // } signal(SIGCHLD,tmp); return (rc != -1 && WIFEXITED(w) && WEXITSTATUS(w) == 0); } signal(SIGCHLD,tmp); return 0; //dummy. } // ************************************************************************** STTY::STTY(bool ext, const QString &termAppName) : QObject(), - out(0), + out(nullptr), ttySlave(""), - m_externalTerminal(0), + m_externalTerminal(nullptr), external_(ext) { if (ext) { findExternalTTY(termAppName); } else { fout = findTTY(); if (fout >= 0) { ttySlave = QString(tty_slave); out = new QSocketNotifier(fout, QSocketNotifier::Read, this); connect( out, &QSocketNotifier::activated, this, &STTY::OutReceived ); } } } // ************************************************************************** STTY::~STTY() { if (out) { ::close(fout); delete out; } } // ************************************************************************** int STTY::findTTY() { int ptyfd = -1; bool needGrantPty = true; // Find a master pty that we can open //////////////////////////////// #ifdef __sgi__ ptyfd = open("/dev/ptmx",O_RDWR); #elif defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) ptyfd = posix_openpt(O_RDWR); #endif #if defined(__sgi__) || defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) if (ptyfd == -1) { perror("Can't open a pseudo teletype"); return(-1); } else if (ptyfd >= 0) { strncpy(tty_slave, ptsname(ptyfd), 50); grantpt(ptyfd); unlockpt(ptyfd); needGrantPty = false; } #endif // first we try UNIX PTY's #if defined(TIOCGPTN) && !defined(Q_OS_FREEBSD) strcpy(pty_master,"/dev/ptmx"); strcpy(tty_slave,"/dev/pts/"); ptyfd = open(pty_master,O_RDWR); if (ptyfd >= 0) { // got the master pty int ptyno; if (ioctl(ptyfd, TIOCGPTN, &ptyno) == 0) { struct stat sbuf; sprintf(tty_slave,"/dev/pts/%d",ptyno); if (stat(tty_slave,&sbuf) == 0 && S_ISCHR(sbuf.st_mode)) needGrantPty = false; else { close(ptyfd); ptyfd = -1; } } else { close(ptyfd); ptyfd = -1; } } #endif #if defined(_SCO_DS) || defined(__USLC__) /* SCO OSr5 and UnixWare */ if (ptyfd < 0) { for (int idx = 0; idx < 256; idx++) { sprintf(pty_master, "/dev/ptyp%d", idx); sprintf(tty_slave, "/dev/ttyp%d", idx); if (access(tty_slave, F_OK) < 0) { idx = 256; break; } if ((ptyfd = open (pty_master, O_RDWR)) >= 0) { if (access (tty_slave, R_OK|W_OK) == 0) break; close(ptyfd); ptyfd = -1; } } } #endif if (ptyfd < 0) { /// \FIXME Linux, Trouble on other systems? for (const char* s3 = "pqrstuvwxyzabcde"; *s3 != 0; s3++) { for (const char* s4 = "0123456789abcdef"; *s4 != 0; s4++) { sprintf(pty_master,"/dev/pty%c%c",*s3,*s4); sprintf(tty_slave,"/dev/tty%c%c",*s3,*s4); if ((ptyfd = open(pty_master, O_RDWR)) >= 0) { if (geteuid() == 0 || access(tty_slave, R_OK|W_OK) == 0) break; close(ptyfd); ptyfd = -1; } } if (ptyfd >= 0) break; } } if (ptyfd >= 0) { if (needGrantPty && !chownpty(ptyfd, true)) { fprintf(stderr,"kdevelop: chownpty failed for device %s::%s.\n",pty_master,tty_slave); fprintf(stderr," : This means the session can be eavesdroped.\n"); fprintf(stderr," : Make sure konsole_grantpty is installed and setuid root.\n"); } ::fcntl(ptyfd, F_SETFL, O_NDELAY); #ifdef TIOCSPTLCK int flag = 0; ioctl(ptyfd, TIOCSPTLCK, &flag); // unlock pty #endif } if (ptyfd==-1) { m_lastError = i18n("Cannot use the tty* or pty* devices.\n" "Check the settings on /dev/tty* and /dev/pty*\n" "As root you may need to \"chmod ug+rw\" tty* and pty* devices " "and/or add the user to the tty group using " "\"usermod -aG tty username\"."); } return ptyfd; } // ************************************************************************** void STTY::OutReceived(int f) { char buf[1024]; int n; // read until socket is empty. We shouldn't be receiving a continuous // stream of data, so the loop is unlikely to cause problems. while ((n = ::read(f, buf, sizeof(buf)-1)) > 0) { *(buf+n) = 0; // a standard string QByteArray ba(buf); emit OutOutput(ba); } // Note: for some reason, n can be 0 here. // I can understand that non-blocking read returns 0, // but I don't understand how OutRecieved can be even // called when there's no input. if (n == 0 /* eof */ || (n == -1 && errno != EAGAIN)) { // Found eof or error. Disable socket notifier, otherwise Qt // will repeatedly call this method, eating CPU // cycles. out->setEnabled(false); } } void STTY::readRemaining() { if (!external_) OutReceived(fout); } bool STTY::findExternalTTY(const QString& termApp) { QString appName(termApp.isEmpty() ? QString("xterm") : termApp); if (QStandardPaths::findExecutable(appName).isEmpty()) { m_lastError = i18n("%1 is incorrect terminal name", termApp); return false; } QTemporaryFile file; if (!file.open()) { m_lastError = i18n("Can't create a temporary file"); return false; } m_externalTerminal.reset(new QProcess(this)); if (appName == "konsole") { m_externalTerminal->start(appName, QStringList() << "-e" << "sh" << "-c" << "tty>" + file.fileName() + ";exec<&-;exec>&-;while :;do sleep 3600;done"); } else if (appName == "xfce4-terminal") { m_externalTerminal->start(appName, QStringList() << "-e" << " sh -c \"tty>" + file.fileName() + ";\"\"<&\\-\"\">&\\-;\"\"while :;\"\"do sleep 3600;\"\"done\""); } else { m_externalTerminal->start(appName, QStringList() << "-e" << "sh -c \"tty>" + file.fileName() + ";exec<&-;exec>&-;while :;do sleep 3600;done\""); } if (!m_externalTerminal->waitForStarted(500)) { m_lastError = "Can't run terminal: " + appName; m_externalTerminal->terminate(); return false; } for (int i = 0; i < 800; i++) { if (!file.bytesAvailable()) { if (m_externalTerminal->state() == QProcess::NotRunning && m_externalTerminal->exitCode()) { break; } QCoreApplication::processEvents(QEventLoop::AllEvents, 100); usleep(8000); } else { qCDebug(DEBUGGERCOMMON) << "Received terminal output(tty)"; break; } } usleep(1000); ttySlave = file.readAll().trimmed(); file.close(); if (ttySlave.isEmpty()) { m_lastError = i18n("Can't receive %1 tty/pty. Check that %1 is actually a terminal and that it accepts these arguments: -e sh -c \"tty> %2 ;exec<&-;exec>&-;while :;do sleep 3600;done\"", appName, file.fileName()); } return true; } // ************************************************************************** diff --git a/debuggers/common/widgets/disassemblewidget.cpp b/debuggers/common/widgets/disassemblewidget.cpp index cd81140d2b..b01e685448 100644 --- a/debuggers/common/widgets/disassemblewidget.cpp +++ b/debuggers/common/widgets/disassemblewidget.cpp @@ -1,540 +1,540 @@ /* * GDB Debugger Support * * Copyright 2000 John Birch * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2013 Vlas Puhov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "disassemblewidget.h" #include "midebuggerplugin.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include "registers/registersmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevMI; using namespace KDevMI::MI; SelectAddressDialog::SelectAddressDialog(QWidget* parent) : QDialog(parent) { m_ui.setupUi(this); setWindowTitle(i18n("Address Selector")); connect(m_ui.comboBox, &KHistoryComboBox::editTextChanged, this, &SelectAddressDialog::validateInput); connect(m_ui.comboBox, static_cast(&KHistoryComboBox::returnPressed), this, &SelectAddressDialog::itemSelected); } QString SelectAddressDialog::address() const { return hasValidAddress() ? m_ui.comboBox->currentText() : QString(); } bool SelectAddressDialog::hasValidAddress() const { bool ok; m_ui.comboBox->currentText().toLongLong(&ok, 16); return ok; } void SelectAddressDialog::updateOkState() { m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(hasValidAddress()); } void SelectAddressDialog::validateInput() { updateOkState(); } void SelectAddressDialog::itemSelected() { QString text = m_ui.comboBox->currentText(); if( hasValidAddress() && m_ui.comboBox->findText(text) < 0 ) m_ui.comboBox->addItem(text); } DisassembleWindow::DisassembleWindow(QWidget *parent, DisassembleWidget* widget) : QTreeWidget(parent) { /*context menu commands */{ m_selectAddrAction = new QAction(i18n("Change &address"), this); m_selectAddrAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(m_selectAddrAction, &QAction::triggered, widget, &DisassembleWidget::slotChangeAddress); m_jumpToLocation = new QAction(QIcon::fromTheme("debug-execute-to-cursor"), i18n("&Jump to Cursor"), this); m_jumpToLocation->setWhatsThis(i18n("Sets the execution pointer to the current cursor position.")); connect(m_jumpToLocation,&QAction::triggered, widget, &DisassembleWidget::jumpToCursor); m_runUntilCursor = new QAction(QIcon::fromTheme("debug-run-cursor"), i18n("&Run to Cursor"), this); m_runUntilCursor->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); connect(m_runUntilCursor,&QAction::triggered, widget, &DisassembleWidget::runToCursor); m_disassemblyFlavorAtt = new QAction(i18n("&AT&&T"), this); m_disassemblyFlavorAtt->setToolTip(i18n("GDB will use the AT&T disassembly flavor (e.g. mov 0xc(%ebp),%eax).")); m_disassemblyFlavorAtt->setData(DisassemblyFlavorATT); m_disassemblyFlavorAtt->setCheckable(true); m_disassemblyFlavorIntel = new QAction(i18n("&Intel"), this); m_disassemblyFlavorIntel->setToolTip(i18n("GDB will use the Intel disassembly flavor (e.g. mov eax, DWORD PTR [ebp+0xc]).")); m_disassemblyFlavorIntel->setData(DisassemblyFlavorIntel); m_disassemblyFlavorIntel->setCheckable(true); m_disassemblyFlavorActionGroup = new QActionGroup(this); m_disassemblyFlavorActionGroup->setExclusive(true); m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorAtt); m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorIntel); connect(m_disassemblyFlavorActionGroup, &QActionGroup::triggered, widget, &DisassembleWidget::setDisassemblyFlavor); } } void DisassembleWindow::setDisassemblyFlavor(DisassemblyFlavor flavor) { switch(flavor) { default: case DisassemblyFlavorUnknown: m_disassemblyFlavorAtt->setChecked(false); m_disassemblyFlavorIntel->setChecked(false); break; case DisassemblyFlavorATT: m_disassemblyFlavorAtt->setChecked(true); m_disassemblyFlavorIntel->setChecked(false); break; case DisassemblyFlavorIntel: m_disassemblyFlavorAtt->setChecked(false); m_disassemblyFlavorIntel->setChecked(true); break; } } void DisassembleWindow::contextMenuEvent(QContextMenuEvent *e) { QMenu popup(this); popup.addAction(m_selectAddrAction); popup.addAction(m_jumpToLocation); popup.addAction(m_runUntilCursor); QMenu* disassemblyFlavorMenu = popup.addMenu(i18n("Disassembly flavor")); disassemblyFlavorMenu->addAction(m_disassemblyFlavorAtt); disassemblyFlavorMenu->addAction(m_disassemblyFlavorIntel); popup.exec(e->globalPos()); } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ DisassembleWidget::DisassembleWidget(MIDebuggerPlugin* plugin, QWidget *parent) : QWidget(parent), active_(false), lower_(0), upper_(0), address_(0), m_splitter(new KDevelop::AutoOrientedSplitter(this)) { QVBoxLayout* topLayout = new QVBoxLayout(this); QHBoxLayout* controlsLayout = new QHBoxLayout; topLayout->addLayout(controlsLayout); { // initialize disasm/registers views topLayout->addWidget(m_splitter); //topLayout->setMargin(0); m_disassembleWindow = new DisassembleWindow(m_splitter, this); m_disassembleWindow->setWhatsThis(i18n("Machine code display

" "A machine code view into your running " "executable with the current instruction " "highlighted. You can step instruction by " "instruction using the debuggers toolbar " "buttons of \"step over\" instruction and " "\"step into\" instruction.")); m_disassembleWindow->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_disassembleWindow->setSelectionMode(QTreeWidget::SingleSelection); m_disassembleWindow->setColumnCount(ColumnCount); m_disassembleWindow->setUniformRowHeights(true); m_disassembleWindow->setRootIsDecorated(false); m_disassembleWindow->setHeaderLabels(QStringList() << "" << i18n("Address") << i18n("Function") << i18n("Instruction")); m_splitter->setStretchFactor(0, 1); m_splitter->setContentsMargins(0, 0, 0, 0); m_registersManager = new RegistersManager(m_splitter); m_config = KSharedConfig::openConfig()->group("Disassemble/Registers View"); QByteArray state = m_config.readEntry("splitterState", QByteArray()); if (!state.isEmpty()) { m_splitter->restoreState(state); } } setLayout(topLayout); setWindowIcon( QIcon::fromTheme("system-run", windowIcon()) ); setWindowTitle(i18n("Disassemble/Registers View")); KDevelop::IDebugController* pDC=KDevelop::ICore::self()->debugController(); Q_ASSERT(pDC); connect(pDC, &KDevelop::IDebugController::currentSessionChanged, this, &DisassembleWidget::currentSessionChanged); connect(plugin, &MIDebuggerPlugin::reset, this, &DisassembleWidget::slotDeactivate); m_dlg = new SelectAddressDialog(this); // show the data if debug session is active KDevelop::IDebugSession* pS = pDC->currentSession(); currentSessionChanged(pS); if(pS && !pS->currentAddr().isEmpty()) slotShowStepInSource(pS->currentUrl(), pS->currentLine(), pS->currentAddr()); } void DisassembleWidget::jumpToCursor() { MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if (s && s->isRunning()) { QString address = m_disassembleWindow->selectedItems().at(0)->text(Address); s->jumpToMemoryAddress(address); } } void DisassembleWidget::runToCursor(){ MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if (s && s->isRunning()) { QString address = m_disassembleWindow->selectedItems().at(0)->text(Address); s->runUntil(address); } } void DisassembleWidget::currentSessionChanged(KDevelop::IDebugSession* s) { MIDebugSession *session = qobject_cast(s); - enableControls( session != NULL ); // disable if session closed + enableControls( session != nullptr ); // disable if session closed m_registersManager->setSession(session); if (session) { connect(session, &MIDebugSession::showStepInSource, this, &DisassembleWidget::slotShowStepInSource); connect(session,&MIDebugSession::showStepInDisassemble,this, &DisassembleWidget::update); } } /***************************************************************************/ DisassembleWidget::~DisassembleWidget() { m_config.writeEntry("splitterState", m_splitter->saveState()); } /***************************************************************************/ bool DisassembleWidget::displayCurrent() { if(address_ < lower_ || address_ > upper_) return false; bool bFound=false; for (int line=0; line < m_disassembleWindow->topLevelItemCount(); line++) { QTreeWidgetItem* item = m_disassembleWindow->topLevelItem(line); unsigned long address = item->text(Address).toULong(&ok,16); if (address == address_) { // put cursor at start of line and highlight the line m_disassembleWindow->setCurrentItem(item); static const QIcon icon = QIcon::fromTheme(QStringLiteral("go-next")); item->setIcon(Icon, icon); bFound = true; // need to process all items to clear icons } else if(!item->icon(Icon).isNull()) item->setIcon(Icon, QIcon()); } return bFound; } /***************************************************************************/ void DisassembleWidget::slotActivate(bool activate) { qCDebug(DEBUGGERCOMMON) << "Disassemble widget active: " << activate; if (active_ != activate) { active_ = activate; if (active_) { updateDisassemblyFlavor(); m_registersManager->updateRegisters(); if (!displayCurrent()) disassembleMemoryRegion(); } } } /***************************************************************************/ void DisassembleWidget::slotShowStepInSource(const QUrl&, int, const QString& currentAddress) { update(currentAddress); } void DisassembleWidget::updateExecutionAddressHandler(const ResultRecord& r) { const Value& content = r["asm_insns"]; const Value& pc = content[0]; if( pc.hasField("address") ){ QString addr = pc["address"].literal(); address_ = addr.toULong(&ok,16); disassembleMemoryRegion(addr); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryRegion(const QString& from, const QString& to) { MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) return; //only get $pc if (from.isEmpty()){ s->addCommand(DataDisassemble, "-s \"$pc\" -e \"$pc+1\" -- 0", this, &DisassembleWidget::updateExecutionAddressHandler); }else{ QString cmd = (to.isEmpty())? QString("-s %1 -e \"%1 + 256\" -- 0").arg(from ): QString("-s %1 -e %2+1 -- 0").arg(from).arg(to); // if both addr set s->addCommand(DataDisassemble, cmd, this, &DisassembleWidget::disassembleMemoryHandler); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryHandler(const ResultRecord& r) { const Value& content = r["asm_insns"]; QString currentFunction; m_disassembleWindow->clear(); for(int i = 0; i < content.size(); ++i) { const Value& line = content[i]; QString addr, fct, offs, inst; if( line.hasField("address") ) addr = line["address"].literal(); if( line.hasField("func-name") ) fct = line["func-name"].literal(); if( line.hasField("offset") ) offs = line["offset"].literal(); if( line.hasField("inst") ) inst = line["inst"].literal(); //We use offset at the same column where function is. if(currentFunction == fct){ if(!fct.isEmpty()){ fct = QString("+") + offs; } }else { currentFunction = fct; } m_disassembleWindow->addTopLevelItem(new QTreeWidgetItem(m_disassembleWindow, QStringList() << QString() << addr << fct << inst)); if (i == 0) { lower_ = addr.toULong(&ok,16); } else if (i == content.size()-1) { upper_ = addr.toULong(&ok,16); } } displayCurrent(); m_disassembleWindow->resizeColumnToContents(Icon); // make Icon always visible m_disassembleWindow->resizeColumnToContents(Address); // make entire address always visible } void DisassembleWidget::showEvent(QShowEvent*) { slotActivate(true); //it doesn't work for large names of functions // for (int i = 0; i < m_disassembleWindow->model()->columnCount(); ++i) // m_disassembleWindow->resizeColumnToContents(i); } void DisassembleWidget::hideEvent(QHideEvent*) { slotActivate(false); } void DisassembleWidget::slotDeactivate() { slotActivate(false); } void DisassembleWidget::enableControls(bool enabled) { m_disassembleWindow->setEnabled(enabled); } void DisassembleWidget::slotChangeAddress() { if(!m_dlg) return; m_dlg->updateOkState(); if (!m_disassembleWindow->selectedItems().isEmpty()) { m_dlg->setAddress(m_disassembleWindow->selectedItems().first()->text(Address)); } if (m_dlg->exec() == QDialog::Rejected) return; unsigned long addr = m_dlg->address().toULong(&ok,16); if (addr < lower_ || addr > upper_ || !displayCurrent()) disassembleMemoryRegion(m_dlg->address()); } void SelectAddressDialog::setAddress ( const QString& address ) { m_ui.comboBox->setCurrentItem ( address, true ); } void DisassembleWidget::update(const QString &address) { if (!active_) { return; } address_ = address.toULong(&ok, 16); if (!displayCurrent()) { disassembleMemoryRegion(); } m_registersManager->updateRegisters(); } void DisassembleWidget::setDisassemblyFlavor(QAction* action) { MIDebugSession* s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) { return; } DisassemblyFlavor disassemblyFlavor = static_cast(action->data().toInt()); QString cmd; switch(disassemblyFlavor) { default: // unknown flavor, do not build a GDB command break; case DisassemblyFlavorATT: cmd = QStringLiteral("disassembly-flavor att"); break; case DisassemblyFlavorIntel: cmd = QStringLiteral("disassembly-flavor intel"); break; } qCDebug(DEBUGGERCOMMON) << "Disassemble widget set " << cmd; if (!cmd.isEmpty()) { s->addCommand(GdbSet, cmd, this, &DisassembleWidget::setDisassemblyFlavorHandler); } } void DisassembleWidget::setDisassemblyFlavorHandler(const ResultRecord& r) { if (r.reason == "done" && active_) { disassembleMemoryRegion(); } } void DisassembleWidget::updateDisassemblyFlavor() { MIDebugSession* s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) { return; } s->addCommand(GdbShow, QStringLiteral("disassembly-flavor"), this, &DisassembleWidget::showDisassemblyFlavorHandler); } void DisassembleWidget::showDisassemblyFlavorHandler(const ResultRecord& r) { const Value& value = r["value"]; qCDebug(DEBUGGERCOMMON) << "Disassemble widget disassembly flavor" << value.literal(); DisassemblyFlavor disassemblyFlavor = DisassemblyFlavorUnknown; if (value.literal() == "att") { disassemblyFlavor = DisassemblyFlavorATT; } else if (value.literal() == "intel") { disassemblyFlavor = DisassemblyFlavorIntel; } else if (value.literal() == "default") { disassemblyFlavor = DisassemblyFlavorATT; } m_disassembleWindow->setDisassemblyFlavor(disassemblyFlavor); } diff --git a/debuggers/common/widgets/disassemblewidget.h b/debuggers/common/widgets/disassemblewidget.h index ee8b1816c2..7e6a405b04 100644 --- a/debuggers/common/widgets/disassemblewidget.h +++ b/debuggers/common/widgets/disassemblewidget.h @@ -1,172 +1,172 @@ /* * GDB Debugger Support * * Copyright 1999 John Birch * 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. */ #ifndef _DISASSEMBLEWIDGET_H_ #define _DISASSEMBLEWIDGET_H_ #include "mi/mi.h" #include #include #include #include "ui_selectaddressdialog.h" /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ class QSplitter; namespace KDevelop { class IDebugSession; } namespace KDevMI { class RegistersManager; class SelectAddressDialog : public QDialog { Q_OBJECT public: - SelectAddressDialog(QWidget *parent = 0); + SelectAddressDialog(QWidget *parent = nullptr); QString address() const; void setAddress(const QString& address); bool hasValidAddress() const; void updateOkState(); private Q_SLOTS: void validateInput(); void itemSelected(); private: Ui::SelectAddressDialog m_ui; }; class DisassembleWidget; enum DisassemblyFlavor { DisassemblyFlavorUnknown = -1, DisassemblyFlavorATT = 0, DisassemblyFlavorIntel, }; class DisassembleWindow : public QTreeWidget { public: DisassembleWindow(QWidget *parent, DisassembleWidget* widget); void setDisassemblyFlavor(DisassemblyFlavor flavor); protected: void contextMenuEvent(QContextMenuEvent *e) override; private: QAction* m_selectAddrAction; QAction* m_jumpToLocation; QAction* m_runUntilCursor; QAction* m_disassemblyFlavorAtt; QAction* m_disassemblyFlavorIntel; QActionGroup* m_disassemblyFlavorActionGroup; }; class MIDebuggerPlugin; class DisassembleWidget : public QWidget { Q_OBJECT public: enum Columns { Icon, Address, Function, Instruction, ColumnCount }; - DisassembleWidget( MIDebuggerPlugin* plugin, QWidget *parent=0 ); + DisassembleWidget( MIDebuggerPlugin* plugin, QWidget *parent=nullptr ); ~DisassembleWidget() override; Q_SIGNALS: void requestRaise(); public Q_SLOTS: void slotActivate(bool activate); void slotDeactivate(); void slotShowStepInSource(const QUrl &fileName, int lineNum, const QString &address); void slotChangeAddress(); ///Disassembles code at @p address and updates registers void update(const QString &address); void jumpToCursor(); void runToCursor(); void setDisassemblyFlavor(QAction* action); private Q_SLOTS: void currentSessionChanged(KDevelop::IDebugSession* session); protected: void showEvent(QShowEvent*) override; void hideEvent(QHideEvent*) override; void enableControls(bool enabled); private: bool displayCurrent(); void updateDisassemblyFlavor(); /// Disassembles memory region from..to /// if from is empty current execution position is used /// if to is empty, 256 bytes range is taken void disassembleMemoryRegion(const QString& from=QString(), const QString& to=QString() ); /// callbacks for GDBCommands void disassembleMemoryHandler(const MI::ResultRecord& r); void updateExecutionAddressHandler(const MI::ResultRecord& r); void setDisassemblyFlavorHandler(const MI::ResultRecord& r); void showDisassemblyFlavorHandler(const MI::ResultRecord& r); //for str to uint conversion. bool ok; bool active_; unsigned long lower_; unsigned long upper_; unsigned long address_; RegistersManager* m_registersManager ; DisassembleWindow * m_disassembleWindow; SelectAddressDialog* m_dlg; KConfigGroup m_config; QSplitter *m_splitter; }; } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/gdb.h b/debuggers/gdb/gdb.h index 0495154f5e..d09789434f 100644 --- a/debuggers/gdb/gdb.h +++ b/debuggers/gdb/gdb.h @@ -1,43 +1,43 @@ /* * Low level GDB interface. * * Copyright 2007 Vladimir Prus * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef GDB_H_d5c9cb274cbad688fe7a507a84f6633b #define GDB_H_d5c9cb274cbad688fe7a507a84f6633b #include "midebugger.h" namespace KDevMI { namespace GDB { class GdbDebugger : public MIDebugger { Q_OBJECT public: - explicit GdbDebugger(QObject* parent = 0); + explicit GdbDebugger(QObject* parent = nullptr); ~GdbDebugger() override; bool start(KConfigGroup& config, const QStringList& extraArguments = {}) override; }; } // end of namespace GDB } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/gdbconfigpage.cpp b/debuggers/gdb/gdbconfigpage.cpp index 48cc2211a5..6b455da3ec 100644 --- a/debuggers/gdb/gdbconfigpage.cpp +++ b/debuggers/gdb/gdbconfigpage.cpp @@ -1,194 +1,194 @@ /* * GDB Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gdbconfigpage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dbgglobal.h" #include "debugsession.h" #include "debuggerplugin.h" #include "midebugjobs.h" #include "ui_gdbconfigpage.h" #include #include using namespace KDevelop; namespace Config = KDevMI::GDB::Config; GdbConfigPage::GdbConfigPage( QWidget* parent ) : LaunchConfigurationPage(parent), ui( new Ui::GdbConfigPage ) { ui->setupUi( this ); ui->kcfg_gdbPath->setMode(KFile::File|KFile::ExistingOnly|KFile::LocalOnly); connect(ui->kcfg_asmDemangle, &QCheckBox::toggled, this, &GdbConfigPage::changed); connect(ui->kcfg_configGdbScript, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); //connect(ui->kcfg_dbgTerminal, SIGNAL(toggled(bool)), this, SIGNAL(changed())); connect(ui->kcfg_debuggingShell, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_displayStaticMembers, &QCheckBox::toggled, this, &GdbConfigPage::changed); connect(ui->kcfg_gdbPath, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_runGdbScript, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_runShellScript, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_startWith, static_cast(&QComboBox::currentIndexChanged), this, &GdbConfigPage::changed); //Setup data info for combobox ui->kcfg_startWith->setItemData(0, "ApplicationOutput" ); ui->kcfg_startWith->setItemData(1, "GdbConsole" ); ui->kcfg_startWith->setItemData(2, "FrameStack" ); } GdbConfigPage::~GdbConfigPage() { delete ui; } QIcon GdbConfigPage::icon() const { return QIcon(); } void GdbConfigPage::loadFromConfiguration( const KConfigGroup& cfg, KDevelop::IProject* ) { bool block = blockSignals( true ); ui->kcfg_gdbPath->setUrl( cfg.readEntry( Config::GdbPathEntry, QUrl() ) ); ui->kcfg_debuggingShell->setUrl( cfg.readEntry( Config::DebuggerShellEntry, QUrl() ) ); ui->kcfg_configGdbScript->setUrl( cfg.readEntry( Config::RemoteGdbConfigEntry, QUrl() ) ); ui->kcfg_runShellScript->setUrl( cfg.readEntry( Config::RemoteGdbShellEntry, QUrl() ) ); ui->kcfg_runGdbScript->setUrl( cfg.readEntry( Config::RemoteGdbRunEntry, QUrl() ) ); ui->kcfg_displayStaticMembers->setChecked( cfg.readEntry( Config::StaticMembersEntry, false ) ); ui->kcfg_asmDemangle->setChecked( cfg.readEntry( Config::DemangleNamesEntry, true) ); ui->kcfg_startWith->setCurrentIndex( ui->kcfg_startWith->findData( cfg.readEntry( KDevMI::Config::StartWithEntry, "ApplicationOutput" ) ) ); blockSignals( block ); } void GdbConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* ) const { cfg.writeEntry(Config::GdbPathEntry, ui->kcfg_gdbPath->url() ); cfg.writeEntry(Config::DebuggerShellEntry, ui->kcfg_debuggingShell->url() ); cfg.writeEntry(Config::RemoteGdbConfigEntry, ui->kcfg_configGdbScript->url() ); cfg.writeEntry(Config::RemoteGdbShellEntry, ui->kcfg_runShellScript->url() ); cfg.writeEntry(Config::RemoteGdbRunEntry, ui->kcfg_runGdbScript->url() ); cfg.writeEntry(Config::StaticMembersEntry, ui->kcfg_displayStaticMembers->isChecked() ); cfg.writeEntry(Config::DemangleNamesEntry, ui->kcfg_asmDemangle->isChecked() ); cfg.writeEntry(KDevMI::Config::StartWithEntry, ui->kcfg_startWith->itemData( ui->kcfg_startWith->currentIndex() ).toString() ); } QString GdbConfigPage::title() const { return i18n( "GDB Configuration" ); } GdbLauncher::GdbLauncher( KDevMI::GDB::CppDebuggerPlugin* p, IExecutePlugin* execute ) : m_plugin( p ) , m_execute( execute ) { factoryList << new GdbConfigPageFactory(); } QList< KDevelop::LaunchConfigurationPageFactory* > GdbLauncher::configPages() const { return factoryList; } QString GdbLauncher::id() { return "gdb"; } QString GdbLauncher::name() const { return i18n("GDB"); } KJob* GdbLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { - return 0; + return nullptr; } if( launchMode == "debug" ) { Q_ASSERT(m_execute); Q_ASSERT(m_plugin); if (KDevelop::ICore::self()->debugController()->currentSession() != nullptr) { KMessageBox::ButtonCode answer = KMessageBox::warningYesNo( nullptr, i18n("A program is already being debugged. Do you want to abort the " "currently running debug session and continue with the launch?")); if (answer == KMessageBox::No) return nullptr; } QList l; KJob* depjob = m_execute->dependencyJob(cfg); if( depjob ) { l << depjob; } l << new KDevMI::MIDebugJob( m_plugin, cfg, m_execute ); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l ); } qWarning() << "Unknown launch mode" << launchMode << "for config:" << cfg->name(); - return 0; + return nullptr; } QStringList GdbLauncher::supportedModes() const { return QStringList() << "debug"; } QString GdbLauncher::description() const { return i18n("Executes a Native application in GDB"); } KDevelop::LaunchConfigurationPage* GdbConfigPageFactory::createWidget( QWidget* parent ) { return new GdbConfigPage( parent ); } diff --git a/debuggers/gdb/gdbconfigpage.h b/debuggers/gdb/gdbconfigpage.h index 83d5d2cfaa..23b9087379 100644 --- a/debuggers/gdb/gdbconfigpage.h +++ b/debuggers/gdb/gdbconfigpage.h @@ -1,87 +1,87 @@ /* * GDB Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef GDBLAUNCHCONFIG #define GDBLAUNCHCONFIG #include #include #include class IExecutePlugin; namespace Ui { class GdbConfigPage; } namespace KDevelop { class OutputModel; class ILaunchConfiguration; class IProject; } namespace KDevMI { namespace GDB { class CppDebuggerPlugin; class DebugSession; } } class GdbConfigPageFactory : public KDevelop::LaunchConfigurationPageFactory { public: KDevelop::LaunchConfigurationPage* createWidget(QWidget* parent) override; }; class GdbConfigPage : public KDevelop::LaunchConfigurationPage { Q_OBJECT public: - GdbConfigPage( QWidget* parent = 0 ); + GdbConfigPage( QWidget* parent = nullptr ); ~GdbConfigPage() override; QIcon icon() const override; - void loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* = 0) override; - void saveToConfiguration(KConfigGroup, KDevelop::IProject* = 0 ) const override; + void loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* = nullptr) override; + void saveToConfiguration(KConfigGroup, KDevelop::IProject* = nullptr ) const override; QString title() const override; private: Ui::GdbConfigPage* ui; }; class GdbLauncher : public KDevelop::ILauncher { public: GdbLauncher( KDevMI::GDB::CppDebuggerPlugin* plugin, IExecutePlugin* execute ); QList< KDevelop::LaunchConfigurationPageFactory* > configPages() const override; QString description() const override; QString id() override; QString name() const override; KJob* start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) override; QStringList supportedModes() const override; private: QList factoryList; QPointer m_plugin; IExecutePlugin* m_execute; }; #endif diff --git a/debuggers/gdb/gdboutputwidget.cpp b/debuggers/gdb/gdboutputwidget.cpp index 966b0e3255..b0b02c2839 100644 --- a/debuggers/gdb/gdboutputwidget.cpp +++ b/debuggers/gdb/gdboutputwidget.cpp @@ -1,459 +1,459 @@ /* * GDB Debugger Support * * Copyright 2003 John Birch * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gdboutputwidget.h" #include "dbgglobal.h" #include "debuggerplugin.h" #include "debuglog.h" #include "debugsession.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevMI::GDB; /***************************************************************************/ GDBOutputWidget::GDBOutputWidget(CppDebuggerPlugin* plugin, QWidget *parent) : QWidget(parent), - m_userGDBCmdEditor(0), - m_Interrupt(0), - m_gdbView(0), + m_userGDBCmdEditor(nullptr), + m_Interrupt(nullptr), + m_gdbView(nullptr), showInternalCommands_(false), maxLines_(5000) { setWindowIcon(QIcon::fromTheme("dialog-scripts", windowIcon())); setWindowTitle(i18n("GDB Output")); setWhatsThis(i18n("GDB output

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

")); m_gdbView = new OutputTextEdit(this); m_gdbView->setReadOnly(true); m_userGDBCmdEditor = new KHistoryComboBox (this); QLabel *label = new QLabel(i18n("&GDB cmd:"), this); label->setBuddy(m_userGDBCmdEditor); m_Interrupt = new QToolButton( this ); m_Interrupt->setIcon ( QIcon::fromTheme( "media-playback-pause" ) ); m_Interrupt->setToolTip( i18n ( "Pause execution of the app to enter gdb commands" ) ); QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(m_gdbView); topLayout->setStretchFactor(m_gdbView, 1); topLayout->setMargin(0); QBoxLayout *userGDBCmdEntry = new QHBoxLayout(); userGDBCmdEntry->addWidget(label); userGDBCmdEntry->addWidget(m_userGDBCmdEditor); userGDBCmdEntry->setStretchFactor(m_userGDBCmdEditor, 1); userGDBCmdEntry->addWidget(m_Interrupt); topLayout->addLayout(userGDBCmdEntry); setLayout(topLayout); slotStateChanged(s_none, s_dbgNotStarted); connect(m_userGDBCmdEditor, static_cast(&KHistoryComboBox::returnPressed), this, &GDBOutputWidget::slotGDBCmd); connect(m_Interrupt, &QToolButton::clicked, this, &GDBOutputWidget::breakInto); updateTimer_.setSingleShot(true); connect(&updateTimer_, &QTimer::timeout, this, &GDBOutputWidget::flushPending); connect(KDevelop::ICore::self()->debugController(), &KDevelop::IDebugController::currentSessionChanged, this, &GDBOutputWidget::currentSessionChanged); connect(plugin, &CppDebuggerPlugin::reset, this, &GDBOutputWidget::clear); connect(plugin, &CppDebuggerPlugin::raiseDebuggerConsoleViews, this, &GDBOutputWidget::requestRaise); currentSessionChanged(KDevelop::ICore::self()->debugController()->currentSession()); // TODO Port to KF5 // connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), // this, SLOT(updateColors())); updateColors(); } void GDBOutputWidget::updateColors() { KColorScheme scheme(QPalette::Active); gdbColor_ = scheme.foreground(KColorScheme::LinkText).color(); errorColor_ = scheme.foreground(KColorScheme::NegativeText).color(); } void GDBOutputWidget::currentSessionChanged(KDevelop::IDebugSession* s) { if (!s) return; DebugSession *session = qobject_cast(s); if (!session) return; connect(this, &GDBOutputWidget::userGDBCmd, session, &DebugSession::addUserCommand); connect(this, &GDBOutputWidget::breakInto, session, &DebugSession::interruptDebugger); connect(session, &DebugSession::debuggerInternalCommandOutput, this, &GDBOutputWidget::slotInternalCommandStdout); connect(session, &DebugSession::debuggerUserCommandOutput, this, &GDBOutputWidget::slotUserCommandStdout); // debugger internal output, treat it as an internal command output connect(session, &DebugSession::debuggerInternalOutput, this, &GDBOutputWidget::slotInternalCommandStdout); connect(session, &DebugSession::debuggerStateChanged, this, &GDBOutputWidget::slotStateChanged); slotStateChanged(s_none, session->debuggerState()); } /***************************************************************************/ GDBOutputWidget::~GDBOutputWidget() { delete m_gdbView; delete m_userGDBCmdEditor; } /***************************************************************************/ void GDBOutputWidget::clear() { if (m_gdbView) m_gdbView->clear(); userCommands_.clear(); allCommands_.clear(); } /***************************************************************************/ void GDBOutputWidget::slotInternalCommandStdout(const QString& line) { newStdoutLine(line, true); } void GDBOutputWidget::slotUserCommandStdout(const QString& line) { qCDebug(DEBUGGERGDB) << "User command stdout: " << line; newStdoutLine(line, false); } namespace { QString colorify(QString text, const QColor& color) { // Make sure the newline is at the end of the newly-added // string. This is so that we can always correctly remove // newline inside 'flushPending'. if (!text.endsWith('\n')) text.append('\n'); if (text.endsWith('\n')) { text.remove(text.length()-1, 1); } text = "" + text + "
"; return text; } } void GDBOutputWidget::newStdoutLine(const QString& line, bool internal) { QString s = line.toHtmlEscaped(); if (s.startsWith("(gdb)")) { s = colorify(s, gdbColor_); } else s.replace('\n', "
"); allCommands_.append(s); allCommandsRaw_.append(line); trimList(allCommands_, maxLines_); trimList(allCommandsRaw_, maxLines_); if (!internal) { userCommands_.append(s); userCommandsRaw_.append(line); trimList(userCommands_, maxLines_); trimList(userCommandsRaw_, maxLines_); } if (!internal || showInternalCommands_) showLine(s); } void GDBOutputWidget::showLine(const QString& line) { pendingOutput_ += line; // To improve performance, we update the view after some delay. if (!updateTimer_.isActive()) { updateTimer_.start(100); } } void GDBOutputWidget::trimList(QStringList& l, int max_size) { int length = l.count(); if (length > max_size) { for(int to_delete = length - max_size; to_delete; --to_delete) { l.erase(l.begin()); } } } void GDBOutputWidget::setShowInternalCommands(bool show) { if (show != showInternalCommands_) { showInternalCommands_ = show; // Set of strings to show changes, text edit still has old // set. Refresh. m_gdbView->clear(); QStringList& newList = showInternalCommands_ ? allCommands_ : userCommands_; QStringList::iterator i = newList.begin(), e = newList.end(); for(; i != e; ++i) { // Note that color formatting is already applied to '*i'. showLine(*i); } } } /***************************************************************************/ void GDBOutputWidget::slotReceivedStderr(const char* line) { QString colored = colorify(QString::fromLatin1(line).toHtmlEscaped(), errorColor_); // Errors are shown inside user commands too. allCommands_.append(colored); trimList(allCommands_, maxLines_); userCommands_.append(colored); trimList(userCommands_, maxLines_); allCommandsRaw_.append(line); trimList(allCommandsRaw_, maxLines_); userCommandsRaw_.append(line); trimList(userCommandsRaw_, maxLines_); showLine(colored); } /***************************************************************************/ void GDBOutputWidget::slotGDBCmd() { QString GDBCmd(m_userGDBCmdEditor->currentText()); if (!GDBCmd.isEmpty()) { m_userGDBCmdEditor->addToHistory(GDBCmd); m_userGDBCmdEditor->clearEditText(); emit userGDBCmd(GDBCmd); } } void GDBOutputWidget::flushPending() { m_gdbView->setUpdatesEnabled(false); // QTextEdit adds newline after paragraph automatically. // So, remove trailing newline to avoid double newlines. if (pendingOutput_.endsWith('\n')) pendingOutput_.remove(pendingOutput_.length()-1, 1); Q_ASSERT(!pendingOutput_.endsWith('\n')); QTextDocument *document = m_gdbView->document(); QTextCursor cursor(document); cursor.movePosition(QTextCursor::End); cursor.insertHtml(pendingOutput_); pendingOutput_ = ""; m_gdbView->verticalScrollBar()->setValue(m_gdbView->verticalScrollBar()->maximum()); m_gdbView->setUpdatesEnabled(true); m_gdbView->update(); if (m_cmdEditorHadFocus) { m_userGDBCmdEditor->setFocus(); } } /***************************************************************************/ void GDBOutputWidget::slotStateChanged(KDevMI::DBGStateFlags oldStatus, KDevMI::DBGStateFlags newStatus) { Q_UNUSED(oldStatus) if (newStatus & s_dbgNotStarted) { m_Interrupt->setEnabled(false); m_userGDBCmdEditor->setEnabled(false); return; } else { m_Interrupt->setEnabled(true); } if (newStatus & s_dbgBusy) { if (m_userGDBCmdEditor->isEnabled()) { m_cmdEditorHadFocus = m_userGDBCmdEditor->hasFocus(); } m_userGDBCmdEditor->setEnabled(false); } else { m_userGDBCmdEditor->setEnabled(true); } } /***************************************************************************/ void GDBOutputWidget::focusInEvent(QFocusEvent */*e*/) { m_gdbView->verticalScrollBar()->setValue(m_gdbView->verticalScrollBar()->maximum()); m_userGDBCmdEditor->setFocus(); } void GDBOutputWidget::savePartialProjectSession() { KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); config.writeEntry("showInternalCommands", showInternalCommands_); } void GDBOutputWidget::restorePartialProjectSession() { KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); showInternalCommands_ = config.readEntry("showInternalCommands", false); } void GDBOutputWidget::contextMenuEvent(QContextMenuEvent * e) { QScopedPointer popup(new QMenu(this)); QAction* action = popup->addAction(i18n("Show Internal Commands"), this, SLOT(toggleShowInternalCommands())); action->setCheckable(true); action->setChecked(showInternalCommands_); action->setWhatsThis(i18n( "Controls if commands issued internally by KDevelop " "will be shown or not.
" "This option will affect only future commands, it will not " "add or remove already issued commands from the view.")); popup->addAction(i18n("Copy All"), this, SLOT(copyAll())); popup->exec(e->globalPos()); } void GDBOutputWidget::copyAll() { /* See comments for allCommandRaw_ for explanations of this complex logic, as opposed to calling text(). */ const QStringList& raw = showInternalCommands_ ? allCommandsRaw_ : userCommandsRaw_; QString text; for (int i = 0; i < raw.size(); ++i) text += raw.at(i); // Make sure the text is pastable both with Ctrl-C and with // middle click. QApplication::clipboard()->setText(text, QClipboard::Clipboard); QApplication::clipboard()->setText(text, QClipboard::Selection); } void GDBOutputWidget::toggleShowInternalCommands() { setShowInternalCommands(!showInternalCommands_); } OutputTextEdit::OutputTextEdit(GDBOutputWidget * parent) : QTextEdit(parent) { } void OutputTextEdit::contextMenuEvent(QContextMenuEvent * event) { QMenu* popup = createStandardContextMenu(); QAction* action = popup->addAction(i18n("Show Internal Commands"), parent(), SLOT(toggleShowInternalCommands())); action->setCheckable(true); action->setChecked(static_cast(parent())->showInternalCommands()); action->setWhatsThis(i18n( "Controls if commands issued internally by KDevelop " "will be shown or not.
" "This option will affect only future commands, it will not " "add or remove already issued commands from the view.")); popup->exec(event->globalPos()); } bool GDBOutputWidget::showInternalCommands() const { return showInternalCommands_; } diff --git a/debuggers/gdb/gdboutputwidget.h b/debuggers/gdb/gdboutputwidget.h index 26aba0b2cd..87edf11f49 100644 --- a/debuggers/gdb/gdboutputwidget.h +++ b/debuggers/gdb/gdboutputwidget.h @@ -1,157 +1,157 @@ /* * GDB Debugger Support * * Copyright 2003 John Birch * Copyright 2007 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _GDBOUTPUTWIDGET_H_ #define _GDBOUTPUTWIDGET_H_ #include "dbgglobal.h" #include #include #include #include #include namespace KDevelop { class IDebugSession; } class KHistoryComboBox; class QTextEdit; class QToolButton; namespace KDevMI { namespace GDB { class GDBController; class CppDebuggerPlugin; class GDBOutputWidget : public QWidget { Q_OBJECT public: - GDBOutputWidget(CppDebuggerPlugin* plugin, QWidget *parent=0 ); + GDBOutputWidget(CppDebuggerPlugin* plugin, QWidget *parent=nullptr ); ~GDBOutputWidget() override; void savePartialProjectSession(); void restorePartialProjectSession(); bool showInternalCommands() const; public Q_SLOTS: void clear(); void slotInternalCommandStdout(const QString& line); void slotUserCommandStdout(const QString& line); void slotReceivedStderr(const char* line); void slotStateChanged(DBGStateFlags oldStatus, DBGStateFlags newStatus); void slotGDBCmd(); void flushPending(); void copyAll(); void toggleShowInternalCommands(); private Q_SLOTS: void currentSessionChanged(KDevelop::IDebugSession *session); void updateColors(); protected: void focusInEvent(QFocusEvent *e) override; void contextMenuEvent(QContextMenuEvent* e) override; Q_SIGNALS: void requestRaise(); void userGDBCmd(const QString &cmd); void breakInto(); private: void newStdoutLine(const QString& line, bool internal); /** Arranges for 'line' to be shown to the user. Adds 'line' to pendingOutput_ and makes sure updateTimer_ is running. */ void showLine(const QString& line); /** Makes 'l' no longer than 'max_size' by removing excessive elements from the top. */ void trimList(QStringList& l, int max_size); GDBController* m_controller; KHistoryComboBox* m_userGDBCmdEditor; QToolButton* m_Interrupt; QTextEdit* m_gdbView; bool m_cmdEditorHadFocus; void setShowInternalCommands(bool); friend class OutputText; /** The output from user commands only and from all commands. We keep it here so that if we switch "Show internal commands" on, we can show previous internal commands. */ QStringList userCommands_, allCommands_; /** Same output, without any fancy formatting. Keeping it here because I can't find any way to extract raw text, without formatting, out of QTextEdit except for selecting everything and calling 'copy()'. The latter approach is just ugly. */ QStringList userCommandsRaw_, allCommandsRaw_; /** For performance reasons, we don't immediately add new text to QTExtEdit. Instead we add it to pendingOutput_ and flush it on timer. */ QString pendingOutput_; QTimer updateTimer_; bool showInternalCommands_; int maxLines_; QColor gdbColor_; QColor errorColor_; }; class OutputTextEdit : public QTextEdit { Q_OBJECT public: OutputTextEdit(GDBOutputWidget* parent); protected: void contextMenuEvent(QContextMenuEvent* event) override; }; } // end of namespace GDB } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/unittests/debugeecrash.cpp b/debuggers/gdb/unittests/debugeecrash.cpp index 876d3a27f2..caee580ae1 100644 --- a/debuggers/gdb/unittests/debugeecrash.cpp +++ b/debuggers/gdb/unittests/debugeecrash.cpp @@ -1,31 +1,31 @@ /* Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include void foo() { int j = 0; j++; j++; - int *i=0; + int *i=nullptr; *i = 0; } int main() { std::cout << "Hello, world!" << std::endl; foo(); return 0; } diff --git a/debuggers/gdb/unittests/test_gdb.cpp b/debuggers/gdb/unittests/test_gdb.cpp index d8438f1b56..2c8b94ecd3 100644 --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/gdb/unittests/test_gdb.cpp @@ -1,2113 +1,2113 @@ /* Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_gdb.h" #include "debugsession.h" #include "gdbframestackmodel.h" #include "mi/micommand.h" #include "mi/milexer.h" #include "mi/miparser.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 using KDevelop::AutoTestShell; namespace KDevMI { namespace GDB { QUrl findExecutable(const QString& name) { QFileInfo info(qApp->applicationDirPath() + "/unittests/" + name); Q_ASSERT(info.exists()); Q_ASSERT(info.isExecutable()); return QUrl::fromLocalFile(info.canonicalFilePath()); } QString findSourceFile(const QString& name) { QFileInfo info(QFileInfo(__FILE__).dir().absoluteFilePath(name)); Q_ASSERT(info.exists()); return info.canonicalFilePath(); } static bool isAttachForbidden(const char * file, int line) { // if on linux, ensure we can actually attach QFile canRun("/proc/sys/kernel/yama/ptrace_scope"); if (canRun.exists()) { if (!canRun.open(QIODevice::ReadOnly)) { QTest::qFail("Something is wrong: /proc/sys/kernel/yama/ptrace_scope exists but cannot be read", file, line); return true; } if (canRun.read(1).toInt() != 0) { QTest::qSkip("ptrace attaching not allowed, skipping test. To enable it, set /proc/sys/kernel/yama/ptrace_scope to 0.", file, line); return true; } } return false; } #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) void GdbTest::initTestCase() { AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); m_iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute")->extension(); Q_ASSERT(m_iface); } void GdbTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void GdbTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup breakpoints = KSharedConfig::openConfig()->group("breakpoints"); breakpoints.writeEntry("number", 0); breakpoints.sync(); KDevelop::BreakpointModel* m = KDevelop::ICore::self()->debugController()->breakpointModel(); m->removeRows(0, m->rowCount()); KDevelop::VariableCollection *vc = KDevelop::ICore::self()->debugController()->variableCollection(); for (int i=0; i < vc->watches()->childCount(); ++i) { delete vc->watches()->child(i); } vc->watches()->clear(); } class WritableEnvironmentGroupList : public KDevelop::EnvironmentGroupList { public: explicit WritableEnvironmentGroupList(KConfig* config) : EnvironmentGroupList(config) {} using EnvironmentGroupList::variables; using EnvironmentGroupList::saveSettings; using EnvironmentGroupList::removeGroup; }; class TestLaunchConfiguration : public KDevelop::ILaunchConfiguration { public: TestLaunchConfiguration(const QUrl& executable = findExecutable("debugee"), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = new KConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; QString name() const override { return QString("Test-Launch"); } - KDevelop::IProject* project() const override { return 0; } - KDevelop::LaunchConfigurationType* type() const override { return 0; } + KDevelop::IProject* project() const override { return nullptr; } + KDevelop::LaunchConfigurationType* type() const override { return nullptr; } KConfig *rootConfig() { return c; } private: KConfigGroup cfg; KConfig *c; }; class TestFrameStackModel : public GdbFrameStackModel { public: TestFrameStackModel(DebugSession* session) : GdbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} int fetchFramesCalled; int fetchThreadsCalled; void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; GdbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; GdbFrameStackModel::fetchThreads(); } }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { setSourceInitFile(false); setAutoDisableASLR(false); m_frameStackModel = new TestFrameStackModel(this); KDevelop::ICore::self()->debugController()->addSession(this); } QUrl url() { return currentUrl(); } int line() { return currentLine(); } TestFrameStackModel* frameStackModel() const override { return m_frameStackModel; } private: TestFrameStackModel* m_frameStackModel; }; class TestWaiter { public: TestWaiter(DebugSession * session_, const char * condition_, const char * file_, int line_) : session(session_) , condition(condition_) , file(file_) , line(line_) { stopWatch.start(); } bool waitUnless(bool ok) { if (ok) { qDebug() << "Condition " << condition << " reached in " << file << ':' << line; return false; } if (stopWatch.elapsed() > 5000) { QTest::qFail(qPrintable(QString("Timeout before reaching condition %0").arg(condition)), file, line); return false; } QTest::qWait(100); if (!session) { QTest::qFail(qPrintable(QString("Session ended without reaching condition %0").arg(condition)), file, line); return false; } return true; } private: QTime stopWatch; QPointer session; const char * condition; const char * file; int line; }; #define WAIT_FOR_STATE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ do { if(!compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) bool compareData(QModelIndex index, QString expected, const char *file, int line) { QString s = index.model()->data(index, Qt::DisplayRole).toString(); if (s != expected) { QTest::qFail(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3") .arg(s).arg(expected).arg(file).arg(line)), file, line); return false; } return true; } static const QString debugeeFileName = findSourceFile("debugee.cpp"); KDevelop::BreakpointModel* breakpoints() { return KDevelop::ICore::self()->debugController()->breakpointModel(); } void GdbTest::testStdOut() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); { QCOMPARE(outputSpy.count(), 1); QList arguments = outputSpy.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.first().toStringList(), QStringList() << "Hello, world!" << "Hello"); } } void GdbTest::testEnvironmentSet() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeechoenv")); cfg.config().writeEntry("EnvironmentGroup", "GdbTestGroup"); WritableEnvironmentGroupList envGroups(cfg.rootConfig()); envGroups.removeGroup("GdbTestGroup"); auto &envs = envGroups.variables("GdbTestGroup"); envs["VariableA"] = "-A' \" complex --value"; envs["VariableB"] = "-B' \" complex --value"; envGroups.saveSettings(cfg.rootConfig()); QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" << "-B' \" complex --value"); } void GdbTest::testBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void GdbTest::testDisableBreakpoint() { //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(true); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added KDevelop::Breakpoint * thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), thirdBreakLine); KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(false); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), fourthBreakLine); QTest::qWait(300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QTest::qWait(300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeLocationBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 27); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); QTest::qWait(100); b->setLine(28); QTest::qWait(100); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 28); QTest::qWait(500); breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(debugeeFileName+":30")); QCOMPARE(b->line(), 29); QTest::qWait(100); QCOMPARE(b->line(), 29); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDeleteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPendingBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_gdb.cpp")), 10); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testUpdateBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; // breakpoint 1: line 29 KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); session->startDebugging(&cfg, m_iface); // breakpoint 2: line 28 //insert custom command as user might do it using GDB console session->addCommand(new MI::UserCommand(MI::NonMI, "break "+debugeeFileName+":28")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 28 session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(b->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); // stop at line 29 session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testIgnoreHitsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startDebugging(&cfg, m_iface); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 39); b->setCondition("x[0] == 'H'"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 23); b->setCondition("i==2"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->line() == 24); b->setCondition("i == 0"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addWatchpoint("i"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteWithConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint("i"); b->setCondition("i==2"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnReadBreakpoint() { /* test disabled because of gdb bug: http://sourceware.org/bugzilla/show_bug.cgi?id=10136 TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addReadWatchpoint("foo::i"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); */ } void GdbTest::testBreakOnReadBreakpoint2() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addReadWatchpoint("i"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnAccessBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addAccessWatchpoint("i"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunningMultiple() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); KDevelop::Breakpoint *b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointFunctionName() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(MI::NonMI, "break debugee.cpp:23"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 1); KDevelop::Breakpoint* b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); session->addCommand(MI::NonMI, "disable 2"); session->addCommand(MI::NonMI, "condition 2 i == 1"); session->addCommand(MI::NonMI, "ignore 2 1"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->enabled(), false); QCOMPARE(b->condition(), QString("i == 1")); QCOMPARE(b->ignoreHits(), 1); session->addCommand(MI::NonMI, "delete 2"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testShowStepInSource() { TestDebugSession *session = new TestDebugSession; QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 22); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 23); } } void GdbTest::testStack() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 2); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":23"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "main"); COMPARE_DATA(tIdx.child(1, 2), debugeeFileName+":29"); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackFetchMore() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeerecursion")); QString fileName = findSourceFile("debugeerecursion.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo"); COMPARE_DATA(tIdx.child(0, 2), fileName+":26"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "foo"); COMPARE_DATA(tIdx.child(1, 2), fileName+":24"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "foo"); COMPARE_DATA(tIdx.child(2, 2), fileName+":24"); COMPARE_DATA(tIdx.child(19, 0), "19"); COMPARE_DATA(tIdx.child(20, 0), "20"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(tIdx.child(20, 0), "20"); COMPARE_DATA(tIdx.child(21, 0), "21"); COMPARE_DATA(tIdx.child(22, 0), "22"); COMPARE_DATA(tIdx.child(39, 0), "39"); COMPARE_DATA(tIdx.child(40, 0), "40"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(tIdx.child(40, 0), "40"); COMPARE_DATA(tIdx.child(41, 0), "41"); COMPARE_DATA(tIdx.child(42, 0), "42"); COMPARE_DATA(tIdx.child(119, 0), "119"); COMPARE_DATA(tIdx.child(120, 0), "120"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(tIdx.child(120, 0), "120"); COMPARE_DATA(tIdx.child(121, 0), "121"); COMPARE_DATA(tIdx.child(122, 0), "122"); COMPARE_DATA(tIdx.child(300, 0), "300"); COMPARE_DATA(tIdx.child(300, 1), "main"); COMPARE_DATA(tIdx.child(300, 2), fileName+":30"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackDeactivateAndActive() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(200); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackSwitchThread() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), fileName+":39"); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); QTest::qWait(200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << "nice" << findExecutable("debugeeslow").toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); TestDebugSession *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); WAIT_FOR_STATE(session, DebugSession::PausedState); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 34); QTest::qWait(100); session->run(); QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::PausedState); if (session->line() < 34 || session->line() < 35) { QCOMPARE(session->line(), 34); } session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << "nice" << findExecutable("debugeeslow").toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(findSourceFile("gdb_script_empty"))); QVERIFY(session->startDebugging(&cfg, m_iface)); session->addCommand(MI::NonMI, QString("attach %0").arg(debugeeProcess.pid())); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); QTest::qWait(2000); // give the slow inferior some extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCoreFile() { QFile f("core"); if (f.exists()) f.remove(); KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << "bash" << "-c" << "ulimit -c unlimited; " + findExecutable("debugeecrash").toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); bool coreFileFound = QFile::exists("core"); if (!coreFileFound) { // Try to use coredumpctl auto coredumpctl = QStandardPaths::findExecutable("coredumpctl"); if (!coredumpctl.isEmpty()) { QFileInfo fi("core"); KProcess::execute(coredumpctl, {"-1", "-o", fi.absoluteFilePath(), "dump", "debugeecrash"}); coreFileFound = fi.exists(); } } if (!coreFileFound) QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); TestDebugSession *session = new TestDebugSession; session->examineCoreFile(findExecutable("debugeecrash"), QUrl::fromLocalFile(QDir::currentPath()+"/core")); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } KDevelop::VariableCollection *variableCollection() { return KDevelop::ICore::self()->debugController()->variableCollection(); } void GdbTest::testVariablesLocals() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QCOMPARE(variableCollection()->rowCount(), 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "0"); session->run(); QTest::qWait(1000); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesLocalsStruct() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == "ts") { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatches() { TestDebugSession *session = new TestDebugSession; KDevelop::ICore::self()->debugController()->variableCollection()->variableWidgetShown(); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesQuotes() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; // the unquoted string (the actual content): t\"t // quoted string (what we would write as a c string): "t\\\"t" // written in source file: R"("t\\\"t")" const QString testString("t\\\"t"); // the actual content const QString quotedTestString(R"("t\\\"t")"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); COMPARE_DATA(variableCollection()->index(0, 1, i), "[" + QString::number(testString.length() + 1) + "]"); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); QTest::qWait(100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QString::number(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '"; if (c == '\\') value += "\\\\"; else if (c == '\'') value += "\\'"; else value += c; value += "'"; COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QString::number(len)); COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\000'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesTwoSessions() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); QTest::qWait(300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); KDevelop::Variable* v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(v->inScope()); QCOMPARE(v->data(1, Qt::DisplayRole).toString(), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } void GdbTest::testVariablesStopDebugger() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stopDebugger(); QTest::qWait(300); } void GdbTest::testVariablesStartSecondSession() { QPointer session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QPointer session2 = new TestDebugSession; session2->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session2->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session2, DebugSession::PausedState); session2->run(); WAIT_FOR_STATE(session2, DebugSession::EndedState); } void GdbTest::testVariablesSwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); QTest::qWait(200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesQuicklySwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); QTest::qWait(300); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(1); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSegfaultDebugee() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable("debugeecrash")); QString fileName = findSourceFile("debugeecrash.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSwitchFrameGdbConsole() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); QTest::qWait(500); QCOMPARE(stackModel->currentFrame(), 1); session->addUserCommand("print x"); QTest::qWait(500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); } //Bug 201771 void GdbTest::testInsertAndRemoveBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); b->setDeleted(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 274390 void GdbTest::testCommandOrderFastStepping() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeqt")); breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); for(int i=0; i<20; i++) { session->stepInto(); } WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupManuallyInsertedBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); session->addCommand(MI::NonMI, "break debugee.cpp:32"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); //wait for breakpoints update QCOMPARE(breakpoints()->breakpoints().count(), 2); QCOMPARE(breakpoints()->rowCount(), 2); KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); QVERIFY(b); QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 QCOMPARE(b->url().fileName(), QString("debugee.cpp")); } //Bug 270970 void GdbTest::testPickupManuallyInsertedBreakpointOnlyOnce() { TestDebugSession *session = new TestDebugSession; //inject here, so it behaves similar like a command from .gdbinit QTemporaryFile configScript; configScript.open(); configScript.write(QString("file %0\n").arg(findExecutable("debugee").toLocalFile()).toLocal8Bit()); configScript.write("break debugee.cpp:32\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile("debugee.cpp"), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupCatchThrowOnlyOnce() { QTemporaryFile configScript; configScript.open(); configScript.write("catch throw\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); for (int i = 0; i < 2; ++i) { TestDebugSession* session = new TestDebugSession; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::EndedState); } QCOMPARE(breakpoints()->rowCount(), 1); //one from kdevelop, one from runScript } void GdbTest::testRunGdbScript() { TestDebugSession *session = new TestDebugSession; QTemporaryFile runScript; runScript.open(); runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); runScript.write("break main\n"); runScript.write("run\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRemoteDebug() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpoint() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + '\n'); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpointPickupOnlyOnce() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 "+findExecutable("debugee").toLocalFile().toLatin1()+"\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file "+findExecutable("debugee").toLocalFile().toLatin1()+"\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //************************** second session session = new TestDebugSession; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testBreakpointWithSpaceInPath() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeespace")); KConfigGroup grp = cfg.config(); QString fileName = findSourceFile("debugee space.cpp"); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 20); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakpointDisabledOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28) ->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 31); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 31); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCatchpoint() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable("debugeeexception")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp")), 29); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); TestFrameStackModel* fsModel = session->frameStackModel(); QCOMPARE(fsModel->currentFrame(), 0); QCOMPARE(session->line(), 29); session->addCommand(MI::NonMI, "catch throw"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); const QList frames = fsModel->frames(fsModel->currentThread()); QVERIFY(frames.size() >= 2); // frame 0 is somewhere inside libstdc++ QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); QCOMPARE(frames[1].line, 22); QCOMPARE(breakpoints()->rowCount(),2); QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testThreadAndFrameInfo() { // Check if --thread is added to user commands TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QSignalSpy outputSpy(session, &TestDebugSession::debuggerUserCommandOutput); session->addCommand(new MI::UserCommand(MI::ThreadInfo,"")); session->addCommand(new MI::UserCommand(MI::StackListLocals, QLatin1String("0"))); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // wait for command finish // outputs should be // 1. -thread-info // 2. ^done for thread-info // 3. -stack-list-locals // 4. ^done for -stack-list-locals QCOMPARE(outputSpy.count(), 4); QVERIFY(outputSpy.at(2).at(0).toString().contains(QLatin1String("--thread 1"))); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::parseBug304730() { MI::FileSymbol file; file.contents = QByteArray("^done,bkpt={" "number=\"1\",type=\"breakpoint\",disp=\"keep\",enabled=\"y\",addr=\"\",times=\"0\"," "original-location=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp:231\"}," "{number=\"1.1\",enabled=\"y\",addr=\"0x081d84aa\"," "func=\"PatchMatch, 2u> >" "::Propagation(ForwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.2\",enabled=\"y\",addr=\"0x081d8ae2\"," "func=\"PatchMatch, 2u> >" "::Propagation(BackwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.3\",enabled=\"y\",addr=\"0x081d911a\"," "func=\"PatchMatch, 2u> >" "::Propagation(AllowedPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}"); MI::MIParser parser; std::unique_ptr record(parser.parse(&file)); QVERIFY(record.get() != nullptr); } void GdbTest::testMultipleLocationsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("aPlusB"); //TODO check if the additional location breakpoint is added session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 19); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBug301287() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("argc"); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testMultipleBreakpoint() { TestDebugSession *session = new TestDebugSession; //there'll be about 3-4 breakpoints, but we treat it like one. TestLaunchConfiguration c(findExecutable("debugeemultiplebreakpoint")); KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint("debugeemultiplebreakpoint.cpp:52"); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRegularExpressionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable("debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("main"); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, "rbreak .*aPl.*B"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); session->addCommand(MI::BreakDelete, ""); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable("debugeeslow")); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("debugeeslow.cpp:25"); session->startDebugging(&c, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); session->run(); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //to make one loop QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; foreach (const QString & console, QStringList() << "konsole" << "xterm" << "xfce4-terminal" << "gnome-terminal") { - TestDebugSession* session = 0; + TestDebugSession* session = nullptr; if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } // see: https://bugs.kde.org/show_bug.cgi?id=339231 void GdbTest::testPathWithSpace() { #ifdef HAVE_PATH_WITH_SPACES_TEST TestDebugSession* session = new TestDebugSession; auto debugee = findExecutable("path with space/spacedebugee"); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("spacedebugee.cpp:30"); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); #endif } bool GdbTest::waitForState(DebugSession *session, DebugSession::DebuggerState state, const char *file, int line, bool waitForIdle) { QPointer s(session); //session can get deleted in DebugController QTime stopWatch; stopWatch.start(); // legacy behavior for tests that implicitly may require waiting for idle, // but which were written before waitForIdle was added waitForIdle = waitForIdle || state != MIDebugSession::EndedState; while (s && (s->state() != state || (waitForIdle && s->debuggerStateIsOn(s_dbgBusy)))) { if (stopWatch.elapsed() > 5000) { qWarning() << "current state" << s->state() << "waiting for" << state; QTest::qFail(qPrintable(QString("Timeout before reaching state %0").arg(state)), file, line); return false; } QTest::qWait(20); } // NOTE: don't wait anymore after leaving the loop. Waiting re-enters event loop and // may change session state. if (!s && state != MIDebugSession::EndedState) { QTest::qFail(qPrintable(QString("Session ended before reaching state %0").arg(state)), file, line); return false; } qDebug() << "Reached state " << state << " in " << file << ':' << line; return true; } } // end of namespace GDB } // end of namespace KDevMI QTEST_MAIN(KDevMI::GDB::GdbTest) #include "test_gdb.moc" #include "moc_test_gdb.cpp" diff --git a/debuggers/lldb/lldbdebugger.h b/debuggers/lldb/lldbdebugger.h index 95e46af80f..003524628e 100644 --- a/debuggers/lldb/lldbdebugger.h +++ b/debuggers/lldb/lldbdebugger.h @@ -1,46 +1,46 @@ /* * Low level LLDB interface * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef LLDBDEBUGGER_H #define LLDBDEBUGGER_H #include "midebugger.h" namespace KDevMI { namespace LLDB { class LldbDebugger : public MIDebugger { Q_OBJECT public: - explicit LldbDebugger(QObject* parent = 0); + explicit LldbDebugger(QObject* parent = nullptr); ~LldbDebugger() override; bool start(KConfigGroup& config, const QStringList& extraArguments = {}) override; private: bool checkVersion(); }; } // end of namespace LLDB } // end of namespace KDevMI #endif // LLDBDEBUGGER_H diff --git a/debuggers/lldb/unittests/debugees/debugeecrash.cpp b/debuggers/lldb/unittests/debugees/debugeecrash.cpp index 876d3a27f2..caee580ae1 100644 --- a/debuggers/lldb/unittests/debugees/debugeecrash.cpp +++ b/debuggers/lldb/unittests/debugees/debugeecrash.cpp @@ -1,31 +1,31 @@ /* Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include void foo() { int j = 0; j++; j++; - int *i=0; + int *i=nullptr; *i = 0; } int main() { std::cout << "Hello, world!" << std::endl; foo(); return 0; } diff --git a/debuggers/lldb/unittests/test_lldb.cpp b/debuggers/lldb/unittests/test_lldb.cpp index bb5a4e59c7..0f7b62347b 100644 --- a/debuggers/lldb/unittests/test_lldb.cpp +++ b/debuggers/lldb/unittests/test_lldb.cpp @@ -1,1904 +1,1904 @@ /* * Unit tests for LLDB debugger plugin Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov * 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 "test_lldb.h" #include "controllers/framestackmodel.h" #include "debugsession.h" #include "testhelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WAIT_FOR_STATE(session, state) \ do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR_A_WHILE(session, ms) \ do { if (!KDevMI::LLDB::waitForAWhile((session), (ms), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ KDevMI::LLDB::TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ do { if (!KDevMI::LLDB::compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) #define findSourceFile(name) \ findSourceFile(__FILE__, (name)) using namespace KDevelop; using namespace KDevMI::LLDB; namespace { class WritableEnvironmentGroupList : public EnvironmentGroupList { public: explicit WritableEnvironmentGroupList(KConfig* config) : EnvironmentGroupList(config) {} using EnvironmentGroupList::variables; using EnvironmentGroupList::saveSettings; using EnvironmentGroupList::removeGroup; }; class TestLaunchConfiguration : public ILaunchConfiguration { public: TestLaunchConfiguration(const QUrl& executable = findExecutable("lldb_debugee"), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = new KConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; QString name() const override { return QString("Test-Launch"); } - KDevelop::IProject* project() const override { return 0; } - KDevelop::LaunchConfigurationType* type() const override { return 0; } + KDevelop::IProject* project() const override { return nullptr; } + KDevelop::LaunchConfigurationType* type() const override { return nullptr; } KConfig *rootConfig() { return c; } private: KConfigGroup cfg; KConfig *c; }; class TestFrameStackModel : public LldbFrameStackModel { public: TestFrameStackModel(DebugSession* session) : LldbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; LldbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; LldbFrameStackModel::fetchThreads(); } int fetchFramesCalled; int fetchThreadsCalled; }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { setSourceInitFile(false); m_frameStackModel = new TestFrameStackModel(this); setFormatterPath(findSourceFile("../formatters/all.py")); KDevelop::ICore::self()->debugController()->addSession(this); } TestFrameStackModel* frameStackModel() const override { return m_frameStackModel; } private: TestFrameStackModel* m_frameStackModel; }; } // end of anonymous namespace BreakpointModel* LldbTest::breakpoints() { return m_core->debugController()->breakpointModel(); } VariableCollection *LldbTest::variableCollection() { return m_core->debugController()->variableCollection(); } Variable *LldbTest::watchVariableAt(int i) { auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); auto idx = variableCollection()->index(i, 0, watchRoot); return dynamic_cast(variableCollection()->itemForIndex(idx)); } QModelIndex LldbTest::localVariableIndexAt(int i, int col) { auto localRoot = variableCollection()->indexForItem(variableCollection()->locals(), 0); return variableCollection()->index(i, col, localRoot); } // Called before the first testfunction is executed void LldbTest::initTestCase() { AutoTestShell::init(); m_core = TestCore::initialize(Core::NoUi); m_iface = m_core->pluginController() ->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute") ->extension(); Q_ASSERT(m_iface); m_debugeeFileName = findSourceFile("debugee.cpp"); } // Called after the last testfunction was executed void LldbTest::cleanupTestCase() { TestCore::shutdown(); } // Called before each testfunction is executed void LldbTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup bpCfg = KSharedConfig::openConfig()->group("breakpoints"); bpCfg.writeEntry("number", 0); bpCfg.sync(); breakpoints()->removeRows(0, breakpoints()->rowCount()); while (variableCollection()->watches()->childCount() > 0) { auto var = watchVariableAt(0); if (!var) break; var->die(); } } void LldbTest::cleanup() { // Called after every testfunction } void LldbTest::testStdout() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "Hello, world!" << "Hello"); } void LldbTest::testEnvironmentSet() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeeechoenv")); cfg.config().writeEntry("EnvironmentGroup", "LldbTestGroup"); WritableEnvironmentGroupList envGroups(cfg.rootConfig()); envGroups.removeGroup("LldbTestGroup"); auto &envs = envGroups.variables("LldbTestGroup"); envs["VariableA"] = "-A' \" complex --value"; envs["VariableB"] = "-B' \" complex --value"; envGroups.saveSettings(cfg.rootConfig()); QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" << "-B' \" complex --value"); } void LldbTest::testBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); QCOMPARE(session->currentLine(), 29); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void LldbTest::testBreakOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(KDevMI::Config::BreakOnStartEntry, true); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); // line 28 is the start of main function in debugee.cpp QCOMPARE(session->currentLine(), 27); // currentLine is zero-based session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDisableBreakpoint() { QSKIP("Skipping... In lldb-mi -d flag has no effect when mixed with -f"); //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. m_core->debugController()->breakpointModel()->blockSignals(true); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added auto *thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), thirdBreakLine); m_core->debugController()->breakpointModel()->blockSignals(false); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), fourthBreakLine); WAIT_FOR_A_WHILE(session, 300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); WAIT_FOR_A_WHILE(session, 300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testChangeLocationBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; auto *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 27); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); WAIT_FOR_A_WHILE(session, 100); b->setLine(28); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 28); WAIT_FOR_A_WHILE(session, 500); breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(m_debugeeFileName+":30")); QCOMPARE(b->line(), 29); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(b->line(), 29); session->run(); WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDeleteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testPendingBreakpoint() { QSKIP("Skipping... Pending breakpoint not work on lldb-mi"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); auto * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_lldb.cpp")), 10); QCOMPARE(b->state(), Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testUpdateBreakpoint() { // Description: user might insert breakpoints using lldb console. model should // pick up the manually set breakpoint TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; // break at line 29 auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 29 session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step QCOMPARE(session->currentLine(), 23-1); // at the begining of foo():23: ++i; session->addUserCommand(QString("break set --file %1 --line %2").arg(m_debugeeFileName).arg(33)); WAIT_FOR_A_WHILE(session, 20); QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(b->line(), 33-1); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 25 QCOMPARE(session->currentLine(), 33-1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testIgnoreHitsBreakpoint() { QSKIP("Skipping... lldb-mi doesn't provide breakpoint hit count update"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 39); b->setCondition("x[0] == 'H'"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setCondition("i==2"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->currentLine() == 24); b->setCondition("i == 0"); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnWriteBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addWatchpoint("i"); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnWriteWithConditionBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint("i"); b->setCondition("i==2"); QTest::qWait(100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnReadBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addReadWatchpoint("foo::i"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnReadBreakpoint2() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addReadWatchpoint("i"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 22); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnAccessBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addAccessWatchpoint("i"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 22); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 500); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointWhileRunningMultiple() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 2000); qDebug() << "adding breakpoint"; auto b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); auto b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 500); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 25); b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointFunctionName() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testManualBreakpoint() { QSKIP("Skipping... lldb-mi output malformated response which breaks this"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(MI::NonMI, "break set --file debugee.cpp --line 23"); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 1); auto b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); session->addCommand(MI::NonMI, "break disable 2"); session->addCommand(MI::NonMI, "break modify -c 'i == 1' 2"); session->addCommand(MI::NonMI, "break modify -i 1 2"); WAIT_FOR_A_WHILE(session, 1000); QCOMPARE(b->enabled(), false); QCOMPARE(b->condition(), QString("i == 1")); QCOMPARE(b->ignoreHits(), 1); session->addCommand(MI::NonMI, "break delete 2"); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 201771 void LldbTest::testInsertAndRemoveBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 1000); KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 200); // wait for feedback notification from lldb-mi b->setDeleted(); WAIT_FOR_A_WHILE(session, 3000); // give slow debugee extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testPickupManuallyInsertedBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, "break set --file debugee.cpp --line 32"); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 2); QCOMPARE(breakpoints()->rowCount(), 2); KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); QVERIFY(b); QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 QCOMPARE(b->url().fileName(), QString("debugee.cpp")); } //Bug 270970 void LldbTest::testPickupManuallyInsertedBreakpointOnlyOnce() { TestDebugSession *session = new TestDebugSession; QString sourceFile = findSourceFile("debugee.cpp"); //inject here, so it behaves similar like a command from .lldbinit QTemporaryFile configScript; configScript.open(); configScript.write(QString("break set --file %0 --line 32\n").arg(sourceFile).toLocal8Bit()); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile("debugee.cpp"), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakpointWithSpaceInPath() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeespace")); KConfigGroup grp = cfg.config(); QString fileName = findSourceFile("debugee space.cpp"); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 20); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakpointDisabledOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 29); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 34); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testMultipleLocationsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("aPlusB"); //TODO check if the additional location breakpoint is added QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 19); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testMultipleBreakpoint() { TestDebugSession *session = new TestDebugSession; //there'll be about 3-4 breakpoints, but we treat it like one. TestLaunchConfiguration c(findExecutable("lldb_debugeemultiplebreakpoint")); auto b = breakpoints()->addCodeBreakpoint("debugeemultiplebreakpoint.cpp:52"); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRegularExpressionBreakpoint() { QSKIP("Skipping... lldb has only one breakpoint for multiple locations" " (and lldb-mi returns the first one), not support this yet"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable("lldb_debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, "break set --func-regex .*aPl.*B"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); session->addCommand(MI::BreakDelete, ""); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testChangeBreakpointWhileRunning() { QSKIP("Skipping... lldb-mi command -break-enable doesn't enable breakpoint"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable("lldb_debugeeslow")); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("debugeeslow.cpp:25"); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); session->run(); WAIT_FOR_STATE(session, DebugSession::ActiveState); qDebug() << "Disabling breakpoint"; b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //to make one loop WAIT_FOR_A_WHILE(session, 2500); qDebug() << "Waiting for active"; WAIT_FOR_STATE(session, DebugSession::ActiveState); qDebug() << "Enabling breakpoint"; // Use native user command works, but not through -break-enable, which is triggered by setData session->addCommand(MI::NonMI, "break enable"); //b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testCatchpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeeexception")); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel* fsModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp")), 29); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(fsModel->currentFrame(), 0); QCOMPARE(session->currentLine(), 29); session->addCommand(MI::NonMI, "break set -E c++"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); const auto frames = fsModel->frames(fsModel->currentThread()); QVERIFY(frames.size() >= 2); // frame 0 is somewhere inside libstdc++ QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); QCOMPARE(frames[1].line, 22); QCOMPARE(breakpoints()->rowCount(),2); QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testShowStepInSource() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 22); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 23); } } void LldbTest::testStack() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); QCOMPARE(stackModel->rowCount(tIdx), 4); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo()"); COMPARE_DATA(tIdx.child(0, 2), m_debugeeFileName+":23"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "main"); COMPARE_DATA(tIdx.child(1, 2), m_debugeeFileName+":29"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "__libc_start_main"); COMPARE_DATA(tIdx.child(3, 0), "3"); COMPARE_DATA(tIdx.child(3, 1), "_start"); session->stepOut(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), m_debugeeFileName+":30"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "__libc_start_main"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "_start"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackFetchMore() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeerecursion")); QString fileName = findSourceFile("debugeerecursion.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo()"); COMPARE_DATA(tIdx.child(0, 2), fileName+":26"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "foo()"); COMPARE_DATA(tIdx.child(1, 2), fileName+":24"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "foo()"); COMPARE_DATA(tIdx.child(2, 2), fileName+":24"); COMPARE_DATA(tIdx.child(19, 0), "19"); COMPARE_DATA(tIdx.child(20, 0), "20"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(tIdx.child(20, 0), "20"); COMPARE_DATA(tIdx.child(21, 0), "21"); COMPARE_DATA(tIdx.child(22, 0), "22"); COMPARE_DATA(tIdx.child(39, 0), "39"); COMPARE_DATA(tIdx.child(40, 0), "40"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(tIdx.child(40, 0), "40"); COMPARE_DATA(tIdx.child(41, 0), "41"); COMPARE_DATA(tIdx.child(42, 0), "42"); COMPARE_DATA(tIdx.child(119, 0), "119"); COMPARE_DATA(tIdx.child(120, 0), "120"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(tIdx.child(120, 0), "120"); COMPARE_DATA(tIdx.child(121, 0), "121"); COMPARE_DATA(tIdx.child(122, 0), "122"); COMPARE_DATA(tIdx.child(298, 0), "298"); COMPARE_DATA(tIdx.child(298, 1), "main"); COMPARE_DATA(tIdx.child(298, 2), fileName+":30"); COMPARE_DATA(tIdx.child(299, 0), "299"); COMPARE_DATA(tIdx.child(299, 1), "__libc_start_main"); COMPARE_DATA(tIdx.child(300, 0), "300"); COMPARE_DATA(tIdx.child(300, 1), "_start"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackDeactivateAndActive() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), m_debugeeFileName+":30"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "__libc_start_main"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "_start"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackSwitchThread() { QSKIP("Skipping... lldb-mi crashes when break at a location with multiple threads running"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), fileName+":39"); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); WAIT_FOR_A_WHILE(session, 200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << "nice" << findExecutable("lldb_debugeeslow").toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); TestDebugSession *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); WAIT_FOR_A_WHILE(session, 100); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 35); // lldb-mi sliently stops when attaching to a process. Force it continue to run. session->addCommand(MI::ExecContinue, QString(), MI::CmdMaybeStartsRunning); WAIT_FOR_A_WHILE(session, 2000); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRemoteDebugging() { KProcess gdbServer; gdbServer << "lldb-server" << "gdbserver" << "*:1234"; gdbServer.start(); QVERIFY(gdbServer.waitForStarted()); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(Config::LldbRemoteDebuggingEntry, true); cfg.config().writeEntry(Config::LldbRemoteServerEntry, "localhost:1234"); cfg.config().writeEntry(Config::LldbRemotePathEntry, "/tmp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testCoreFile() { QFile f("core"); if (f.exists()) f.remove(); KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << "bash" << "-c" << "ulimit -c unlimited; " + findExecutable("lldb_crash").toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); bool coreFileFound = QFile::exists("core"); if (!coreFileFound) { // Try to use coredumpctl auto coredumpctl = QStandardPaths::findExecutable("coredumpctl"); if (!coredumpctl.isEmpty()) { QFileInfo fi("core"); KProcess::execute(coredumpctl, {"-1", "-o", fi.absoluteFilePath(), "dump", "lldb_crash"}); coreFileFound = fi.exists(); } } if (!coreFileFound) QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); TestDebugSession *session = new TestDebugSession; session->examineCoreFile(findExecutable("lldb_crash"), QUrl::fromLocalFile(QDir::currentPath()+"/core")); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesLocals() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(variableCollection()->rowCount(), 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); COMPARE_DATA(variableCollection()->index(0, 1, i), "2"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesLocalsStruct() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == "ts") { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatches() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; m_core->debugController()->variableCollection()->variableWidgetShown(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatchesQuotes() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); // the unquoted string (the actual content): t\"t // quoted string (what we would write as a c string): "t\\\"t" // written in source file: R"("t\\\"t")" const QString testString("t\\\"t"); // the actual content const QString quotedTestString(R"("t\\\"t")"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string WAIT_FOR_A_WHILE(session, 3000); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); COMPARE_DATA(variableCollection()->index(0, 1, i), quotedTestString); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); WAIT_FOR_A_WHILE(session, 100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QStringLiteral("[%0]").arg(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '" + c + "'"; COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QStringLiteral("[%0]").arg(len)); COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\0'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatchesTwoSessions() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); WAIT_FOR_A_WHILE(session, 300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); auto v = dynamic_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = dynamic_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(v->inScope()); COMPARE_DATA(variableCollection()->indexForItem(v, 1), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope v = dynamic_cast(watchVariableAt(0)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } void LldbTest::testVariablesStopDebugger() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesStartSecondSession() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesSwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesQuicklySwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 300); stackModel->setCurrentFrame(0); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(0); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesNonascii() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeeqt")); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); QString fileName = findSourceFile("debugeeqt.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 30); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 30); COMPARE_DATA(localVariableIndexAt(0, 1), QString("\"\u4f60\u597d\u4e16\u754c\"")); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testSwitchFrameLldbConsole() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(stackModel->currentFrame(), 1); session->addUserCommand("print i"); WAIT_FOR_A_WHILE(session, 500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); } void LldbTest::testSegfaultDebugee() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_crash")); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); QString fileName = findSourceFile("debugeecrash.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 274390 void LldbTest::testCommandOrderFastStepping() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeeqt")); breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); for(int i=0; i<20; i++) { session->stepInto(); } WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRunLldbScript() { TestDebugSession *session = new TestDebugSession; QTemporaryFile runScript; runScript.open(); runScript.write(QStringLiteral("break set --file %1 --line 35\n").arg(findSourceFile("debugee.cpp")).toUtf8()); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBug301287() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add("argc"); WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; foreach (const QString & console, QStringList() << "konsole" << "xterm" << "xfce4-terminal" << "gnome-terminal") { if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } TestDebugSession* session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } void LldbTest::testSpecialPath() { #ifndef HAVE_PATH_WITH_SPACES_TEST QSKIP("Skipping... special path test," " this CMake version would create a faulty build.ninja file. Upgrade to at least CMake v3.0"); #endif QSKIP("Skipping... lldb-mi itself can't handle path with space in application dir"); TestDebugSession* session = new TestDebugSession; auto debugee = findExecutable("path with space/lldb_spacedebugee"); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("spacedebugee.cpp:30"); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void KDevMI::LLDB::LldbTest::testEnvironmentCd() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); auto path = KIO::upUrl(findExecutable("path with space/lldb_spacedebugee")); TestLaunchConfiguration cfg(findExecutable("lldb_debugeepath"), path); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << path.toLocalFile()); } QTEST_MAIN(KDevMI::LLDB::LldbTest); #include "test_lldb.moc" diff --git a/debuggers/lldb/unittests/test_lldbformatters.cpp b/debuggers/lldb/unittests/test_lldbformatters.cpp index fe7f4da429..2d720c8069 100644 --- a/debuggers/lldb/unittests/test_lldbformatters.cpp +++ b/debuggers/lldb/unittests/test_lldbformatters.cpp @@ -1,600 +1,600 @@ /* * Unit tests for LLDB debugger plugin * 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 "test_lldbformatters.h" #include "controllers/variable.h" #include "controllers/variablecontroller.h" #include "debugsession.h" #include "stringhelpers.h" #include "testhelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define WAIT_FOR_STATE(session, state) \ do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR_A_WHILE(session, ms) \ do { if (!KDevMI::LLDB::waitForAWhile((session), (ms), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ KDevMI::LLDB::TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ do { if (!KDevMI::LLDB::compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) #define VERIFY_QSTRING(row, name, expected) \ do { if (!verifyQString((row), (name), (expected), __FILE__, __LINE__)) return; } while (0) #define findSourceFile(name) \ findSourceFile(__FILE__, (name)) using namespace KDevelop; using namespace KDevMI::LLDB; class TestLaunchConfiguration : public ILaunchConfiguration { public: TestLaunchConfiguration(const QString& executable, const QUrl& workingDirectory = QUrl()) { auto execPath = findExecutable(executable); qDebug() << "FIND" << execPath; c = new KConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", execPath); cfg.writeEntry("Working Directory", workingDirectory); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; QString name() const override { return QString("Test-Launch"); } - KDevelop::IProject* project() const override { return 0; } - KDevelop::LaunchConfigurationType* type() const override { return 0; } + KDevelop::IProject* project() const override { return nullptr; } + KDevelop::LaunchConfigurationType* type() const override { return nullptr; } KConfig *rootConfig() { return c; } private: KConfigGroup cfg; KConfig *c; }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { setSourceInitFile(false); setFormatterPath(findSourceFile("../formatters/all.py")); KDevelop::ICore::self()->debugController()->addSession(this); variableController()->setAutoUpdate(IVariableController::UpdateLocals); } }; VariableCollection *LldbFormattersTest::variableCollection() { return m_core->debugController()->variableCollection(); } LldbVariable *LldbFormattersTest::watchVariableAt(int i) { auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); auto idx = variableCollection()->index(i, 0, watchRoot); return dynamic_cast(variableCollection()->itemForIndex(idx)); } QModelIndex LldbFormattersTest::localVariableIndexAt(int i, int col) { auto localRoot = variableCollection()->indexForItem(variableCollection()->locals(), 0); return variableCollection()->index(i, col, localRoot); } KDevelop::Breakpoint* LldbFormattersTest::addCodeBreakpoint(const QUrl& location, int line) { return m_core->debugController()->breakpointModel()->addCodeBreakpoint(location, line); } // Called before the first testfunction is executed void LldbFormattersTest::initTestCase() { AutoTestShell::init(); m_core = TestCore::initialize(Core::NoUi); m_iface = m_core->pluginController() ->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute") ->extension(); Q_ASSERT(m_iface); } // Called after the last testfunction was executed void LldbFormattersTest::cleanupTestCase() { TestCore::shutdown(); } // Called before each testfunction is executed void LldbFormattersTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup bpCfg = KSharedConfig::openConfig()->group("breakpoints"); bpCfg.writeEntry("number", 0); bpCfg.sync(); auto count = m_core->debugController()->breakpointModel()->rowCount(); m_core->debugController()->breakpointModel()->removeRows(0, count); while (variableCollection()->watches()->childCount() > 0) { auto var = watchVariableAt(0); if (!var) break; var->die(); } m_session = new TestDebugSession; } void LldbFormattersTest::cleanup() { // Called after every testfunction if (m_session) m_session->stopDebugger(); WAIT_FOR_STATE(m_session, DebugSession::EndedState); m_session.clear(); } bool LldbFormattersTest::verifyQString(int index, const QString &name, const QString &expected, const char *file, int line) { auto varidx = localVariableIndexAt(index, 0); auto var = variableCollection()->itemForIndex(varidx); if (!compareData(varidx, name, file, line)) { return false; } if (!compareData(localVariableIndexAt(index, 1), Utils::quote(expected), file, line)) { return false; } // fetch all children auto childCount = 0; while (childCount != variableCollection()->rowCount(varidx)) { childCount = variableCollection()->rowCount(varidx); var->fetchMoreChildren(); if (!waitForAWhile(m_session, 50, file, line)) return false; } if (childCount != expected.length()) { QTest::qFail(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3") .arg(childCount).arg(expected.length()).arg(file).arg(line)), file, line); return false; } for (int i = 0; i != childCount; ++i) { if (!compareData(variableCollection()->index(i, 0, varidx), QString("[%0]").arg(i), file, line)) { return false; } if (!compareData(variableCollection()->index(i, 1, varidx), QString("'%0'").arg(expected[i]), file, line)) { return false; } } return true; } void LldbFormattersTest::testQString() { TestLaunchConfiguration cfg("lldb_qstring"); addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("qstring.cpp")), 4); QVERIFY(m_session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); QCOMPARE(variableCollection()->rowCount(), 2); VERIFY_QSTRING(0, "s", "test string"); m_session->stepOver(); WAIT_FOR_STATE_AND_IDLE(m_session, DebugSession::PausedState); QCOMPARE(m_session->currentLine(), 5); VERIFY_QSTRING(0, "s", "test stringx"); m_session->run(); WAIT_FOR_STATE(m_session, DebugSession::EndedState); } /* void LldbFormattersTest::testQByteArray() { LldbProcess lldb("qbytearray"); lldb.execute("break qbytearray.cpp:5"); lldb.execute("run"); QByteArray out = lldb.execute("print ba"); QVERIFY(out.contains("\"test byte array\"")); QVERIFY(out.contains("[0] = 116 't'")); QVERIFY(out.contains("[4] = 32 ' '")); lldb.execute("next"); QVERIFY(lldb.execute("print ba").contains("\"test byte arrayx\"")); } void LldbFormattersTest::testQListContainer_data() { QTest::addColumn("container"); QTest::newRow("QList") << "QList"; QTest::newRow("QQueue") << "QQueue"; QTest::newRow("QVector") << "QVector"; QTest::newRow("QStack") << "QStack"; QTest::newRow("QLinkedList") << "QLinkedList"; QTest::newRow("QSet") << "QSet"; } void LldbFormattersTest::testQListContainer() { QFETCH(QString, container); LldbProcess lldb("qlistcontainer"); lldb.execute("break main"); lldb.execute("run"); lldb.execute(QString("break 'doStuff<%1>()'").arg(container).toLocal8Bit()); lldb.execute("cont"); { // lldb.execute("break qlistcontainer.cpp:34"); lldb.execute("cont"); QByteArray out = lldb.execute("print intList"); QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); lldb.execute("next"); out = lldb.execute("print intList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = 10")); QVERIFY(out.contains("[1] = 20")); QVERIFY(!out.contains("[2] = 30")); } else { // QSet order is undefined QVERIFY(out.contains("] = 10")); QVERIFY(out.contains("] = 20")); QVERIFY(!out.contains("] = 30")); } lldb.execute("next"); out = lldb.execute("print intList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = 10")); QVERIFY(out.contains("[1] = 20")); QVERIFY(out.contains("[2] = 30")); } else { // QSet order is undefined QVERIFY(out.contains("] = 10")); QVERIFY(out.contains("] = 20")); QVERIFY(out.contains("] = 30")); } } { // lldb.execute("next"); QByteArray out = lldb.execute("print stringList"); QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); lldb.execute("next"); out = lldb.execute("print stringList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = \"a\"")); QVERIFY(out.contains("[1] = \"bc\"")); QVERIFY(!out.contains("[2] = \"d\"")); } else { // QSet order is undefined QVERIFY(out.contains("] = \"a\"")); QVERIFY(out.contains("] = \"bc\"")); QVERIFY(!out.contains("] = \"d\"")); } lldb.execute("next"); out = lldb.execute("print stringList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = \"a\"")); QVERIFY(out.contains("[1] = \"bc\"")); QVERIFY(out.contains("[2] = \"d\"")); } else { // QSet order is undefined QVERIFY(out.contains("] = \"a\"")); QVERIFY(out.contains("] = \"bc\"")); QVERIFY(out.contains("] = \"d\"")); } } { // lldb.execute("next"); QByteArray out = lldb.execute("print structList"); QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); lldb.execute("next"); out = lldb.execute("print structList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); QVERIFY(out.contains("[0] = {")); QVERIFY(out.contains("a = \"a\"")); QVERIFY(out.contains("b = \"b\"")); QVERIFY(out.contains("c = 100")); QVERIFY(out.contains("d = -200")); lldb.execute("next"); out = lldb.execute("print structList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); QVERIFY(out.contains("[1] = {")); } { // lldb.execute("next"); QByteArray out = lldb.execute("print pointerList"); QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); lldb.execute("next"); out = lldb.execute("print pointerList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); QVERIFY(out.contains("[0] = 0x")); QVERIFY(out.contains("[1] = 0x")); QVERIFY(!out.contains("[2] = 0x")); lldb.execute("next"); out = lldb.execute("print pointerList"); QVERIFY(out.contains("[0] = 0x")); QVERIFY(out.contains("[1] = 0x")); QVERIFY(out.contains("[2] = 0x")); lldb.execute("next"); } { // > lldb.execute("next"); QByteArray out = lldb.execute("print pairList"); QVERIFY(out.contains(QString("empty %1>").arg(container).toLocal8Bit())); lldb.execute("next"); out = lldb.execute("print pairList"); QVERIFY(out.contains(QString("%1>").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("[1] = {\n first = 2, \n second = 3\n }")); } else { // order is undefined in QSet QVERIFY(out.contains("] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("] = {\n first = 2, \n second = 3\n }")); } QVERIFY(!out.contains("[2] = ")); lldb.execute("next"); out = lldb.execute("print pairList"); if (container != "QSet") { QVERIFY(out.contains("[0] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("[1] = {\n first = 2, \n second = 3\n }")); QVERIFY(out.contains("[2] = {\n first = 4, \n second = 5\n }")); } else { // order is undefined in QSet QVERIFY(out.contains("] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("] = {\n first = 2, \n second = 3\n }")); QVERIFY(out.contains("] = {\n first = 4, \n second = 5\n }")); } } } void LldbFormattersTest::testQMapInt() { LldbProcess lldb("qmapint"); lldb.execute("break qmapint.cpp:7"); lldb.execute("run"); QByteArray out = lldb.execute("print m"); QVERIFY(out.contains("QMap")); QVERIFY(out.contains("[10] = 100")); QVERIFY(out.contains("[20] = 200")); lldb.execute("next"); out = lldb.execute("print m"); QVERIFY(out.contains("[30] = 300")); } void LldbFormattersTest::testQMapString() { LldbProcess lldb("qmapstring"); lldb.execute("break qmapstring.cpp:8"); lldb.execute("run"); QByteArray out = lldb.execute("print m"); QVERIFY(out.contains("QMap")); QVERIFY(out.contains("[\"10\"] = \"100\"")); QVERIFY(out.contains("[\"20\"] = \"200\"")); lldb.execute("next"); out = lldb.execute("print m"); QVERIFY(out.contains("[\"30\"] = \"300\"")); } void LldbFormattersTest::testQMapStringBool() { LldbProcess lldb("qmapstringbool"); lldb.execute("break qmapstringbool.cpp:8"); lldb.execute("run"); QByteArray out = lldb.execute("print m"); QVERIFY(out.contains("QMap")); QVERIFY(out.contains("[\"10\"] = true")); QVERIFY(out.contains("[\"20\"] = false")); lldb.execute("next"); out = lldb.execute("print m"); QVERIFY(out.contains("[\"30\"] = true")); } void LldbFormattersTest::testQDate() { LldbProcess lldb("qdate"); lldb.execute("break qdate.cpp:6"); lldb.execute("run"); QByteArray out = lldb.execute("print d"); QVERIFY(out.contains("2010-01-20")); } void LldbFormattersTest::testQTime() { LldbProcess lldb("qtime"); lldb.execute("break qtime.cpp:6"); lldb.execute("run"); QByteArray out = lldb.execute("print t"); QVERIFY(out.contains("15:30:10.123")); } void LldbFormattersTest::testQDateTime() { LldbProcess lldb("qdatetime"); lldb.execute("break qdatetime.cpp:5"); lldb.execute("run"); QByteArray out = lldb.execute("print dt"); QVERIFY(out.contains("Wed Jan 20 15:31:13 2010")); } void LldbFormattersTest::testQUrl() { LldbProcess lldb("qurl"); lldb.execute("break qurl.cpp:5"); lldb.execute("run"); QByteArray out = lldb.execute("print u"); QVERIFY(out.contains("http://www.kdevelop.org/foo")); } void LldbFormattersTest::testQHashInt() { LldbProcess lldb("qhashint"); lldb.execute("break qhashint.cpp:7"); lldb.execute("run"); QByteArray out = lldb.execute("print h"); QVERIFY(out.contains("[10] = 100")); QVERIFY(out.contains("[20] = 200")); lldb.execute("next"); out = lldb.execute("print h"); QVERIFY(out.contains("[30] = 300")); } void LldbFormattersTest::testQHashString() { LldbProcess lldb("qhashstring"); lldb.execute("break qhashstring.cpp:8"); lldb.execute("run"); QByteArray out = lldb.execute("print h"); QVERIFY(out.contains("[\"10\"] = \"100\"")); QVERIFY(out.contains("[\"20\"] = \"200\"")); lldb.execute("next"); out = lldb.execute("print h"); QVERIFY(out.contains("[\"30\"] = \"300\"")); } void LldbFormattersTest::testQSetInt() { LldbProcess lldb("qsetint"); lldb.execute("break qsetint.cpp:7"); lldb.execute("run"); QByteArray out = lldb.execute("print s"); QVERIFY(out.contains("] = 10")); QVERIFY(out.contains("] = 20")); lldb.execute("next"); out = lldb.execute("print s"); QVERIFY(out.contains("] = 30")); } void LldbFormattersTest::testQSetString() { LldbProcess lldb("qsetstring"); lldb.execute("break qsetstring.cpp:8"); lldb.execute("run"); QByteArray out = lldb.execute("print s"); QVERIFY(out.contains("] = \"10\"")); QVERIFY(out.contains("] = \"20\"")); lldb.execute("next"); out = lldb.execute("print s"); QVERIFY(out.contains("] = \"30\"")); } void LldbFormattersTest::testQChar() { LldbProcess lldb("qchar"); lldb.execute("break qchar.cpp:5"); lldb.execute("run"); QVERIFY(lldb.execute("print c").contains("\"k\"")); } void LldbFormattersTest::testQListPOD() { LldbProcess lldb("qlistpod"); lldb.execute("break qlistpod.cpp:31"); lldb.execute("run"); QVERIFY(lldb.execute("print b").contains("false")); QVERIFY(lldb.execute("print c").contains("50")); QVERIFY(lldb.execute("print uc").contains("50")); QVERIFY(lldb.execute("print s").contains("50")); QVERIFY(lldb.execute("print us").contains("50")); QVERIFY(lldb.execute("print i").contains("50")); QVERIFY(lldb.execute("print ui").contains("50")); QVERIFY(lldb.execute("print l").contains("50")); QVERIFY(lldb.execute("print ul").contains("50")); QVERIFY(lldb.execute("print i64").contains("50")); QVERIFY(lldb.execute("print ui64").contains("50")); QVERIFY(lldb.execute("print f").contains("50")); QVERIFY(lldb.execute("print d").contains("50")); } void LldbFormattersTest::testQUuid() { LldbProcess lldb("quuid"); lldb.execute("break quuid.cpp:4"); lldb.execute("run"); QByteArray data = lldb.execute("print id"); QVERIFY(data.contains("{9ec3b70b-d105-42bf-b3b4-656e44d2e223}")); } void LldbFormattersTest::testKTextEditorTypes() { LldbProcess lldb("ktexteditortypes"); lldb.execute("break ktexteditortypes.cpp:9"); lldb.execute("run"); QByteArray data = lldb.execute("print cursor"); QCOMPARE(data, QByteArray("$1 = [1, 1]")); data = lldb.execute("print range"); QCOMPARE(data, QByteArray("$2 = [(1, 1) -> (2, 2)]")); } void LldbFormattersTest::testKDevelopTypes() { LldbProcess lldb("kdeveloptypes"); lldb.execute("break kdeveloptypes.cpp:12"); lldb.execute("run"); QVERIFY(lldb.execute("print path1").contains("(\"tmp\", \"foo\")")); QVERIFY(lldb.execute("print path2").contains("(\"http://www.test.com\", \"tmp\", \"asdf.txt\")")); } */ QTEST_MAIN(LldbFormattersTest) #include "test_lldbformatters.moc" diff --git a/debuggers/lldb/widgets/lldbconfigpage.h b/debuggers/lldb/widgets/lldbconfigpage.h index 61cb72ba1d..ba5cabfd19 100644 --- a/debuggers/lldb/widgets/lldbconfigpage.h +++ b/debuggers/lldb/widgets/lldbconfigpage.h @@ -1,56 +1,56 @@ /* * LLDB Debugger Support * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef LLDBCONFIGPAGE_H #define LLDBCONFIGPAGE_H #include namespace Ui { class LldbConfigPage; } class LldbConfigPage : public KDevelop::LaunchConfigurationPage { Q_OBJECT public: - LldbConfigPage( QWidget* parent = 0 ); + LldbConfigPage( QWidget* parent = nullptr ); ~LldbConfigPage() override; QIcon icon() const override; QString title() const override; void loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject *proj = nullptr) override; void saveToConfiguration(KConfigGroup cfg, KDevelop::IProject *proj = nullptr) const override; private: Ui::LldbConfigPage* ui; }; class LldbConfigPageFactory : public KDevelop::LaunchConfigurationPageFactory { public: KDevelop::LaunchConfigurationPage* createWidget(QWidget* parent) override; }; #endif // LLDBCONFIGPAGE_H diff --git a/documentation/manpage/manpagedocumentation.cpp b/documentation/manpage/manpagedocumentation.cpp index f5070d727f..f3584b9186 100644 --- a/documentation/manpage/manpagedocumentation.cpp +++ b/documentation/manpage/manpagedocumentation.cpp @@ -1,106 +1,106 @@ /* This file is part of KDevelop Copyright 2010 Yannick Motta Copyright 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "manpagedocumentation.h" #include "manpageplugin.h" #include "manpagedocumentationwidget.h" #include #include #include #include -ManPagePlugin* ManPageDocumentation::s_provider=0; +ManPagePlugin* ManPageDocumentation::s_provider=nullptr; ManPageDocumentation::ManPageDocumentation(const QString& name, const QUrl& url) : m_url(url), m_name(name) { KIO::StoredTransferJob* transferJob = KIO::storedGet(m_url, KIO::NoReload, KIO::HideProgressInfo); connect( transferJob, &KIO::StoredTransferJob::finished, this, &ManPageDocumentation::finished); transferJob->start(); } void ManPageDocumentation::finished(KJob* j) { KIO::StoredTransferJob* job = qobject_cast(j); if(job && job->error()==0) { m_description = QString::fromUtf8(job->data()); } else { m_description.clear(); } emit descriptionChanged(); } KDevelop::IDocumentationProvider* ManPageDocumentation::provider() const { return s_provider; } QString ManPageDocumentation::description() const { return m_description; } QWidget* ManPageDocumentation::documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent ) { KDevelop::StandardDocumentationView* view = new KDevelop::StandardDocumentationView(findWidget, parent); view->setDocumentation(IDocumentation::Ptr(this)); // apply custom style-sheet to normalize look of the page const QString cssFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevmanpage/manpagedocumentation.css"); QWebSettings* settings = view->settings(); settings->setUserStyleSheetUrl(QUrl::fromLocalFile(cssFile)); view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); QObject::connect(view, &KDevelop::StandardDocumentationView::linkClicked, ManPageDocumentation::s_provider->model(), &ManPageModel::showItemFromUrl); return view; } bool ManPageDocumentation::providesWidget() const { return false; } QWidget* ManPageHomeDocumentation::documentationWidget(KDevelop::DocumentationFindWidget *findWidget, QWidget *parent){ Q_UNUSED(findWidget); return new ManPageDocumentationWidget(parent); } QString ManPageHomeDocumentation::name() const { return i18n("Man Content Page"); } KDevelop::IDocumentationProvider* ManPageHomeDocumentation::provider() const { return ManPageDocumentation::s_provider; } diff --git a/documentation/manpage/manpagedocumentation.h b/documentation/manpage/manpagedocumentation.h index 8d994b4a57..97c920a010 100644 --- a/documentation/manpage/manpagedocumentation.h +++ b/documentation/manpage/manpagedocumentation.h @@ -1,72 +1,72 @@ /* This file is part of KDevelop Copyright 2010 Yannick Motta Copyright 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MANPAGEDOCUMENTATION_H #define MANPAGEDOCUMENTATION_H #include "manpagemodel.h" #include #include #include #include #include class QWidget; class QStackedWidget; class QTreeView; class ManPagePlugin; class ManPageDocumentation : public KDevelop::IDocumentation { Q_OBJECT public: ManPageDocumentation(const QString& name, const QUrl& url); QString name() const override { return m_name; } QString description() const override; virtual bool providesWidget() const; - QWidget* documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = 0) override; + QWidget* documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = nullptr) override; KDevelop::IDocumentationProvider* provider() const override; static ManPagePlugin* s_provider; private slots: void finished(KJob*); private: const QUrl m_url; const QString m_name; QString m_description; }; class ManPageHomeDocumentation : public KDevelop::IDocumentation { Q_OBJECT public: KDevelop::IDocumentationProvider* provider() const override; QString name() const override; QString description() const override { return name(); } - QWidget* documentationWidget ( KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = 0 ) override; + QWidget* documentationWidget ( KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = nullptr ) override; }; #endif // MANPAGEDOCUMENTATION_H diff --git a/documentation/manpage/manpagedocumentationwidget.cpp b/documentation/manpage/manpagedocumentationwidget.cpp index 6cd4a58b43..4f4957d197 100644 --- a/documentation/manpage/manpagedocumentationwidget.cpp +++ b/documentation/manpage/manpagedocumentationwidget.cpp @@ -1,98 +1,98 @@ /* This file is part of KDevelop Copyright 2010 Benjamin Port Copyright 2010 Yannick Motta This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "manpagedocumentationwidget.h" #include "manpagedocumentation.h" #include "manpageplugin.h" #include #include #include #include #include #include ManPageDocumentationWidget::ManPageDocumentationWidget(QWidget *parent) : QStackedWidget(parent) - , m_loadingWidget(0) + , m_loadingWidget(nullptr) { ManPageModel* model = ManPageDocumentation::s_provider->model(); m_treeView = new QTreeView(this); m_treeView->header()->setVisible(false); connect(m_treeView, &QTreeView::clicked, model, &ManPageModel::showItem); addWidget(m_treeView); if(!model->isLoaded()){ m_loadingWidget = new QWidget(this); m_progressBar = new QProgressBar(m_loadingWidget); m_statusLabel = new QLabel(i18n("Loading man pages ...")); if(model->sectionCount() == 0){ connect(model, &ManPageModel::sectionListUpdated, this, &ManPageDocumentationWidget::sectionListUpdated ); } else { sectionListUpdated(); } connect(model, &ManPageModel::sectionParsed, this, &ManPageDocumentationWidget::sectionParsed ); connect(model, &ManPageModel::manPagesLoaded, this, &ManPageDocumentationWidget::manIndexLoaded); connect(model, &ManPageModel::error, this, &ManPageDocumentationWidget::handleError); m_statusLabel->setAlignment(Qt::AlignHCenter); QVBoxLayout* layout = new QVBoxLayout(); layout->addWidget(m_statusLabel); layout->addWidget(m_progressBar); layout->addStretch(); m_loadingWidget->setLayout(layout); addWidget(m_loadingWidget); setCurrentWidget(m_loadingWidget); if(model->hasError()) handleError(model->errorString()); } else { manIndexLoaded(); } } void ManPageDocumentationWidget::manIndexLoaded() { ManPageModel* model = ManPageDocumentation::s_provider->model(); m_treeView->setModel(model); setCurrentWidget(m_treeView); if(m_loadingWidget){ removeWidget(m_loadingWidget); delete m_loadingWidget; - m_loadingWidget = 0; + m_loadingWidget = nullptr; } } void ManPageDocumentationWidget::sectionListUpdated() { ManPageModel* model = ManPageDocumentation::s_provider->model(); m_progressBar->setRange(0, model->sectionCount()); } void ManPageDocumentationWidget::sectionParsed() { ManPageModel* model = ManPageDocumentation::s_provider->model(); m_progressBar->setValue(model->nbSectionLoaded()); } void ManPageDocumentationWidget::handleError(const QString& errorString) { delete m_progressBar; m_progressBar = nullptr; m_statusLabel->setWordWrap(true); m_statusLabel->setText(i18n("Man pages loading error") + '\n' + errorString); } diff --git a/documentation/manpage/manpagedocumentationwidget.h b/documentation/manpage/manpagedocumentationwidget.h index a40122654e..51c3f600ba 100644 --- a/documentation/manpage/manpagedocumentationwidget.h +++ b/documentation/manpage/manpagedocumentationwidget.h @@ -1,48 +1,48 @@ /* This file is part of KDevelop Copyright 2010 Benjamin Port Copyright 2010 Yannick Motta This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MANPAGEDOCUMENTATIONWIDGET_H #define MANPAGEDOCUMENTATIONWIDGET_H #include class QString; class QLabel; class QTreeView; class QProgressBar; class ManPageDocumentationWidget : public QStackedWidget { Q_OBJECT public: - explicit ManPageDocumentationWidget(QWidget *parent = 0); + explicit ManPageDocumentationWidget(QWidget *parent = nullptr); public slots: void manIndexLoaded(); void sectionListUpdated(); void sectionParsed(); void handleError(const QString& errorString); private: QWidget* m_loadingWidget; QTreeView* m_treeView; QLabel* m_statusLabel; QProgressBar* m_progressBar; }; #endif // MANPAGEDOCUMENTATIONWIDGET_H diff --git a/documentation/manpage/manpagemodel.h b/documentation/manpage/manpagemodel.h index 5139a70af7..17e2e47f37 100644 --- a/documentation/manpage/manpagemodel.h +++ b/documentation/manpage/manpagemodel.h @@ -1,96 +1,96 @@ /* This file is part of KDevelop Copyright 2010 Yannick Motta Copyright 2010 Benjamin Port Copyright 2014 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MANPAGEMODEL_H #define MANPAGEMODEL_H #include #include #include // id and name for man section typedef QPair ManSection; class ManPageModel : public QAbstractItemModel { Q_OBJECT public: - ManPageModel(QObject* parent = 0); + ManPageModel(QObject* parent = nullptr); ~ManPageModel() override; /** * You can use @p DeclarationRole to get the Declaration for a given index. * NOTE: If you use that, don't forget to lock the DUChain if you access the declaration! */ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& = QModelIndex()) const override { return 1; } QModelIndex parent(const QModelIndex& child = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QStringListModel* indexList(); bool containsIdentifier(QString identifier); int sectionCount() const; bool isLoaded() const; int nbSectionLoaded() const; bool identifierInSection(const QString &identifier, const QString §ion) const; bool hasError() const; const QString& errorString() const; signals: void sectionParsed(); void sectionListUpdated(); void manPagesLoaded(); void error(const QString& errorString); public slots: void showItem(const QModelIndex& idx); void showItemFromUrl(const QUrl& url); private slots: void initModel(); void indexEntries(KIO::Job* job, const KIO::UDSEntryList& entries); void indexLoaded(KJob* job); void sectionEntries(KIO::Job* job, const KIO::UDSEntryList& entries); void sectionLoaded(); private: QString manPage(const QString §ionUrl, int position) const; void initSection(); QListIterator *iterator = nullptr; QList m_sectionList; QHash > m_manMap; QStringList m_index; QStringListModel* m_indexModel; bool m_loaded; int m_nbSectionLoaded; QString m_errorString; }; #endif // MANPAGEMODEL_H diff --git a/documentation/qthelp/qthelpconfig.cpp b/documentation/qthelp/qthelpconfig.cpp index 7e29776e00..12ca45d41b 100644 --- a/documentation/qthelp/qthelpconfig.cpp +++ b/documentation/qthelp/qthelpconfig.cpp @@ -1,359 +1,359 @@ /* This file is part of KDevelop Copyright 2010 Benjamin Port Copyright 2014 Kevin Funk Copyright 2016 Andreas Cord-Landwehr This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qthelpconfig.h" #include #include #include #include #include #include #include #include #include "ui_qthelpconfig.h" #include "ui_qthelpconfigeditdialog.h" #include "qthelp_config_shared.h" #include "debug.h" #include "qthelpplugin.h" enum Column { NameColumn, PathColumn, IconColumn, GhnsColumn, ConfigColumn }; class QtHelpConfigEditDialog : public QDialog, public Ui_QtHelpConfigEditDialog { public: - explicit QtHelpConfigEditDialog(QTreeWidgetItem* modifiedItem, QtHelpConfig* parent = 0, - Qt::WindowFlags f = 0) + explicit QtHelpConfigEditDialog(QTreeWidgetItem* modifiedItem, QtHelpConfig* parent = nullptr, + Qt::WindowFlags f = nullptr) : QDialog(parent, f) , m_modifiedItem(modifiedItem) , m_config(parent) { setupUi(this); if (modifiedItem) { setWindowTitle(i18n("Modify Entry")); } else { setWindowTitle(i18n("Add New Entry")); } qchIcon->setIcon("qtlogo"); } bool checkQtHelpFile(); void accept() override; private: QTreeWidgetItem* m_modifiedItem; QtHelpConfig* m_config; }; bool QtHelpConfigEditDialog::checkQtHelpFile() { //verify if the file is valid and if there is a name if(qchName->text().isEmpty()){ KMessageBox::error(this, i18n("Name cannot be empty.")); return false; } return m_config->checkNamespace(qchRequester->text(), m_modifiedItem); } void QtHelpConfigEditDialog::accept() { if (!checkQtHelpFile()) return; QDialog::accept(); } QtHelpConfig::QtHelpConfig(QtHelpPlugin* plugin, QWidget *parent) : KDevelop::ConfigPage(plugin, nullptr, parent) { QVBoxLayout * l = new QVBoxLayout( this ); QWidget* w = new QWidget; m_configWidget = new Ui::QtHelpConfigUI; m_configWidget->setupUi( w ); m_configWidget->addButton->setIcon(QIcon::fromTheme("list-add")); connect(m_configWidget->addButton, &QPushButton::clicked, this, &QtHelpConfig::add); // Table m_configWidget->qchTable->setColumnHidden(IconColumn, true); m_configWidget->qchTable->setColumnHidden(GhnsColumn, true); m_configWidget->qchTable->model()->setHeaderData(ConfigColumn, Qt::Horizontal, QVariant()); m_configWidget->qchTable->header()->setSectionsMovable(false); m_configWidget->qchTable->header()->setStretchLastSection(false); m_configWidget->qchTable->header()->setSectionResizeMode(NameColumn, QHeaderView::Stretch); m_configWidget->qchTable->header()->setSectionResizeMode(PathColumn, QHeaderView::Stretch); m_configWidget->qchTable->header()->setSectionResizeMode(ConfigColumn, QHeaderView::Fixed); // Add GHNS button KNS3::Button *knsButton = new KNS3::Button(i18nc("Allow user to get some API documentation with GHNS", "Get New Documentation"), "kdevelop-qthelp.knsrc", m_configWidget->boxQchManage); m_configWidget->tableCtrlLayout->insertWidget(1, knsButton); connect(knsButton, &KNS3::Button::dialogFinished, this, &QtHelpConfig::knsUpdate); connect(m_configWidget->loadQtDocsCheckBox, &QCheckBox::toggled, this, static_cast(&QtHelpConfig::changed)); connect(m_configWidget->qchSearchDirButton, &QPushButton::clicked, this, &QtHelpConfig::chooseSearchDir); connect(m_configWidget->qchSearchDir,&QLineEdit::textChanged, this, &QtHelpConfig::searchDirChanged); // Set availability information for QtHelp m_configWidget->messageAvailabilityQtDocs->setCloseButtonVisible(false); if(plugin->isQtHelpAvailable()) { m_configWidget->messageAvailabilityQtDocs->setVisible(false); } else { m_configWidget->messageAvailabilityQtDocs->setText( i18n("The command \"qmake -query\" could not provide a path to a QtHelp file (QCH).")); m_configWidget->loadQtDocsCheckBox->setVisible(false); } l->addWidget(w); reset(); } QtHelpConfig::~QtHelpConfig() { delete m_configWidget; } void QtHelpConfig::apply() { QStringList iconList, nameList, pathList, ghnsList; for (int i = 0; i < m_configWidget->qchTable->topLevelItemCount(); i++) { const QTreeWidgetItem* item = m_configWidget->qchTable->topLevelItem(i); nameList << item->text(0); pathList << item->text(1); iconList << item->text(2); ghnsList << item->text(3); } QString searchDir = m_configWidget->qchSearchDir->text(); bool loadQtDoc = m_configWidget->loadQtDocsCheckBox->isChecked(); qtHelpWriteConfig(iconList, nameList, pathList, ghnsList, searchDir, loadQtDoc); static_cast(plugin())->readConfig(); } void QtHelpConfig::reset() { m_configWidget->qchTable->clear(); QStringList iconList, nameList, pathList, ghnsList; QString searchDir; bool loadQtDoc; qtHelpReadConfig(iconList, nameList, pathList, ghnsList, searchDir, loadQtDoc); const int size = qMin(qMin(iconList.size(), nameList.size()), pathList.size()); for(int i = 0; i < size; ++i) { QString ghnsStatus = ghnsList.size()>i ? ghnsList.at(i) : "0"; addTableItem(iconList.at(i), nameList.at(i), pathList.at(i), ghnsStatus); } m_configWidget->qchSearchDir->setText(searchDir); m_configWidget->loadQtDocsCheckBox->setChecked(loadQtDoc); emit changed(); } void QtHelpConfig::defaults() { bool change = false; if(m_configWidget->qchTable->topLevelItemCount() > 0) { m_configWidget->qchTable->clear(); change = true; } if(!m_configWidget->loadQtDocsCheckBox->isChecked()){ m_configWidget->loadQtDocsCheckBox->setChecked(true); change = true; } if (change) { emit changed(); } } void QtHelpConfig::add() { - QtHelpConfigEditDialog dialog(0, this); + QtHelpConfigEditDialog dialog(nullptr, this); if (!dialog.exec()) return; QTreeWidgetItem* item = addTableItem(dialog.qchIcon->icon(), dialog.qchName->text(), dialog.qchRequester->text(), "0"); m_configWidget->qchTable->setCurrentItem(item); emit changed(); } void QtHelpConfig::modify(QTreeWidgetItem* item) { if (!item) return; QtHelpConfigEditDialog dialog(item, this); if (item->text(GhnsColumn) != "0") { dialog.qchRequester->setText(i18n("Documentation provided by GHNS")); dialog.qchRequester->setEnabled(false); } else { dialog.qchRequester->setText(item->text(PathColumn)); dialog.qchRequester->setEnabled(true); } dialog.qchName->setText(item->text(NameColumn)); dialog.qchIcon->setIcon(item->text(IconColumn)); if (!dialog.exec()) { return; } item->setIcon(NameColumn, QIcon(dialog.qchIcon->icon())); item->setText(NameColumn, dialog.qchName->text()); item->setText(IconColumn, dialog.qchIcon->icon()); if(item->text(GhnsColumn) == "0") { item->setText(PathColumn, dialog.qchRequester->text()); } emit changed(); } bool QtHelpConfig::checkNamespace(const QString& filename, QTreeWidgetItem* modifiedItem) { QString qtHelpNamespace = QHelpEngineCore::namespaceName(filename); if (qtHelpNamespace.isEmpty()) { // Open error message (not valid Qt Compressed Help file) KMessageBox::error(this, i18n("Qt Compressed Help file is not valid.")); return false; } // verify if it's the namespace it's not already in the list for(int i=0; i < m_configWidget->qchTable->topLevelItemCount(); i++) { const QTreeWidgetItem* item = m_configWidget->qchTable->topLevelItem(i); if (item != modifiedItem){ if (qtHelpNamespace == QHelpEngineCore::namespaceName(item->text(PathColumn))) { // Open error message, documentation already imported KMessageBox::error(this, i18n("Documentation already imported")); return false; } } } return true; } void QtHelpConfig::remove(QTreeWidgetItem* item) { if (!item) return; delete item; emit changed(); } void QtHelpConfig::knsUpdate(KNS3::Entry::List list) { if (list.isEmpty()) return; foreach (const KNS3::Entry& e, list) { if(e.status() == KNS3::Entry::Installed) { if(e.installedFiles().size() == 1) { QString filename = e.installedFiles().at(0); if(checkNamespace(filename, nullptr)){ QTreeWidgetItem* item = addTableItem("documentation", e.name(), filename, "1"); m_configWidget->qchTable->setCurrentItem(item); } else { qCDebug(QTHELP) << "namespace error"; } } } else if(e.status() == KNS3::Entry::Deleted) { if(e.uninstalledFiles().size() == 1) { for(int i=0; i < m_configWidget->qchTable->topLevelItemCount(); i++) { QTreeWidgetItem* item = m_configWidget->qchTable->topLevelItem(i); if (e.uninstalledFiles().at(0) == item->text(PathColumn)) { delete item; break; } } } } } emit changed(); } void QtHelpConfig::chooseSearchDir() { m_configWidget->qchSearchDir->setText(QFileDialog::getExistingDirectory(this)); } void QtHelpConfig::searchDirChanged() { emit changed(); } QString QtHelpConfig::name() const { return i18n("QtHelp Documentation"); } QIcon QtHelpConfig::icon() const { return QIcon::fromTheme(QStringLiteral("help-contents")); } QTreeWidgetItem * QtHelpConfig::addTableItem(const QString &icon, const QString &name, const QString &path, const QString &ghnsStatus) { QTreeWidgetItem *item = new QTreeWidgetItem(m_configWidget->qchTable); item->setIcon(NameColumn, QIcon::fromTheme(icon)); item->setText(NameColumn, name); item->setToolTip(NameColumn, name); item->setText(PathColumn, path); item->setToolTip(PathColumn, path); item->setText(IconColumn, icon); item->setText(GhnsColumn, ghnsStatus); QWidget *ctrlWidget = new QWidget(item->treeWidget()); ctrlWidget->setLayout(new QHBoxLayout(ctrlWidget)); QToolButton *modifyBtn = new QToolButton(item->treeWidget()); modifyBtn->setIcon(QIcon::fromTheme("document-edit")); modifyBtn->setToolTip(ki18n("Modify").toString()); connect(modifyBtn, &QPushButton::clicked, this, [=](){ modify(item); }); QToolButton *removeBtn = new QToolButton(item->treeWidget()); removeBtn->setIcon(QIcon::fromTheme("entry-delete")); removeBtn->setToolTip(ki18n("Delete").toString()); if (item->text(GhnsColumn) != "0") { // KNS3 currently does not provide API to uninstall entries // just removing the files results in wrong installed states in the KNS3 dialog // TODO: add API to KNS to remove files without UI interaction removeBtn->setEnabled(false); removeBtn->setToolTip(tr("Please uninstall this via GHNS")); } else { connect(removeBtn, &QPushButton::clicked, this, [=](){ remove(item); }); } ctrlWidget->layout()->addWidget(modifyBtn); ctrlWidget->layout()->addWidget(removeBtn); m_configWidget->qchTable->setItemWidget(item, ConfigColumn, ctrlWidget); return item; } diff --git a/documentation/qthelp/qthelpconfig.h b/documentation/qthelp/qthelpconfig.h index 3720699802..cdd88b75ae 100644 --- a/documentation/qthelp/qthelpconfig.h +++ b/documentation/qthelp/qthelpconfig.h @@ -1,69 +1,69 @@ /* This file is part of KDevelop Copyright 2010 Benjamin Port Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef QTHELPCONFIG_H #define QTHELPCONFIG_H #include #include class QTreeWidgetItem; class QtHelpPlugin; namespace Ui { class QtHelpConfigUI; } class QtHelpConfig : public KDevelop::ConfigPage { public: Q_OBJECT public: - explicit QtHelpConfig(QtHelpPlugin* plugin, QWidget *parent = 0); + explicit QtHelpConfig(QtHelpPlugin* plugin, QWidget *parent = nullptr); ~QtHelpConfig() override; bool checkNamespace(const QString &filename, QTreeWidgetItem* modifiedItem); QString name() const override; QIcon icon() const override; private slots: void add(); void remove(QTreeWidgetItem* item); void modify(QTreeWidgetItem* item); void knsUpdate(KNS3::Entry::List list); void chooseSearchDir(); void searchDirChanged(); public Q_SLOTS: void apply() override; void defaults() override; void reset() override; private: QTreeWidgetItem * addTableItem(const QString &icon, const QString &name, const QString &path, const QString &ghnsStatus); Ui::QtHelpConfigUI* m_configWidget; }; #endif // QTHELPCONFIG_H diff --git a/documentation/qthelp/qthelpdocumentation.cpp b/documentation/qthelp/qthelpdocumentation.cpp index 7e627ba099..e090bc984e 100644 --- a/documentation/qthelp/qthelpdocumentation.cpp +++ b/documentation/qthelp/qthelpdocumentation.cpp @@ -1,355 +1,355 @@ /* This file is part of KDevelop Copyright 2009 Aleix Pol Copyright 2009 David Nolden Copyright 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qthelpdocumentation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "qthelpnetwork.h" #include "qthelpproviderabstract.h" using namespace KDevelop; namespace { #if QT_VERSION >= 0x050500 int indexOf(const QString& str, const QRegularExpression& re, int from, QRegularExpressionMatch* rmatch) { return str.indexOf(re, from, rmatch); } int lastIndexOf(const QString& str, const QRegularExpression& re, int from, QRegularExpressionMatch* rmatch) { return str.lastIndexOf(re, from, rmatch); } #else int indexOf(const QString& str, const QRegularExpression& re, int from, QRegularExpressionMatch* rmatch) { if (!re.isValid()) { qWarning("QString::indexOf: invalid QRegularExpression object"); return -1; } QRegularExpressionMatch match = re.match(str, from); if (match.hasMatch()) { const int ret = match.capturedStart(); if (rmatch) *rmatch = qMove(match); return ret; } return -1; } int lastIndexOf(const QString &str, const QRegularExpression &re, int from, QRegularExpressionMatch *rmatch) { if (!re.isValid()) { qWarning("QString::lastIndexOf: invalid QRegularExpression object"); return -1; } int endpos = (from < 0) ? (str.size() + from + 1) : (from + 1); QRegularExpressionMatchIterator iterator = re.globalMatch(str); int lastIndex = -1; while (iterator.hasNext()) { QRegularExpressionMatch match = iterator.next(); int start = match.capturedStart(); if (start < endpos) { lastIndex = start; if (rmatch) *rmatch = qMove(match); } else { break; } } return lastIndex; } #endif } -QtHelpProviderAbstract* QtHelpDocumentation::s_provider=0; +QtHelpProviderAbstract* QtHelpDocumentation::s_provider=nullptr; QtHelpDocumentation::QtHelpDocumentation(const QString& name, const QMap& info) - : m_provider(s_provider), m_name(name), m_info(info), m_current(info.constBegin()), lastView(0) + : m_provider(s_provider), m_name(name), m_info(info), m_current(info.constBegin()), lastView(nullptr) {} QtHelpDocumentation::QtHelpDocumentation(const QString& name, const QMap& info, const QString& key) - : m_provider(s_provider), m_name(name), m_info(info), m_current(m_info.find(key)), lastView(0) + : m_provider(s_provider), m_name(name), m_info(info), m_current(m_info.find(key)), lastView(nullptr) { Q_ASSERT(m_current!=m_info.constEnd()); } QtHelpDocumentation::~QtHelpDocumentation() { } QString QtHelpDocumentation::description() const { QUrl url(m_current.value()); QByteArray data = m_provider->engine()->fileData(url); //Extract a short description from the html data const QString dataString = QString::fromLatin1(data); ///@todo encoding const QString fragment = url.fragment(); const QString p = QStringLiteral("((\\\")|(\\\'))"); const QString optionalSpace = QStringLiteral("( )*"); const QString exp = QString("< a name = " + p + fragment + p + " > < / a >").replace(' ', optionalSpace); const QRegularExpression findFragment(exp); QRegularExpressionMatch findFragmentMatch; int pos = indexOf(dataString, findFragment, 0, &findFragmentMatch); if(fragment.isEmpty()) { pos = 0; } else { //Check if there is a title opening-tag right before the fragment, and if yes add it, so we have a nicely formatted caption const QString titleRegExp = QStringLiteral("< h\\d class = \".*\" >").replace(' ', optionalSpace); const QRegularExpression findTitle(titleRegExp); const QRegularExpressionMatch match = findTitle.match(dataString, pos); const int titleStart = match.capturedStart(); const int titleEnd = titleStart + match.capturedEnd(); if(titleStart != -1) { const QStringRef between = dataString.midRef(titleEnd, pos-titleEnd).trimmed(); if(between.isEmpty()) pos = titleStart; } } if(pos != -1) { const QString exp = QString("< a name = " + p + "((\\S)*)" + p + " > < / a >").replace(' ', optionalSpace); const QRegularExpression nextFragmentExpression(exp); int endPos = dataString.indexOf(nextFragmentExpression, pos+(fragment.size() ? findFragmentMatch.capturedLength() : 0)); if(endPos == -1) { endPos = dataString.size(); } { //Find the end of the last paragraph or newline, so we don't add prefixes of the following fragment const QString newLineRegExp = QStringLiteral ("< br / > | < / p >").replace(' ', optionalSpace); const QRegularExpression lastNewLine(newLineRegExp); QRegularExpressionMatch match; const int newEnd = lastIndexOf(dataString, lastNewLine, endPos, &match); if(match.isValid() && newEnd > pos) endPos = newEnd + match.capturedLength(); } { //Find the title, and start from there const QString titleRegExp = QStringLiteral("< h\\d class = \"title\" >").replace(' ', optionalSpace); const QRegularExpression findTitle(titleRegExp); const QRegularExpressionMatch match = findTitle.match(dataString); if (match.isValid()) pos = qBound(pos, match.capturedStart(), endPos); } QString thisFragment = dataString.mid(pos, endPos - pos); { //Completely remove the first large header found, since we don't need a header const QString headerRegExp = QStringLiteral("< h\\d.*>.*?< / h\\d >").replace(' ', optionalSpace); const QRegularExpression findHeader(headerRegExp); const QRegularExpressionMatch match = findHeader.match(thisFragment); if(match.isValid()) { thisFragment.remove(match.capturedStart(), match.capturedLength()); } } { //Replace all gigantic header-font sizes with { const QString sizeRegExp = QStringLiteral("< h\\d ").replace(' ', optionalSpace); const QRegularExpression findSize(sizeRegExp); thisFragment.replace(findSize, "").replace(' ', optionalSpace); const QRegularExpression closeSize(sizeCloseRegExp); thisFragment.replace(closeSize, "
"); } } { //Replace paragraphs by newlines const QString begin = QStringLiteral("< p >").replace(' ', optionalSpace); const QRegularExpression findBegin(begin); thisFragment.replace(findBegin, {}); const QString end = QStringLiteral("< /p >").replace(' ', optionalSpace); const QRegularExpression findEnd(end); thisFragment.replace(findEnd, "
"); } { //Remove links, because they won't work const QString link = QString("< a href = " + p + ".*?" + p).replace(' ', optionalSpace); const QRegularExpression exp(link, QRegularExpression::CaseInsensitiveOption); thisFragment.replace(exp, "
open(); QTextStream ts(file); ts << "html { background: white !important; }\n"; if (url.scheme() == "qthelp" && url.host().startsWith("com.trolltech.qt.")) { ts << ".content .toc + .title + p { clear:left; }\n" << "#qtdocheader .qtref { position: absolute !important; top: 5px !important; right: 0 !important; }\n"; } file->close(); view->settings()->setUserStyleSheetUrl(QUrl::fromLocalFile(file->fileName())); delete m_lastStyleSheet.data(); m_lastStyleSheet = file; } QWidget* QtHelpDocumentation::documentationWidget(DocumentationFindWidget* findWidget, QWidget* parent) { if(m_info.isEmpty()) { //QtHelp sometimes has empty info maps. e.g. availableaudioeffects i 4.5.2 return new QLabel(i18n("Could not find any documentation for '%1'", m_name), parent); } else { StandardDocumentationView* view = new StandardDocumentationView(findWidget, parent); view->settings()->setAttribute(QWebSettings::JavascriptEnabled, false); if (!m_sharedQNAM) { m_sharedQNAM.reset(new HelpNetworkAccessManager(m_provider->engine())); } view->page()->setNetworkAccessManager(m_sharedQNAM.data()); view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(view, &StandardDocumentationView::customContextMenuRequested, this, &QtHelpDocumentation::viewContextMenuRequested); QObject::connect(view, &StandardDocumentationView::linkClicked, this, &QtHelpDocumentation::jumpedTo); setUserStyleSheet(view, m_current.value()); view->setContent(m_provider->engine()->fileData(m_current.value()), QStringLiteral("text/html"), m_current.value()); lastView = view; return view; } } void QtHelpDocumentation::viewContextMenuRequested(const QPoint& pos) { StandardDocumentationView* view = qobject_cast(sender()); if (!view) return; QMenu menu; QAction* copyAction = view->pageAction(QWebPage::Copy); copyAction->setIcon(QIcon::fromTheme("edit-copy")); menu.addAction(copyAction); if (m_info.count() > 1) { menu.addSeparator(); QActionGroup* actionGroup = new QActionGroup(&menu); foreach(const QString& name, m_info.keys()) { QtHelpAlternativeLink* act=new QtHelpAlternativeLink(name, this, actionGroup); act->setCheckable(true); act->setChecked(name==m_current.key()); menu.addAction(act); } } menu.exec(view->mapToGlobal(pos)); } void QtHelpDocumentation::jumpedTo(const QUrl& newUrl) { Q_ASSERT(lastView); m_provider->jumpedTo(newUrl); setUserStyleSheet(lastView, newUrl); lastView->load(newUrl); } IDocumentationProvider* QtHelpDocumentation::provider() const { return m_provider; } QtHelpAlternativeLink::QtHelpAlternativeLink(const QString& name, const QtHelpDocumentation* doc, QObject* parent) : QAction(name, parent), mDoc(doc), mName(name) { connect(this, &QtHelpAlternativeLink::triggered, this, &QtHelpAlternativeLink::showUrl); } void QtHelpAlternativeLink::showUrl() { IDocumentation::Ptr newDoc(new QtHelpDocumentation(mName, mDoc->info(), mName)); ICore::self()->documentationController()->showDocumentation(newDoc); } HomeDocumentation::HomeDocumentation() : m_provider(QtHelpDocumentation::s_provider) { } QWidget* HomeDocumentation::documentationWidget(DocumentationFindWidget*, QWidget* parent) { QTreeView* w=new QTreeView(parent); w->header()->setVisible(false); w->setModel(m_provider->engine()->contentModel()); connect(w, &QTreeView::clicked, this, &HomeDocumentation::clicked); return w; } void HomeDocumentation::clicked(const QModelIndex& idx) { QHelpContentModel* model = m_provider->engine()->contentModel(); QHelpContentItem* it=model->contentItemAt(idx); QMap info; info.insert(it->title(), it->url()); IDocumentation::Ptr newDoc(new QtHelpDocumentation(it->title(), info)); ICore::self()->documentationController()->showDocumentation(newDoc); } QString HomeDocumentation::name() const { return i18n("QtHelp Home Page"); } IDocumentationProvider* HomeDocumentation::provider() const { return m_provider; } diff --git a/documentation/qthelp/qthelpdocumentation.h b/documentation/qthelp/qthelpdocumentation.h index 2ab6c133fc..97f27aa72d 100644 --- a/documentation/qthelp/qthelpdocumentation.h +++ b/documentation/qthelp/qthelpdocumentation.h @@ -1,107 +1,107 @@ /* This file is part of KDevelop Copyright 2009 Aleix Pol Copyright 2009 David Nolden Copyright 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef QTHELPDOCUMENTATION_H #define QTHELPDOCUMENTATION_H #include #include #include #include #include class QModelIndex; class QNetworkAccessManager; class QWebView; class QtHelpProviderAbstract; class QTemporaryFile; class QtHelpDocumentation : public KDevelop::IDocumentation { Q_OBJECT public: QtHelpDocumentation(const QString& name, const QMap& info); QtHelpDocumentation(const QString& name, const QMap& info, const QString& key); ~QtHelpDocumentation() override; QString name() const override { return m_name; } QString description() const override; QWidget* documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent) override; KDevelop::IDocumentationProvider* provider() const override; QMap info() const { return m_info; } static QtHelpProviderAbstract* s_provider; public slots: void viewContextMenuRequested(const QPoint& pos); private slots: void jumpedTo(const QUrl& newUrl); private: void setUserStyleSheet(QWebView* view, const QUrl& url); private: QtHelpProviderAbstract *m_provider; const QString m_name; const QMap m_info; const QMap::const_iterator m_current; QWebView* lastView; QPointer m_lastStyleSheet; QScopedPointer m_sharedQNAM; }; class HomeDocumentation : public KDevelop::IDocumentation { Q_OBJECT public: HomeDocumentation(); - QWidget* documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = 0) override; + QWidget* documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = nullptr) override; QString description() const override { return QString(); } QString name() const override; KDevelop::IDocumentationProvider* provider() const override; public slots: void clicked(const QModelIndex& idx); private: QtHelpProviderAbstract *m_provider; }; class QtHelpAlternativeLink : public QAction { Q_OBJECT public: QtHelpAlternativeLink(const QString& name, const QtHelpDocumentation* doc, QObject* parent); public slots: void showUrl(); private: const QtHelpDocumentation* mDoc; const QString mName; }; #endif diff --git a/documentation/qthelp/qthelpplugin.cpp b/documentation/qthelp/qthelpplugin.cpp index 9eb07cbc2a..bb8d98ce35 100644 --- a/documentation/qthelp/qthelpplugin.cpp +++ b/documentation/qthelp/qthelpplugin.cpp @@ -1,183 +1,183 @@ /* This file is part of KDevelop Copyright 2009 Aleix Pol Copyright 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qthelpplugin.h" #include #include #include #include #include #include "qthelpprovider.h" #include "qthelpqtdoc.h" #include "qthelp_config_shared.h" #include "debug.h" #include "qthelpconfig.h" -QtHelpPlugin *QtHelpPlugin::s_plugin = 0; +QtHelpPlugin *QtHelpPlugin::s_plugin = nullptr; K_PLUGIN_FACTORY_WITH_JSON(QtHelpPluginFactory, "kdevqthelp.json", registerPlugin(); ) QtHelpPlugin::QtHelpPlugin(QObject* parent, const QVariantList& args) : KDevelop::IPlugin("kdevqthelp", parent) , m_qtHelpProviders() , m_qtDoc(new QtHelpQtDoc(this, QVariantList())) , m_loadSystemQtDoc(false) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IDocumentationProviderProvider ) Q_UNUSED(args); s_plugin = this; connect(this, &QtHelpPlugin::changedProvidersList, KDevelop::ICore::self()->documentationController(), &KDevelop::IDocumentationController::changedDocumentationProviders); QMetaObject::invokeMethod(this, "readConfig", Qt::QueuedConnection); } QtHelpPlugin::~QtHelpPlugin() { } void QtHelpPlugin::readConfig() { QStringList iconList, nameList, pathList, ghnsList; QString searchDir; qtHelpReadConfig(iconList, nameList, pathList, ghnsList, searchDir, m_loadSystemQtDoc); searchHelpDirectory(pathList, nameList, iconList, searchDir); loadQtHelpProvider(pathList, nameList, iconList); loadQtDocumentation(m_loadSystemQtDoc); emit changedProvidersList(); } void QtHelpPlugin::loadQtDocumentation(bool loadQtDoc) { if(!loadQtDoc){ m_qtDoc->unloadDocumentation(); } else if(loadQtDoc) { m_qtDoc->loadDocumentation(); } } void QtHelpPlugin::searchHelpDirectory(QStringList& pathList, QStringList& nameList, QStringList& iconList, const QString& searchDir) { if (searchDir.isEmpty()) { return; } qCDebug(QTHELP) << "Searching qch files in: " << searchDir; QDirIterator dirIt(searchDir, QStringList() << "*.qch", QDir::Files, QDirIterator::Subdirectories); const QString logo("qtlogo"); while(dirIt.hasNext() == true) { dirIt.next(); qCDebug(QTHELP) << "qch found: " << dirIt.filePath(); pathList.append(dirIt.filePath()); nameList.append(dirIt.fileInfo().baseName()); iconList.append(logo); } } void QtHelpPlugin::loadQtHelpProvider(QStringList pathList, QStringList nameList, QStringList iconList) { QList oldList(m_qtHelpProviders); m_qtHelpProviders.clear(); for(int i=0; i < pathList.length(); i++) { // check if provider already exist QString fileName = pathList.at(i); QString name = nameList.at(i); QString iconName = iconList.at(i); QString nameSpace = QHelpEngineCore::namespaceName(fileName); if(!nameSpace.isEmpty()){ - QtHelpProvider *provider = 0; + QtHelpProvider *provider = nullptr; foreach(QtHelpProvider* oldProvider, oldList){ if(QHelpEngineCore::namespaceName(oldProvider->fileName()) == nameSpace){ provider = oldProvider; oldList.removeAll(provider); break; } } if(!provider){ provider = new QtHelpProvider(this, fileName, name, iconName, QVariantList()); }else{ provider->setName(name); provider->setIconName(iconName); } bool exist = false; foreach(QtHelpProvider* existingProvider, m_qtHelpProviders){ if(QHelpEngineCore::namespaceName(existingProvider->fileName()) == nameSpace){ exist = true; break; } } if(!exist){ m_qtHelpProviders.append(provider); } } } // delete unused providers qDeleteAll(oldList); } QList QtHelpPlugin::providers() { QList list; foreach(QtHelpProvider* provider, m_qtHelpProviders) { list.append(provider); } if(m_loadSystemQtDoc){ list.append(m_qtDoc); } return list; } QList QtHelpPlugin::qtHelpProviderLoaded() { return m_qtHelpProviders; } bool QtHelpPlugin::isQtHelpQtDocLoaded() const { return m_loadSystemQtDoc; } bool QtHelpPlugin::isQtHelpAvailable() const { return !m_qtDoc->qchFiles().isEmpty(); } KDevelop::ConfigPage* QtHelpPlugin::configPage(int number, QWidget* parent) { if (number == 0) { return new QtHelpConfig(this, parent); } return nullptr; } int QtHelpPlugin::configPages() const { return 1; } #include "qthelpplugin.moc" diff --git a/documentation/qthelp/tests/test_qthelpplugin.cpp b/documentation/qthelp/tests/test_qthelpplugin.cpp index 763ce1bd9f..3c8b3542e0 100644 --- a/documentation/qthelp/tests/test_qthelpplugin.cpp +++ b/documentation/qthelp/tests/test_qthelpplugin.cpp @@ -1,253 +1,253 @@ /* This file is part of KDevelop Copyright 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_qthelpplugin.h" #include "../qthelpplugin.h" #include "../qthelpprovider.h" #include "../qthelp_config_shared.h" #include #include #include #include #include #include #include #include #include "testqthelpconfig.h" const QString VALID1 = QTHELP_FILES "/valid1.qch"; const QString VALID2 = QTHELP_FILES "/valid2.qch"; const QString INVALID = QTHELP_FILES "/invalid.qch"; QTEST_MAIN(TestQtHelpPlugin) using namespace KDevelop; TestQtHelpPlugin::TestQtHelpPlugin() { } void TestQtHelpPlugin::initTestCase() { AutoTestShell::init({"kdevqthelp"}); m_testCore = new TestCore(); m_testCore->initialize(); } void TestQtHelpPlugin::init() { m_plugin = new QtHelpPlugin(m_testCore, QVariantList()); // write default config and read it qtHelpWriteConfig(QStringList(), QStringList(), QStringList(), QStringList(), QString(), true); m_plugin->readConfig(); } void TestQtHelpPlugin::cleanup() { delete m_plugin; } void TestQtHelpPlugin::cleanupTestCase() { m_testCore->cleanup(); delete m_testCore; } void TestQtHelpPlugin::testDefaultValue() { QCOMPARE(m_plugin->isQtHelpQtDocLoaded(), true); QCOMPARE(m_plugin->qtHelpProviderLoaded().size(), 0); QCOMPARE(m_plugin->providers().size(), 1); } void TestQtHelpPlugin::testUnsetQtHelpDoc() { qtHelpWriteConfig(QStringList(), QStringList(), QStringList(), QStringList(), QString(), false); m_plugin->readConfig(); QCOMPARE(m_plugin->providers().size(), 0); } void TestQtHelpPlugin::testAddOneValidProvider() { QStringList path, name, icon, ghns; path << VALID1; name << "file1"; icon << "myIcon"; ghns << "0"; qtHelpWriteConfig(icon, name, path, ghns, QString(), true); m_plugin->readConfig(); QCOMPARE(m_plugin->qtHelpProviderLoaded().size(), 1); QCOMPARE(m_plugin->qtHelpProviderLoaded().at(0)->fileName(), path.at(0)); QCOMPARE(m_plugin->qtHelpProviderLoaded().at(0)->name(), name.at(0)); QCOMPARE(m_plugin->qtHelpProviderLoaded().at(0)->iconName(), icon.at(0)); } void TestQtHelpPlugin::testAddTwoDifferentValidProvider() { QStringList path, name, icon, ghns; path << VALID1 << VALID2; name << "file1" << "file2"; icon << "myIcon" << "myIcon"; ghns << "0" << "0"; qtHelpWriteConfig(icon, name, path, ghns, QString(), true); m_plugin->readConfig(); QCOMPARE(m_plugin->qtHelpProviderLoaded().size(), 2); // first provider QCOMPARE(m_plugin->qtHelpProviderLoaded().at(0)->fileName(), path.at(0)); QCOMPARE(m_plugin->qtHelpProviderLoaded().at(0)->name(), name.at(0)); QCOMPARE(m_plugin->qtHelpProviderLoaded().at(0)->iconName(), icon.at(0)); // second provider QCOMPARE(m_plugin->qtHelpProviderLoaded().at(1)->fileName(), path.at(1)); QCOMPARE(m_plugin->qtHelpProviderLoaded().at(1)->name(), name.at(1)); QCOMPARE(m_plugin->qtHelpProviderLoaded().at(1)->iconName(), icon.at(1)); } void TestQtHelpPlugin::testAddInvalidProvider() { QStringList path, name, icon, ghns; path << INVALID; name << "file1"; icon << "myIcon"; ghns << "0"; qtHelpWriteConfig(icon, name, path, ghns, QString(), true); m_plugin->readConfig(); QCOMPARE(m_plugin->qtHelpProviderLoaded().size(), 0); } void TestQtHelpPlugin::testAddTwiceSameProvider() { QStringList path, name, icon, ghns; path << VALID1 << VALID1; name << "file1" << "file2"; icon << "myIcon" << "myIcon"; ghns << "0" << "0"; qtHelpWriteConfig(icon, name, path, ghns, QString(), true); m_plugin->readConfig(); QCOMPARE(m_plugin->qtHelpProviderLoaded().size(), 1); } void TestQtHelpPlugin::testRemoveOneProvider() { QStringList path, name, icon, ghns; path << VALID1 << VALID2; name << "file1" << "file2"; icon << "myIcon" << "myIcon"; ghns << "0" << "0"; qtHelpWriteConfig(icon, name, path, ghns, QString(), true); m_plugin->readConfig(); QCOMPARE(m_plugin->qtHelpProviderLoaded().size(), 2); // we remove the second provider QtHelpProvider *provider = m_plugin->qtHelpProviderLoaded().at(0); path.removeAt(1); name.removeAt(1); icon.removeAt(1); ghns.removeAt(1); qtHelpWriteConfig(icon, name, path, ghns, QString(), true); m_plugin->readConfig(); QCOMPARE(m_plugin->qtHelpProviderLoaded().size(), 1); QCOMPARE(m_plugin->qtHelpProviderLoaded().at(0), provider); } void TestQtHelpPlugin::testDeclarationLookup_Class() { init(); TestFile file("class QObject; QObject* o;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto ctx = file.topContext(); QVERIFY(ctx); auto decl = ctx->findDeclarations(QualifiedIdentifier("o")).first(); QVERIFY(decl); - auto typeDecl = dynamic_cast(decl->type()->baseType().data())->declaration(0); + auto typeDecl = dynamic_cast(decl->type()->baseType().data())->declaration(nullptr); QVERIFY(typeDecl); auto provider = dynamic_cast(m_plugin->providers().at(0)); QVERIFY(provider); if (!provider->isValid() || provider->engine()->linksForIdentifier("QObject").isEmpty()) { QSKIP("Qt help not available", SkipSingle); } auto doc = provider->documentationForDeclaration(typeDecl); QVERIFY(doc); QCOMPARE(doc->name(), QStringLiteral("QObject")); const auto description = doc->description(); QVERIFY(description.contains("QObject")); } void TestQtHelpPlugin::testDeclarationLookup_Method() { init(); TestFile file("class QString { static QString fromLatin1(const QByteArray&); };", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto ctx = file.topContext(); QVERIFY(ctx); auto decl = ctx->findDeclarations(QualifiedIdentifier("QString")).first(); auto declFromLatin1 = decl->internalContext()->findDeclarations(QualifiedIdentifier("fromLatin1")).first(); QVERIFY(decl); auto provider = dynamic_cast(m_plugin->providers().at(0)); QVERIFY(provider); if (!provider->isValid() || provider->engine()->linksForIdentifier("QString::fromLatin1").isEmpty()) { QSKIP("Qt help not available", SkipSingle); } auto doc = provider->documentationForDeclaration(declFromLatin1); QVERIFY(doc); QCOMPARE(doc->name(), QStringLiteral("QString::fromLatin1")); const auto description = doc->description(); QVERIFY(description.contains("fromLatin1")); } void TestQtHelpPlugin::testDeclarationLookup_OperatorFunction() { init(); TestFile file("class C {}; bool operator<(const C& a, const C& b) { return true; }", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto ctx = file.topContext(); auto decl = ctx->findDeclarations(QualifiedIdentifier("operator<")).first(); QVERIFY(decl); auto provider = dynamic_cast(m_plugin->providers().at(0)); QVERIFY(provider); if (!provider->isValid() || provider->engine()->linksForIdentifier("QObject").isEmpty()) { QSKIP("Qt help not available", SkipSingle); } auto doc = provider->documentationForDeclaration(decl); // TODO: We should never find a documentation entry for this (but instead, the operator< for QChar is found here) QEXPECT_FAIL("", "doc should be null here", Continue); QVERIFY(!doc); } diff --git a/formatters/astyle/astyle_formatter.cpp b/formatters/astyle/astyle_formatter.cpp index 5b6f381317..2ed142ed0a 100644 --- a/formatters/astyle/astyle_formatter.cpp +++ b/formatters/astyle/astyle_formatter.cpp @@ -1,530 +1,530 @@ /* This file is part of KDevelop * Copyright (C) 2008 Cédric Pasteur Copyright (C) 2001 Matthias Hölzer-Klüpfel 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 "astyle_formatter.h" #include #include #include #include #include #include #include "astyle_stringiterator.h" #include "debug.h" using namespace KDevelop; AStyleFormatter::AStyleFormatter() : ASFormatter() { } QString AStyleFormatter::formatSource(const QString &text, const QString& leftContext, const QString& rightContext) { QString useText = leftContext + text + rightContext; AStyleStringIterator is(useText); QString output; QTextStream os(&output, QIODevice::WriteOnly); init(&is); while(hasMoreLines()) os << QString::fromUtf8(nextLine().c_str()) << endl; - init(0); + init(nullptr); return extractFormattedTextFromContext(output, text, leftContext, rightContext, m_options["FillCount"].toInt()); } void AStyleFormatter::setOption(const QString &key, const QVariant &value) { m_options[key] = value; } void AStyleFormatter::updateFormatter() { qCDebug(ASTYLE) << "Updating option with: " << ISourceFormatter::optionMapToString(m_options) << endl; // fill int wsCount = m_options["FillCount"].toInt(); if(m_options["Fill"].toString() == "Tabs") { ///TODO: rename FillForce somehow... bool force = m_options["FillForce"].toBool(); AStyleFormatter::setTabSpaceConversionMode(false); AStyleFormatter::setTabIndentation(wsCount, force ); m_indentString = "\t"; } else { AStyleFormatter::setSpaceIndentation(wsCount); m_indentString = ""; m_indentString.fill(' ', wsCount); AStyleFormatter::setTabSpaceConversionMode(m_options["FillForce"].toBool()); } AStyleFormatter::setEmptyLineFill(m_options["Fill_EmptyLines"].toBool()); // indent AStyleFormatter::setSwitchIndent(m_options["IndentSwitches"].toBool()); AStyleFormatter::setClassIndent(m_options["IndentClasses"].toBool()); AStyleFormatter::setCaseIndent(m_options["IndentCases"].toBool()); AStyleFormatter::setBracketIndent(m_options["IndentBrackets"].toBool()); AStyleFormatter::setNamespaceIndent(m_options["IndentNamespaces"].toBool()); AStyleFormatter::setLabelIndent(m_options["IndentLabels"].toBool()); AStyleFormatter::setBlockIndent(m_options["IndentBlocks"].toBool()); AStyleFormatter::setPreprocessorIndent(m_options["IndentPreprocessors"].toBool()); // continuation AStyleFormatter::setMaxInStatementIndentLength(m_options["MaxStatement"].toInt()); if(m_options["MinConditional"].toInt() != -1) AStyleFormatter::setMinConditionalIndentLength(m_options["MinConditional"].toInt()); // brackets QString s = m_options["Brackets"].toString(); if(s == "Break") AStyleFormatter::setBracketFormatMode(astyle::BREAK_MODE); else if(s == "Attach") AStyleFormatter::setBracketFormatMode(astyle::ATTACH_MODE); else if(s == "Linux") AStyleFormatter::setBracketFormatMode(astyle::LINUX_MODE); else if(s == "Stroustrup") AStyleFormatter::setBracketFormatMode(astyle::STROUSTRUP_MODE); else if(s == "Horstmann" || s == "RunInMode") AStyleFormatter::setBracketFormatMode(astyle::RUN_IN_MODE); else AStyleFormatter::setBracketFormatMode(astyle::NONE_MODE); AStyleFormatter::setBreakClosingHeaderBracketsMode(m_options["BracketsCloseHeaders"].toBool()); // blocks AStyleFormatter::setBreakBlocksMode(m_options["BlockBreak"].toBool()); AStyleFormatter::setBreakClosingHeaderBlocksMode(m_options["BlockBreakAll"].toBool()); AStyleFormatter::setBreakElseIfsMode(m_options["BlockIfElse"].toBool()); // padding AStyleFormatter::setOperatorPaddingMode(m_options["PadOperators"].toBool()); AStyleFormatter::setParensInsidePaddingMode(m_options["PadParenthesesIn"].toBool()); AStyleFormatter::setParensOutsidePaddingMode(m_options["PadParenthesesOut"].toBool()); AStyleFormatter::setParensHeaderPaddingMode(m_options["PadParenthesesHeader"].toBool()); AStyleFormatter::setParensUnPaddingMode(m_options["PadParenthesesUn"].toBool()); // oneliner AStyleFormatter::setBreakOneLineBlocksMode(!m_options["KeepBlocks"].toBool()); AStyleFormatter::setSingleStatementsMode(!m_options["KeepStatements"].toBool()); // pointer s = m_options["PointerAlign"].toString(); if(s == "Name") AStyleFormatter::setPointerAlignment(astyle::PTR_ALIGN_NAME); else if(s == "Middle") AStyleFormatter::setPointerAlignment(astyle::PTR_ALIGN_MIDDLE); else if(s == "Type") AStyleFormatter::setPointerAlignment(astyle::PTR_ALIGN_TYPE); else AStyleFormatter::setPointerAlignment(astyle::PTR_ALIGN_NONE); } void AStyleFormatter::resetStyle() { setSpaceIndentation(4); setBracketFormatMode(astyle::NONE_MODE); setBreakOneLineBlocksMode(true); setSingleStatementsMode(true); // blocks setBreakBlocksMode(false); setBreakClosingHeaderBlocksMode(false); setBreakElseIfsMode(false); setBreakClosingHeaderBracketsMode(false); //indent setTabIndentation(4, false); setEmptyLineFill(false); setMaxInStatementIndentLength(40); setMinConditionalIndentLength(-1); setSwitchIndent(true); setClassIndent(true); setCaseIndent(false); setBracketIndent(false); setNamespaceIndent(true); setLabelIndent(true); setBlockIndent(false); setPreprocessorIndent(false); //padding setOperatorPaddingMode(false); setParensInsidePaddingMode(true); setParensOutsidePaddingMode(true); setParensHeaderPaddingMode(true); setParensUnPaddingMode(true); } bool AStyleFormatter::predefinedStyle( const QString & style ) { if(style == "ANSI") { resetStyle(); setBracketIndent(false); setSpaceIndentation(4); setBracketFormatMode(astyle::BREAK_MODE); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if(style == "K&R") { resetStyle(); setBracketIndent(false); setSpaceIndentation(4); setBracketFormatMode(astyle::ATTACH_MODE); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if(style == "Linux") { resetStyle(); setBracketIndent(false); setSpaceIndentation(8); setBracketFormatMode(astyle::LINUX_MODE); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if(style == "GNU") { resetStyle(); setBlockIndent(true); setSpaceIndentation(2); setBracketFormatMode(astyle::BREAK_MODE); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if(style == "Java") { resetStyle(); setJavaStyle(); setBracketIndent(false); setSpaceIndentation(4); setBracketFormatMode(astyle::ATTACH_MODE); setSwitchIndent(false); return true; } else if (style == "Stroustrup") { resetStyle(); setBracketFormatMode(astyle::STROUSTRUP_MODE); setBlockIndent(false); setBracketIndent(false); setSpaceIndentation(5); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if (style == "Horstmann") { resetStyle(); setBracketFormatMode(astyle::RUN_IN_MODE); setBlockIndent(false); setBracketIndent(false); setSwitchIndent(true); setSpaceIndentation(3); setClassIndent(false); setNamespaceIndent(false); return true; } else if (style == "Whitesmith") { resetStyle(); setSpaceIndentation(4); setBracketFormatMode(astyle::BREAK_MODE); setBlockIndent(false); setBracketIndent(true); setClassIndent(true); setSwitchIndent(true); setNamespaceIndent(false); return true; } else if (style == "Banner") { resetStyle(); setSpaceIndentation(4); setBracketFormatMode(astyle::ATTACH_MODE); setBlockIndent(false); setBracketIndent(true); setClassIndent(true); setSwitchIndent(true); setNamespaceIndent(false); return true; } else if (style == "1TBS") { resetStyle(); setSpaceIndentation(4); setBracketFormatMode(astyle::LINUX_MODE); setBlockIndent(false); setBracketIndent(false); setAddBracketsMode(true); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if (style == "KDELibs") { // http://techbase.kde.org/Policies/Kdelibs_Coding_Style resetStyle(); setSpaceIndentation(4); setBracketFormatMode(astyle::LINUX_MODE); setPointerAlignment(astyle::PTR_ALIGN_TYPE); setLabelIndent(true); setOperatorPaddingMode(true); setParensInsidePaddingMode(false); setParensOutsidePaddingMode(false); setParensHeaderPaddingMode(true); setParensUnPaddingMode(true); setSingleStatementsMode(false); setTabSpaceConversionMode(true); setPreprocessorIndent(true); setSwitchIndent(false); setClassIndent(false); setNamespaceIndent(false); return true; } else if (style == "Qt") { // http://qt-project.org/wiki/Qt_Coding_Style resetStyle(); setPointerAlignment(astyle::PTR_ALIGN_NAME); setOperatorPaddingMode(true); setBracketFormatMode(astyle::LINUX_MODE); setSwitchIndent(false); setParensInsidePaddingMode(false); setParensOutsidePaddingMode(false); setParensHeaderPaddingMode(true); setParensUnPaddingMode(true); setSpaceIndentation(4); setClassIndent(false); setNamespaceIndent(false); return true; } return false; } QVariant AStyleFormatter::option(const QString &key) { if(!m_options.contains(key)) qCDebug(ASTYLE) << "Missing option name " << key << endl; return m_options[key]; } QString AStyleFormatter::indentString() { return QString(getIndentString().c_str()); } void AStyleFormatter::loadStyle(const QString &content) { m_options = ISourceFormatter::stringToOptionMap(content); updateFormatter(); } QString AStyleFormatter::saveStyle() { return ISourceFormatter::optionMapToString(m_options); } void AStyleFormatter::setTabIndentation(int length, bool forceTabs) { ASFormatter::setTabIndentation(length, forceTabs); m_options["Fill"] = "Tabs"; m_options["FillForce"] = forceTabs; m_options["FillCount"] = length; } void AStyleFormatter::setSpaceIndentation(int length) { ASFormatter::setSpaceIndentation(length); m_options["Fill"] = "Spaces"; m_options["FillCount"] = length; } void AStyleFormatter::setTabSpaceConversionMode(bool mode) { m_options["FillForce"] = mode; ASFormatter::setTabSpaceConversionMode(mode); } void AStyleFormatter::setFillEmptyLines(bool on) { m_options["FillEmptyLines"] = on; ASFormatter::setEmptyLineFill(on); } void AStyleFormatter::setBlockIndent(bool on) { m_options["IndentBlocks"] = on; ASFormatter::setBlockIndent(on); } void AStyleFormatter::setBracketIndent(bool on) { m_options["IndentBrackets"] = on; ASFormatter::setBracketIndent(on); } void AStyleFormatter::setCaseIndent(bool on) { m_options["IndentCases"] = on; ASFormatter::setCaseIndent(on); } void AStyleFormatter::setClassIndent(bool on) { m_options["IndentClasses"] = on; ASFormatter::setClassIndent(on); } void AStyleFormatter::setLabelIndent(bool on) { m_options["IndentLabels"] = on; ASFormatter::setLabelIndent(on); } void AStyleFormatter::setNamespaceIndent(bool on) { m_options["IndentNamespaces"] = on; ASFormatter::setNamespaceIndent(on); } void AStyleFormatter::setPreprocessorIndent(bool on) { m_options["IndentPreprocessors"] = on; ASFormatter::setPreprocDefineIndent(on); } void AStyleFormatter::setSwitchIndent(bool on) { m_options["IndentSwitches"] = on; ASFormatter::setSwitchIndent(on); } void AStyleFormatter::setMaxInStatementIndentLength(int max) { m_options["MaxStatement"] = max; ASFormatter::setMaxInStatementIndentLength(max); } void AStyleFormatter::setMinConditionalIndentLength(int min) { m_options["MinConditional"] = min; ASFormatter::setMinConditionalIndentOption(min); ASFormatter::setMinConditionalIndentLength(); } void AStyleFormatter::setBracketFormatMode(astyle::BracketMode mode) { switch (mode) { case astyle::NONE_MODE: m_options["Brackets"] = ""; break; case astyle::ATTACH_MODE: m_options["Brackets"] = "Attach"; break; case astyle::BREAK_MODE: m_options["Brackets"] = "Break"; break; case astyle::LINUX_MODE: m_options["Brackets"] = "Linux"; break; case astyle::STROUSTRUP_MODE: m_options["Brackets"] = "Stroustrup"; break; case astyle::RUN_IN_MODE: m_options["Brackets"] = "RunInMode"; break; } ASFormatter::setBracketFormatMode(mode); } void AStyleFormatter::setBreakClosingHeaderBracketsMode(bool state) { m_options["BracketsCloseHeaders"] = state; ASFormatter::setBreakClosingHeaderBracketsMode(state); } void AStyleFormatter::setBreakBlocksMode(bool state) { m_options["BlockBreak"] = state; ASFormatter::setBreakBlocksMode(state); } void AStyleFormatter::setBreakElseIfsMode(bool state) { m_options["BlockIfElse"] = state; ASFormatter::setBreakElseIfsMode(state); } void AStyleFormatter::setBreakClosingHeaderBlocksMode(bool state) { m_options["BlockBreakAll"] = state; ASFormatter::setBreakClosingHeaderBlocksMode(state); } void AStyleFormatter::setOperatorPaddingMode(bool mode) { m_options["PadOperators"] = mode; ASFormatter::setOperatorPaddingMode(mode); } void AStyleFormatter::setParensOutsidePaddingMode(bool mode) { m_options["PadParenthesesOut"] = mode; ASFormatter::setParensOutsidePaddingMode(mode); } void AStyleFormatter::setParensInsidePaddingMode(bool mode) { m_options["PadParenthesesIn"] = mode; ASFormatter::setParensInsidePaddingMode(mode); } void AStyleFormatter::setParensHeaderPaddingMode(bool mode) { m_options["PadParenthesesHeader"] = mode; ASFormatter::setParensHeaderPaddingMode(mode); } void AStyleFormatter::setParensUnPaddingMode(bool state) { m_options["PadParenthesesUn"] = state; ASFormatter::setParensUnPaddingMode(state); } void AStyleFormatter::setBreakOneLineBlocksMode(bool state) { m_options["KeepBlocks"] = !state; ASFormatter::setBreakOneLineBlocksMode(state); } void AStyleFormatter::setSingleStatementsMode(bool state) { m_options["KeepStatements"] = !state; ASFormatter::setSingleStatementsMode(state); } void AStyleFormatter::setPointerAlignment(astyle::PointerAlign alignment) { switch (alignment) { case astyle::PTR_ALIGN_NONE: m_options["PointerAlign"] = "None"; break; case astyle::PTR_ALIGN_NAME: m_options["PointerAlign"] = "Name"; break; case astyle::PTR_ALIGN_MIDDLE: m_options["PointerAlign"] = "Middle"; break; case astyle::PTR_ALIGN_TYPE: m_options["PointerAlign"] = "Type"; break; } ASFormatter::setPointerAlignment(alignment); } diff --git a/formatters/astyle/astyle_preferences.h b/formatters/astyle/astyle_preferences.h index 5055f6779c..8f661efb3c 100644 --- a/formatters/astyle/astyle_preferences.h +++ b/formatters/astyle/astyle_preferences.h @@ -1,64 +1,64 @@ /* This file is part of KDevelop * Copyright (C) 2008 Cédric Pasteur Copyright (C) 2001 Matthias Hölzer-Klüpfel 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. */ #ifndef ASTYLEPREFERENCES_H #define ASTYLEPREFERENCES_H #include #include "ui_astyle_preferences.h" class AStyleFormatter; class AStylePreferences : public KDevelop::SettingsWidget, public Ui::AStylePreferences { Q_OBJECT public: enum Language { CPP, Java, CSharp}; - AStylePreferences(Language lang=CPP, QWidget *parent=0); + AStylePreferences(Language lang=CPP, QWidget *parent=nullptr); ~AStylePreferences() override; void load(const KDevelop::SourceFormatterStyle &style) override; QString save() override; protected: void init(); void updatePreviewText(bool emitChangedSignal = true); void setItemChecked(int idx, bool checked); void updateWidgets(); private slots: void currentTabChanged(); void indentChanged(); void indentObjectsChanged(QListWidgetItem *item); void minMaxValuesChanged(); void bracketsChanged(); void blocksChanged(); void paddingChanged(); void onelinersChanged(); void pointerAlignChanged(); private: QScopedPointer m_formatter; bool m_enableWidgetSignals; }; #endif // ASTYLEPREFERENCES_H diff --git a/kdeintegration/executeplasmoid/executeplasmoidplugin.cpp b/kdeintegration/executeplasmoid/executeplasmoidplugin.cpp index c86c0195cf..1032ff01a6 100644 --- a/kdeintegration/executeplasmoid/executeplasmoidplugin.cpp +++ b/kdeintegration/executeplasmoid/executeplasmoidplugin.cpp @@ -1,97 +1,97 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 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 "executeplasmoidplugin.h" #include "plasmoidexecutionconfig.h" #include "plasmoidexecutionjob.h" #include "debug.h" #include #include #include #include using namespace KDevelop; Q_LOGGING_CATEGORY(EXECUTEPLASMOID, "kdevelop.kdeintegration.executeplasmoid") K_PLUGIN_FACTORY_WITH_JSON(KDevExecutePlasmoidFactory,"kdevexecuteplasmoid.json", registerPlugin(); ) ExecutePlasmoidPlugin::ExecutePlasmoidPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin("kdevexecuteplasmoid", parent) { KDEV_USE_EXTENSION_INTERFACE( IExecutePlugin ) m_configType = new PlasmoidExecutionConfigType(); m_configType->addLauncher( new PlasmoidLauncher( this ) ); qCDebug(EXECUTEPLASMOID) << "adding plasmoid launch config"; core()->runController()->addConfigurationType( m_configType ); } ExecutePlasmoidPlugin::~ExecutePlasmoidPlugin() {} void ExecutePlasmoidPlugin::unload() { core()->runController()->removeConfigurationType( m_configType ); delete m_configType; - m_configType = 0; + m_configType = nullptr; } QUrl ExecutePlasmoidPlugin::executable(ILaunchConfiguration* config, QString& /*error*/) const { return QUrl::fromLocalFile(PlasmoidExecutionJob::executable(config)); } QStringList ExecutePlasmoidPlugin::arguments(ILaunchConfiguration* config, QString& /*error*/) const { return PlasmoidExecutionJob::arguments(config); } KJob* ExecutePlasmoidPlugin::dependencyJob(ILaunchConfiguration* config) const { return PlasmoidLauncher::calculateDependencies(config); } QUrl ExecutePlasmoidPlugin::workingDirectory(ILaunchConfiguration* config) const { return QUrl::fromLocalFile(PlasmoidExecutionJob::workingDirectory(config)); } QString ExecutePlasmoidPlugin::environmentGroup(ILaunchConfiguration* /*config*/) const { return QString(); } QString ExecutePlasmoidPlugin::nativeAppConfigTypeId() const { return PlasmoidExecutionConfigType::typeId(); } bool ExecutePlasmoidPlugin::useTerminal(ILaunchConfiguration* /*config*/) const { return false; } QString ExecutePlasmoidPlugin::terminal(ILaunchConfiguration* /*config*/) const { return QString(); } #include "executeplasmoidplugin.moc" diff --git a/kdeintegration/executeplasmoid/plasmoidexecutionconfig.cpp b/kdeintegration/executeplasmoid/plasmoidexecutionconfig.cpp index dd145c35cd..c9acf1e665 100644 --- a/kdeintegration/executeplasmoid/plasmoidexecutionconfig.cpp +++ b/kdeintegration/executeplasmoid/plasmoidexecutionconfig.cpp @@ -1,328 +1,328 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plasmoidexecutionconfig.h" #include "plasmoidexecutionjob.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class la; Q_DECLARE_METATYPE(KDevelop::IProject*); QIcon PlasmoidExecutionConfig::icon() const { return QIcon::fromTheme("system-run"); } QStringList readProcess(QProcess* p) { QStringList ret; while(!p->atEnd()) { QByteArray line = p->readLine(); int nameEnd=line.indexOf(' '); if(nameEnd>0) { ret += line.left(nameEnd); } } return ret; } PlasmoidExecutionConfig::PlasmoidExecutionConfig( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); connect( identifier->lineEdit(), &QLineEdit::textEdited, this, &PlasmoidExecutionConfig::changed ); QProcess pPlasmoids; pPlasmoids.start("plasmoidviewer", QStringList("--list"), QIODevice::ReadOnly); QProcess pThemes; pThemes.start("plasmoidviewer", QStringList("--list-themes"), QIODevice::ReadOnly); pThemes.waitForFinished(); pPlasmoids.waitForFinished(); foreach(const QString& plasmoid, readProcess(&pPlasmoids)) { identifier->addItem(plasmoid); } themes->addItem(QString()); foreach(const QString& theme, readProcess(&pThemes)) { themes->addItem(theme); } connect( dependencies, &KDevelop::DependenciesWidget::changed, this, &PlasmoidExecutionConfig::changed ); } void PlasmoidExecutionConfig::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry("PlasmoidIdentifier", identifier->lineEdit()->text()); QStringList args; args += "--formfactor"; args += formFactor->currentText(); if(!themes->currentText().isEmpty()) { args += "--theme"; args += themes->currentText(); } cfg.writeEntry("Arguments", args); QVariantList deps = dependencies->dependencies(); cfg.writeEntry( "Dependencies", KDevelop::qvariantToString( QVariant( deps ) ) ); } void PlasmoidExecutionConfig::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* ) { bool b = blockSignals( true ); identifier->lineEdit()->setText(cfg.readEntry("PlasmoidIdentifier", "")); blockSignals( b ); QStringList arguments = cfg.readEntry("Arguments", QStringList()); int idxFormFactor = arguments.indexOf("--formfactor")+1; if(idxFormFactor>0) formFactor->setCurrentIndex(formFactor->findText(arguments[idxFormFactor])); int idxTheme = arguments.indexOf("--theme")+1; if(idxTheme>0) themes->setCurrentIndex(themes->findText(arguments[idxTheme])); dependencies->setDependencies( KDevelop::stringToQVariant( cfg.readEntry( "Dependencies", QString() ) ).toList()); } QString PlasmoidExecutionConfig::title() const { return i18n("Configure Plasmoid Execution"); } QList< KDevelop::LaunchConfigurationPageFactory* > PlasmoidLauncher::configPages() const { return QList(); } QString PlasmoidLauncher::description() const { return i18n("Display a plasmoid"); } QString PlasmoidLauncher::id() { return "PlasmoidLauncher"; } QString PlasmoidLauncher::name() const { return i18n("Plasmoid Launcher"); } PlasmoidLauncher::PlasmoidLauncher(ExecutePlasmoidPlugin* plugin) : m_plugin(plugin) { } KJob* PlasmoidLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { - return 0; + return nullptr; } if( launchMode == "execute" ) { KJob* depsJob = dependencies(cfg); QList jobs; if(depsJob) jobs << depsJob; jobs << new PlasmoidExecutionJob(m_plugin, cfg); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), jobs ); } qWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); - return 0; + return nullptr; } KJob* PlasmoidLauncher::calculateDependencies(KDevelop::ILaunchConfiguration* cfg) { QVariantList deps = KDevelop::stringToQVariant( cfg->config().readEntry( "Dependencies", QString() ) ).toList(); if( !deps.isEmpty() ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList items; foreach( const QVariant& dep, deps ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex( dep.toStringList() ) ); if( item ) { items << item; } else { KMessageBox::error(KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Couldn't resolve the dependency: %1", dep.toString())); } } KDevelop::BuilderJob* job = new KDevelop::BuilderJob; job->addItems( KDevelop::BuilderJob::Install, items ); job->updateJobName(); return job; } - return 0; + return nullptr; } KJob* PlasmoidLauncher::dependencies(KDevelop::ILaunchConfiguration* cfg) { return calculateDependencies(cfg); } QStringList PlasmoidLauncher::supportedModes() const { return QStringList() << "execute"; } KDevelop::LaunchConfigurationPage* PlasmoidPageFactory::createWidget(QWidget* parent) { return new PlasmoidExecutionConfig( parent ); } PlasmoidPageFactory::PlasmoidPageFactory() {} PlasmoidExecutionConfigType::PlasmoidExecutionConfigType() { factoryList.append( new PlasmoidPageFactory ); } PlasmoidExecutionConfigType::~PlasmoidExecutionConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString PlasmoidExecutionConfigType::name() const { return i18n("Plasmoid Launcher"); } QList PlasmoidExecutionConfigType::configPages() const { return factoryList; } QString PlasmoidExecutionConfigType::typeId() { return "PlasmoidLauncherType"; } QIcon PlasmoidExecutionConfigType::icon() const { return QIcon::fromTheme("plasma"); } static bool canLaunchMetadataFile(const KDevelop::Path &path) { KConfig cfg(path.toLocalFile(), KConfig::SimpleConfig); KConfigGroup group(&cfg, "Desktop Entry"); QStringList services = group.readEntry("ServiceTypes", group.readEntry("X-KDE-ServiceTypes", QStringList())); return services.contains("Plasma/Applet"); } //don't bother, nobody uses this interface bool PlasmoidExecutionConfigType::canLaunch(const QUrl& ) const { return false; } bool PlasmoidExecutionConfigType::canLaunch(KDevelop::ProjectBaseItem* item) const { KDevelop::ProjectFolderItem* folder = item->folder(); if(folder && folder->hasFileOrFolder("metadata.desktop")) { return canLaunchMetadataFile(KDevelop::Path(folder->path(), "metadata.desktop")); } return false; } void PlasmoidExecutionConfigType::configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const { config.writeEntry("PlasmoidIdentifier", item->path().toUrl().toLocalFile()); } void PlasmoidExecutionConfigType::configureLaunchFromCmdLineArguments(KConfigGroup /*config*/, const QStringList &/*args*/) const {} QMenu* PlasmoidExecutionConfigType::launcherSuggestions() { QList found; QList projects = KDevelop::ICore::self()->projectController()->projects(); foreach(KDevelop::IProject* p, projects) { QSet files = p->fileSet(); foreach(const KDevelop::IndexedString& file, files) { KDevelop::Path path(file.str()); if (path.lastPathSegment() == "metadata.desktop" && canLaunchMetadataFile(path)) { path = path.parent(); QString relUrl = p->path().relativePath(path); QAction* action = new QAction(relUrl, this); action->setProperty("url", relUrl); action->setProperty("project", qVariantFromValue(p)); connect(action, &QAction::triggered, this, &PlasmoidExecutionConfigType::suggestionTriggered); found.append(action); } } } - QMenu *m = 0; + QMenu *m = nullptr; if(!found.isEmpty()) { m = new QMenu(i18n("Plasmoids")); m->addActions(found); } return m; } void PlasmoidExecutionConfigType::suggestionTriggered() { QAction* action = qobject_cast(sender()); KDevelop::IProject* p = action->property("project").value(); QString relUrl = action->property("url").toString(); KDevelop::ILauncher* launcherInstance = launchers().at( 0 ); QPair launcher = qMakePair( launcherInstance->supportedModes().at(0), launcherInstance->id() ); QString name = relUrl.mid(relUrl.lastIndexOf('/')+1); KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, name); KConfigGroup cfg = config->config(); cfg.writeEntry("PlasmoidIdentifier", relUrl); emit signalAddLaunchConfiguration(config); } diff --git a/kdeintegration/executeplasmoid/plasmoidexecutionconfig.h b/kdeintegration/executeplasmoid/plasmoidexecutionconfig.h index 4f2d10a663..6e7abedfda 100644 --- a/kdeintegration/executeplasmoid/plasmoidexecutionconfig.h +++ b/kdeintegration/executeplasmoid/plasmoidexecutionconfig.h @@ -1,99 +1,99 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SCRIPTAPPCONFIGTYPE_H #define SCRIPTAPPCONFIGTYPE_H #include #include #include #include #include "ui_plasmoidexecutionconfig.h" class ExecutePlasmoidPlugin; class PlasmoidExecutionConfig : public KDevelop::LaunchConfigurationPage, Ui::PlasmoidExecutionPage { Q_OBJECT public: PlasmoidExecutionConfig( QWidget* parent ); - void loadFromConfiguration( const KConfigGroup& cfg, KDevelop::IProject* project = 0 ) override; - void saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project = 0 ) const override; + void loadFromConfiguration( const KConfigGroup& cfg, KDevelop::IProject* project = nullptr ) override; + void saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project = nullptr ) const override; QString title() const override; QIcon icon() const override; }; class PlasmoidLauncher : public KDevelop::ILauncher { public: PlasmoidLauncher( ExecutePlasmoidPlugin* plugin ); QList< KDevelop::LaunchConfigurationPageFactory* > configPages() const override; QString description() const override; QString id() override; QString name() const override; KJob* start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) override; virtual KJob* dependencies(KDevelop::ILaunchConfiguration* cfg); QStringList supportedModes() const override; static KJob* calculateDependencies(KDevelop::ILaunchConfiguration* cfg); private: ExecutePlasmoidPlugin* m_plugin; }; class PlasmoidPageFactory : public KDevelop::LaunchConfigurationPageFactory { public: PlasmoidPageFactory(); KDevelop::LaunchConfigurationPage* createWidget(QWidget* parent) override; }; /** * A specific configuration to start a launchable, this could be a native * compiled application, or some script file or byte-compiled file or something else * Provides access to the various configured informations, as well as its type and a name */ class PlasmoidExecutionConfigType : public KDevelop::LaunchConfigurationType { Q_OBJECT public: PlasmoidExecutionConfigType(); ~PlasmoidExecutionConfigType() override; static QString typeId(); QString id() const override { return typeId(); } QString name() const override; QList configPages() const override; QIcon icon() const override; bool canLaunch( const QUrl &file ) const override; bool canLaunch(KDevelop::ProjectBaseItem* item) const override; void configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const override; void configureLaunchFromCmdLineArguments(KConfigGroup config, const QStringList& args) const override; QMenu* launcherSuggestions() override; private: QList factoryList; public slots: void suggestionTriggered(); }; #endif diff --git a/languages/clang/codegen/adaptsignatureaction.cpp b/languages/clang/codegen/adaptsignatureaction.cpp index f1b1066cd6..3cba4ce5ad 100644 --- a/languages/clang/codegen/adaptsignatureaction.cpp +++ b/languages/clang/codegen/adaptsignatureaction.cpp @@ -1,129 +1,129 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "adaptsignatureaction.h" #include "codegenhelper.h" #include "../duchain/duchainutils.h" #include "../util/clangdebug.h" #include #include #include #include #include #include #include #include using namespace KDevelop; AdaptSignatureAction::AdaptSignatureAction(const DeclarationId& definitionId, ReferencedTopDUContext definitionContext, const Signature& oldSignature, const Signature& newSignature, bool editingDefinition, const QList& renameActions) : m_otherSideId(definitionId) , m_otherSideTopContext(definitionContext) , m_oldSignature(oldSignature) , m_newSignature(newSignature) , m_editingDefinition(editingDefinition) , m_renameActions(renameActions) { } AdaptSignatureAction::~AdaptSignatureAction() { qDeleteAll(m_renameActions); } QString AdaptSignatureAction::description() const { return m_editingDefinition ? i18n("Update declaration signature") : i18n("Update definition signature"); } QString AdaptSignatureAction::toolTip() const { DUChainReadLocker lock; auto declaration = m_otherSideId.getDeclaration(m_otherSideTopContext.data()); if (!declaration) { return {}; } return i18n("Update %1 signature\nfrom: %2\nto: %3", m_editingDefinition ? i18n("declaration") : i18n("definition"), CodegenHelper::makeSignatureString(declaration, m_oldSignature, m_editingDefinition), CodegenHelper::makeSignatureString(declaration, m_newSignature, !m_editingDefinition)); } void AdaptSignatureAction::execute() { ENSURE_CHAIN_NOT_LOCKED DUChainReadLocker lock; IndexedString url = m_otherSideTopContext->url(); lock.unlock(); m_otherSideTopContext = DUChain::self()->waitForUpdate(url, TopDUContext::AllDeclarationsContextsAndUses); if (!m_otherSideTopContext) { clangDebug() << "failed to update" << url.str(); return; } lock.lock(); Declaration* otherSide = m_otherSideId.getDeclaration(m_otherSideTopContext.data()); if (!otherSide) { clangDebug() << "could not find definition"; return; } DUContext* functionContext = DUChainUtils::getFunctionContext(otherSide); if (!functionContext) { clangDebug() << "no function context"; return; } if (!functionContext || functionContext->type() != DUContext::Function) { clangDebug() << "no correct function context"; return; } DocumentChangeSet changes; KTextEditor::Range parameterRange = ClangIntegration::DUChainUtils::functionSignatureRange(otherSide); QString newText = CodegenHelper::makeSignatureString(otherSide, m_newSignature, !m_editingDefinition); if (!m_editingDefinition) { // append a newline after the method signature in case the method definition follows newText += QLatin1Char('\n'); } DocumentChange changeParameters(functionContext->url(), parameterRange, QString(), newText); lock.unlock(); changeParameters.m_ignoreOldText = true; changes.addChange(changeParameters); changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); if (!result) { - KMessageBox::error(0, i18n("Failed to apply changes: %1", result.m_failureReason)); + KMessageBox::error(nullptr, i18n("Failed to apply changes: %1", result.m_failureReason)); } emit executed(this); foreach(RenameAction * renAct, m_renameActions) { renAct->execute(); } } #include "moc_adaptsignatureaction.cpp" diff --git a/languages/clang/codegen/adaptsignatureassistant.cpp b/languages/clang/codegen/adaptsignatureassistant.cpp index 6724bcf92d..81db656532 100644 --- a/languages/clang/codegen/adaptsignatureassistant.cpp +++ b/languages/clang/codegen/adaptsignatureassistant.cpp @@ -1,320 +1,320 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "adaptsignatureassistant.h" #include #include #include #include #include #include #include #include #include #include "../util/clangdebug.h" using namespace KDevelop; namespace { Declaration *getDeclarationAtCursor(const KTextEditor::Cursor &cursor, const QUrl &documentUrl) { ENSURE_CHAIN_READ_LOCKED ReferencedTopDUContext top(DUChainUtils::standardContextForUrl(documentUrl)); if (!top) { clangDebug() << "no context found for document" << documentUrl; return nullptr; } const auto *context = top->findContextAt(top->transformToLocalRevision(cursor), true); return context->type() == DUContext::Function ? context->owner() : nullptr; } bool isConstructor(const Declaration *functionDecl) { auto classFun = dynamic_cast(functionDecl); return classFun && classFun->isConstructor(); } Signature getDeclarationSignature(const Declaration *functionDecl, const DUContext *functionCtxt, bool includeDefaults) { ENSURE_CHAIN_READ_LOCKED int pos = 0; Signature signature; const AbstractFunctionDeclaration* abstractFunDecl = dynamic_cast(functionDecl); foreach(Declaration * parameter, functionCtxt->localDeclarations()) { signature.defaultParams << (includeDefaults ? abstractFunDecl->defaultParameterForArgument(pos).str() : QString()); signature.parameters << qMakePair(parameter->indexedType(), parameter->identifier().identifier().str()); ++pos; } signature.isConst = functionDecl->abstractType() && functionDecl->abstractType()->modifiers() & AbstractType::ConstModifier; if (!isConstructor(functionDecl)) { if (auto funType = functionDecl->type()) { signature.returnType = IndexedType(funType->returnType()); } } return signature; } } AdaptSignatureAssistant::AdaptSignatureAssistant(ILanguageSupport* supportedLanguage) : StaticAssistant(supportedLanguage) { } QString AdaptSignatureAssistant::title() const { return i18n("Adapt Signature"); } void AdaptSignatureAssistant::reset() { doHide(); clearActions(); m_editingDefinition = {}; m_declarationName = {}; m_otherSideId = DeclarationId(); m_otherSideTopContext = {}; m_otherSideContext = {}; m_oldSignature = {}; m_document = nullptr; m_view.clear(); } void AdaptSignatureAssistant::textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, const QString& removedText) { reset(); m_document = doc; m_lastEditPosition = invocationRange.end(); KTextEditor::Range sigAssistRange = invocationRange; if (!removedText.isEmpty()) { sigAssistRange.setRange(sigAssistRange.start(), sigAssistRange.start()); } DUChainReadLocker lock(DUChain::lock(), 300); if (!lock.locked()) { clangDebug() << "failed to lock duchain in time"; return; } KTextEditor::Range simpleInvocationRange = KTextEditor::Range(sigAssistRange); Declaration* funDecl = getDeclarationAtCursor(simpleInvocationRange.start(), m_document->url()); if (!funDecl || !funDecl->type()) { clangDebug() << "No function at cursor"; return; } /* TODO: Port? if(QtFunctionDeclaration* classFun = dynamic_cast(funDecl)) { if (classFun->isSignal()) { // do not offer to change signature of a signal, as the implementation will be generated by moc return; } } */ - Declaration* otherSide = 0; + Declaration* otherSide = nullptr; FunctionDefinition* definition = dynamic_cast(funDecl); if (definition) { m_editingDefinition = true; otherSide = definition->declaration(); } else if ((definition = FunctionDefinition::definition(funDecl))) { m_editingDefinition = false; otherSide = definition; } if (!otherSide) { clangDebug() << "no other side for signature found"; return; } m_otherSideContext = DUContextPointer(DUChainUtils::getFunctionContext(otherSide)); if (!m_otherSideContext) { clangDebug() << "no context for other side found"; return; } m_declarationName = funDecl->identifier(); m_otherSideId = otherSide->id(); m_otherSideTopContext = ReferencedTopDUContext(otherSide->topContext()); m_oldSignature = getDeclarationSignature(otherSide, m_otherSideContext.data(), true); //Schedule an update, to make sure the ranges match DUChain::self()->updateContextForUrl(m_otherSideTopContext->url(), TopDUContext::AllDeclarationsAndContexts); } bool AdaptSignatureAssistant::isUseful() const { return !m_declarationName.isEmpty() && m_otherSideId.isValid() && !actions().isEmpty(); } bool AdaptSignatureAssistant::getSignatureChanges(const Signature& newSignature, QList& oldPositions) const { bool changed = false; for (int i = 0; i < newSignature.parameters.size(); ++i) { oldPositions.append(-1); } for (int curNewParam = newSignature.parameters.size() - 1; curNewParam >= 0; --curNewParam) { int foundAt = -1; for (int curOldParam = m_oldSignature.parameters.size() - 1; curOldParam >= 0; --curOldParam) { if (newSignature.parameters[curNewParam].first != m_oldSignature.parameters[curOldParam].first) { continue; //Different type == different parameters } if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second || curOldParam == curNewParam) { //given the same type and either the same position or the same name, it's (probably) the same argument foundAt = curOldParam; if (newSignature.parameters[curNewParam].second != m_oldSignature.parameters[curOldParam].second || curOldParam != curNewParam) { changed = true; //Either the name changed at this position, or position of this name has changed } if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second) { break; //Found an argument with the same name and type, no need to look further } //else: position/type match, but name match will trump, allowing: (int i=0, int j=1) => (int j=1, int i=0) } } if (foundAt < 0) { changed = true; } oldPositions[curNewParam] = foundAt; } if (newSignature.parameters.size() != m_oldSignature.parameters.size()) { changed = true; } if (newSignature.isConst != m_oldSignature.isConst) { changed = true; } if (newSignature.returnType != m_oldSignature.returnType) { changed = true; } return changed; } void AdaptSignatureAssistant::setDefaultParams(Signature& newSignature, const QList& oldPositions) const { bool hadDefaultParam = false; for (int i = 0; i < newSignature.defaultParams.size(); ++i) { const auto oldPos = oldPositions[i]; if (oldPos == -1) { // default-initialize new argument if we encountered a previous default param if (hadDefaultParam) { newSignature.defaultParams[i] = QStringLiteral("{} /* TODO */"); } } else { newSignature.defaultParams[i] = m_oldSignature.defaultParams[oldPos]; hadDefaultParam = hadDefaultParam || !newSignature.defaultParams[i].isEmpty(); } } } QList AdaptSignatureAssistant::getRenameActions(const Signature &newSignature, const QList &oldPositions) const { ENSURE_CHAIN_READ_LOCKED QList renameActions; if (!m_otherSideContext) { return renameActions; } for (int i = newSignature.parameters.size() - 1; i >= 0; --i) { if (oldPositions[i] == -1) { continue; //new parameter } Declaration *renamedDecl = m_otherSideContext->localDeclarations()[oldPositions[i]]; if (newSignature.parameters[i].second != m_oldSignature.parameters[oldPositions[i]].second) { QMap > uses = renamedDecl->uses(); if (!uses.isEmpty()) { renameActions << new RenameAction(renamedDecl->identifier(), newSignature.parameters[i].second, RevisionedFileRanges::convert(uses)); } } } return renameActions; } void AdaptSignatureAssistant::updateReady(const KDevelop::IndexedString& document, const KDevelop::ReferencedTopDUContext& top) { if (!top || !m_document || document.toUrl() != m_document->url() || top->url() != IndexedString(m_document->url())) { return; } clearActions(); DUChainReadLocker lock; Declaration *functionDecl = getDeclarationAtCursor(m_lastEditPosition, m_document->url()); if (!functionDecl || functionDecl->identifier() != m_declarationName) { clangDebug() << "No function found at" << m_document->url() << m_lastEditPosition; return; } DUContext *functionCtxt = DUChainUtils::getFunctionContext(functionDecl); if (!functionCtxt) { clangDebug() << "No function context found for" << functionDecl->toString(); return; } #if 0 // TODO: Port if (QtFunctionDeclaration * classFun = dynamic_cast(functionDecl)) { if (classFun->isSignal()) { // do not offer to change signature of a signal, as the implementation will be generated by moc return; } } #endif //ParseJob having finished, get the signature that was modified Signature newSignature = getDeclarationSignature(functionDecl, functionCtxt, false); //Check for changes between m_oldSignature and newSignature, use oldPositions to store old<->new param index mapping QList oldPositions; if (!getSignatureChanges(newSignature, oldPositions)) { reset(); clangDebug() << "no changes to signature"; return; //No changes to signature } QList renameActions; if (m_editingDefinition) { setDefaultParams(newSignature, oldPositions); //restore default parameters before updating the declarations } else { renameActions = getRenameActions(newSignature, oldPositions); //rename as needed when updating the definition } IAssistantAction::Ptr action(new AdaptSignatureAction(m_otherSideId, m_otherSideTopContext, m_oldSignature, newSignature, m_editingDefinition, renameActions)); connect(action.data(), &IAssistantAction::executed, this, &AdaptSignatureAssistant::reset); addAction(action); emit actionsChanged(); } KTextEditor::Range AdaptSignatureAssistant::displayRange() const { if (!m_document) { return {}; } auto s = m_lastEditPosition; KTextEditor::Range ran = {s.line(), 0, s.line(), m_document->lineLength(s.line())}; qDebug() << "display range:" << ran; return ran; } #include "moc_adaptsignatureassistant.cpp" diff --git a/languages/clang/codegen/clangrefactoring.h b/languages/clang/codegen/clangrefactoring.h index 47d8cef4be..c4572bbe8e 100644 --- a/languages/clang/codegen/clangrefactoring.h +++ b/languages/clang/codegen/clangrefactoring.h @@ -1,56 +1,56 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef SIMPLEREFACTORING_H #define SIMPLEREFACTORING_H #include "clangprivateexport.h" #include namespace KDevelop { class Context; class ContextMenuExtension; class Declaration; } class KDEVCLANGPRIVATE_EXPORT ClangRefactoring : public KDevelop::BasicRefactoring { Q_OBJECT public: - explicit ClangRefactoring(QObject* parent = 0); + explicit ClangRefactoring(QObject* parent = nullptr); void fillContextMenu(KDevelop::ContextMenuExtension& extension, KDevelop::Context* context) override; QString moveIntoSource(const KDevelop::IndexedDeclaration& iDecl); public slots: void executeMoveIntoSourceAction(); private: bool validCandidateToMoveIntoSource(KDevelop::Declaration* decl); }; #endif diff --git a/languages/clang/codegen/codegenhelper.cpp b/languages/clang/codegen/codegenhelper.cpp index 05e94adf4c..55dda98501 100644 --- a/languages/clang/codegen/codegenhelper.cpp +++ b/languages/clang/codegen/codegenhelper.cpp @@ -1,469 +1,469 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codegenhelper.h" #include "adaptsignatureaction.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { IndexedTypeIdentifier stripPrefixIdentifiers(const IndexedTypeIdentifier& id, const QualifiedIdentifier& strip); Identifier stripPrefixIdentifiers(const Identifier& id, const QualifiedIdentifier& strip) { Identifier ret(id); ret.clearTemplateIdentifiers(); for (unsigned int a = 0; a < id.templateIdentifiersCount(); ++a) { ret.appendTemplateIdentifier(stripPrefixIdentifiers(id.templateIdentifier(a), strip)); } return ret; } IndexedTypeIdentifier stripPrefixIdentifiers(const IndexedTypeIdentifier& id, const QualifiedIdentifier& strip) { QualifiedIdentifier oldId(id.identifier().identifier()); QualifiedIdentifier qid; int commonPrefix = 0; for (; commonPrefix < oldId.count() - 1 && commonPrefix < strip.count(); ++commonPrefix) { if (strip.at(commonPrefix).toString() != oldId.at(commonPrefix).toString()) { break; } } for (int a = commonPrefix; a < oldId.count(); ++a) { qid.push(stripPrefixIdentifiers(oldId.at(a), strip)); } IndexedTypeIdentifier ret(id); ret.setIdentifier(qid); return ret; } int reservedIdentifierCount(const QString &name) { QStringList l = name.split(QLatin1String("::")); int ret = 0; foreach(const QString &s, l) if (s.startsWith(QLatin1Char('_'))) { ++ret; } return ret; } uint buildIdentifierForType(const AbstractType::Ptr& type, IndexedTypeIdentifier& id, uint pointerLevel, TopDUContext* top) { if (!type) { return pointerLevel; } TypePtr refType = type.cast(); if (refType) { id.setIsReference(true); if (refType->modifiers() & AbstractType::ConstModifier) { id.setIsConstant(true); } return buildIdentifierForType(refType->baseType(), id, pointerLevel, top); } TypePtr pointerType = type.cast(); if (pointerType) { ++pointerLevel; uint maxPointerLevel = buildIdentifierForType(pointerType->baseType(), id, pointerLevel, top); if (type->modifiers() & AbstractType::ConstModifier) { id.setIsConstPointer(maxPointerLevel - pointerLevel, true); } if (static_cast(id.pointerDepth()) < pointerLevel) { id.setPointerDepth(pointerLevel); } return maxPointerLevel; } AbstractType::Ptr useTypeText = type; if (type->modifiers() & AbstractType::ConstModifier) { //Remove the 'const' modifier, as it will be added to the type-identifier below useTypeText = IndexedType(type).abstractType(); useTypeText->setModifiers(useTypeText->modifiers() & (~AbstractType::ConstModifier)); } id.setIdentifier(QualifiedIdentifier(useTypeText->toString(), true)); if (type->modifiers() & AbstractType::ConstModifier) { id.setIsConstant(true); } if (type->modifiers() & AbstractType::VolatileModifier) { id.setIsVolatile(true); } return pointerLevel; } IndexedTypeIdentifier identifierForType(const AbstractType::Ptr& type, TopDUContext* top) { IndexedTypeIdentifier ret; buildIdentifierForType(type, ret, 0, top); return ret; } IndexedTypeIdentifier removeTemplateParameters(const IndexedTypeIdentifier& identifier, int behindPosition); Identifier removeTemplateParameters(const Identifier& id, int behindPosition) { Identifier ret(id); ret.clearTemplateIdentifiers(); for (unsigned int a = 0; a < id.templateIdentifiersCount(); ++a) { IndexedTypeIdentifier replacement = removeTemplateParameters(id.templateIdentifier(a), behindPosition); if (( int ) a < behindPosition) { ret.appendTemplateIdentifier(replacement); } else { ret.appendTemplateIdentifier(IndexedTypeIdentifier(QualifiedIdentifier(QStringLiteral("...")))); break; } } return ret; } IndexedTypeIdentifier removeTemplateParameters(const IndexedTypeIdentifier& identifier, int behindPosition) { IndexedTypeIdentifier ret(identifier); QualifiedIdentifier oldId(identifier.identifier().identifier()); QualifiedIdentifier qid; for (int a = 0; a < oldId.count(); ++a) { qid.push(removeTemplateParameters(oldId.at(a), behindPosition)); } ret.setIdentifier(qid); return ret; } IndexedType removeConstModifier(const IndexedType& indexedType) { AbstractType::Ptr type = indexedType.abstractType(); type->setModifiers(type->modifiers() & (~AbstractType::ConstModifier)); return type->indexed(); } AbstractType::Ptr shortenTypeForViewing(const AbstractType::Ptr& type) { struct ShortenAliasExchanger : public TypeExchanger { AbstractType::Ptr exchange(const AbstractType::Ptr& type) override { if (!type) { return type; } AbstractType::Ptr newType(type->clone()); TypeAliasType::Ptr alias = type.cast(); if (alias) { //If the aliased type has less involved template arguments, prefer it AbstractType::Ptr shortenedTarget = exchange(alias->type()); if (shortenedTarget && shortenedTarget->toString().count(QLatin1Char('<')) < alias->toString().count(QLatin1Char('<')) && reservedIdentifierCount(shortenedTarget->toString()) <= reservedIdentifierCount(alias->toString())) { shortenedTarget->setModifiers(shortenedTarget->modifiers() | alias->modifiers()); return shortenedTarget; } } newType->exchangeTypes(this); return newType; } }; ShortenAliasExchanger exchanger; return exchanger.exchange(type); } ///Returns a type that has all template types replaced with DelayedType's that have their template default parameters stripped away, ///and all scope prefixes removed that are redundant within the given context ///The returned type should not actively be used in the type-system, but rather only for displaying. AbstractType::Ptr stripType(const AbstractType::Ptr& type, DUContext* ctx) { if (!type) { return AbstractType::Ptr(); } struct ShortenTemplateDefaultParameter : public TypeExchanger { DUContext* ctx; ShortenTemplateDefaultParameter(DUContext* _ctx) : ctx(_ctx) { Q_ASSERT(ctx); } AbstractType::Ptr exchange(const AbstractType::Ptr& type) override { if (!type) { return type; } AbstractType::Ptr newType(type->clone()); if (const IdentifiedType * idType = dynamic_cast(type.data())) { Declaration* decl = idType->declaration(ctx->topContext()); if (!decl) { return type; } QualifiedIdentifier newTypeName; #if 0 // from oldcpp if (TemplateDeclaration * tempDecl = dynamic_cast(decl)) { if (decl->context()->type() == DUContext::Class && decl->context()->owner()) { //Strip template default-parameters from the parent class AbstractType::Ptr parentType = stripType(decl->context()->owner()->abstractType(), ctx); if (parentType) { newTypeName = QualifiedIdentifier(parentType->toString(), true); } } if (newTypeName.isEmpty()) { newTypeName = decl->context()->scopeIdentifier(true); } Identifier currentId; if (!idType->qualifiedIdentifier().isEmpty()) { currentId = idType->qualifiedIdentifier().last(); } currentId.clearTemplateIdentifiers(); InstantiationInformation instantiationInfo = tempDecl->instantiatedWith().information(); InstantiationInformation newInformation(instantiationInfo); newInformation.templateParametersList().clear(); for (uint neededParameters = 0; neededParameters < instantiationInfo.templateParametersSize(); ++neededParameters) { newInformation.templateParametersList().append(instantiationInfo.templateParameters()[neededParameters]); AbstractType::Ptr niceParam = stripType(instantiationInfo.templateParameters()[neededParameters].abstractType(), ctx); if (niceParam) { currentId.appendTemplateIdentifier(IndexedTypeIdentifier(niceParam->toString(), true)); // debug() << "testing param" << niceParam->toString(); } if (tempDecl->instantiate(newInformation, ctx->topContext()) == decl) { // debug() << "got full instantiation"; break; } } newTypeName.push(currentId); } else { newTypeName = decl->qualifiedIdentifier(); } #endif newTypeName = decl->qualifiedIdentifier(); //Strip unneded prefixes of the scope QualifiedIdentifier candidate = newTypeName; while (candidate.count() > 1) { candidate = candidate.mid(1); QList decls = ctx->findDeclarations(candidate); if (decls.isEmpty()) { continue; // type aliases might be available for nested sub scopes, hence we must not break early } if (decls[0]->kind() != Declaration::Type || removeConstModifier(decls[0]->indexedType()) != removeConstModifier(IndexedType(type))) { break; } newTypeName = candidate; } if (newTypeName == decl->qualifiedIdentifier()) { return type; } DelayedType::Ptr ret(new DelayedType); IndexedTypeIdentifier ti(newTypeName); ti.setIsConstant(type->modifiers() & AbstractType::ConstModifier); ret->setIdentifier(ti); return ret.cast(); } newType->exchangeTypes(this); return newType; } }; ShortenTemplateDefaultParameter exchanger(ctx); return exchanger.exchange(type); } } namespace CodegenHelper { AbstractType::Ptr typeForShortenedString(Declaration* decl) { AbstractType::Ptr type = decl->abstractType(); if (decl->isTypeAlias()) { if (type.cast()) { type = type.cast()->type(); } } if (decl->isFunctionDeclaration()) { FunctionType::Ptr funType = decl->type(); if (!funType) { return AbstractType::Ptr(); } type = funType->returnType(); } return type; } QString shortenedTypeString(Declaration* decl, DUContext* ctx, int desiredLength, const QualifiedIdentifier& stripPrefix) { return shortenedTypeString(typeForShortenedString(decl), ctx, desiredLength, stripPrefix); } QString simplifiedTypeString(const AbstractType::Ptr& type, DUContext* visibilityFrom) { return shortenedTypeString(type, visibilityFrom, 100000); } QString shortenedTypeString(const AbstractType::Ptr& type, DUContext* ctx, int desiredLength, const QualifiedIdentifier& stripPrefix) { return shortenedTypeIdentifier(type, ctx, desiredLength, stripPrefix).toString(); } IndexedTypeIdentifier shortenedTypeIdentifier(const AbstractType::Ptr& type_, DUContext* ctx, int desiredLength, const QualifiedIdentifier& stripPrefix) { bool isReference = false; bool isRValue = false; auto type = type_; if (const auto& refType = type.cast()) { isReference = true; type = refType->baseType(); isRValue = refType->isRValue(); } type = shortenTypeForViewing(type); if (ctx) { type = stripType(type, ctx); } if (!type) { return IndexedTypeIdentifier(); } - IndexedTypeIdentifier identifier = identifierForType(type, ctx ? ctx->topContext() : 0); + IndexedTypeIdentifier identifier = identifierForType(type, ctx ? ctx->topContext() : nullptr); identifier = stripPrefixIdentifiers(identifier, stripPrefix); if (isReference) { identifier.setIsReference(true); } if (isRValue) { identifier.setIsRValue(true); } int removeTemplateParametersFrom = 10; while (identifier.toString().length() > desiredLength * 3 && removeTemplateParametersFrom >= 0) { --removeTemplateParametersFrom; identifier = removeTemplateParameters(identifier, removeTemplateParametersFrom); } return identifier; } QString makeSignatureString(const KDevelop::Declaration* functionDecl, const Signature& signature, const bool editingDefinition) { if (!functionDecl || !functionDecl->internalContext()) { return {}; } const auto visibilityFrom = functionDecl->internalContext()->parentContext(); if (!visibilityFrom) { return {}; } QString ret; if (!editingDefinition) { auto classMember = dynamic_cast(functionDecl); if (classMember && classMember->isStatic()) { ret += QLatin1String("static "); } } // constructors don't have a return type if (signature.returnType.isValid()) { ret += CodegenHelper::simplifiedTypeString(signature.returnType.abstractType(), visibilityFrom); ret += QLatin1Char(' '); } ret += editingDefinition ? functionDecl->qualifiedIdentifier().toString() : functionDecl->identifier().toString(); ret += QLatin1Char('('); int pos = 0; foreach(const ParameterItem &item, signature.parameters) { if (pos != 0) { ret += QLatin1String(", "); } AbstractType::Ptr type = item.first.abstractType(); QString arrayAppendix; ArrayType::Ptr arrayType; while ((arrayType = type.cast())) { type = arrayType->elementType(); //note: we have to prepend since we iterate from outside, i.e. from right to left. if (arrayType->dimension()) { arrayAppendix.prepend(QStringLiteral("[%1]").arg(arrayType->dimension())); } else { // dimensionless arrayAppendix.prepend(QLatin1String("[]")); } } ret += CodegenHelper::simplifiedTypeString(type, visibilityFrom); if (!item.second.isEmpty()) { ret += QLatin1Char(' ') + item.second; } ret += arrayAppendix; if (signature.defaultParams.size() > pos && !signature.defaultParams[pos].isEmpty()) { ret += QLatin1String(" = ") + signature.defaultParams[pos]; } ++pos; } ret += QLatin1Char(')'); if (signature.isConst) { ret += QLatin1String(" const"); } return ret; } } diff --git a/languages/clang/duchain/builder.cpp b/languages/clang/duchain/builder.cpp index b338105bc3..7a58e3222d 100644 --- a/languages/clang/duchain/builder.cpp +++ b/languages/clang/duchain/builder.cpp @@ -1,1519 +1,1519 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * Copyright 2015 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "builder.h" #include "util/clangdebug.h" #include "templatehelpers.h" #include "cursorkindtraits.h" #include "clangducontext.h" #include "macrodefinition.h" #include "types/classspecializationtype.h" #include "util/clangdebug.h" #include "util/clangutils.h" #include "util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// Turn on for debugging the declaration building #define IF_DEBUG(x) namespace { // TODO: this is ugly, can we find a better alternative? bool jsonTestRun() { static bool runningTest = qEnvironmentVariableIsSet("KDEV_CLANG_JSON_TEST_RUN"); return runningTest; } //BEGIN helpers /** * Find the cursor that cursor @p cursor references * * First tries to get the referenced cursor via clang_getCursorReferenced, * and if that fails, tries to get them via clang_getOverloadedDecl * (which returns the referenced cursor for CXCursor_OverloadedDeclRef, for example) * * @return Valid cursor on success, else null cursor */ CXCursor referencedCursor(CXCursor cursor) { auto referenced = clang_getCursorReferenced(cursor); if (!clang_equalCursors(cursor, referenced)) { return referenced; } // get the first result for now referenced = clang_getOverloadedDecl(cursor, 0); if (!clang_Cursor_isNull(referenced)) { return referenced; } return clang_getNullCursor(); } Identifier makeId(CXCursor cursor) { if (CursorKindTraits::isClassTemplate(cursor.kind)) { // TODO: how to handle functions here? We don't want to add the "real" function arguments here // and there does not seem to be an API to get the template arguments for non-specializations easily // NOTE: using the QString overload of the Identifier ctor here, so that the template name gets parsed return Identifier(ClangString(clang_getCursorDisplayName(cursor)).toString()); } return Identifier(ClangString(clang_getCursorSpelling(cursor)).toIndexed()); } QByteArray makeComment(CXComment comment) { if (Q_UNLIKELY(jsonTestRun())) { auto kind = clang_Comment_getKind(comment); if (kind == CXComment_Text) return ClangString(clang_TextComment_getText(comment)).toByteArray(); QByteArray text; int numChildren = clang_Comment_getNumChildren(comment); for (int i = 0; i < numChildren; ++i) text += makeComment(clang_Comment_getChild(comment, i)); return text; } return ClangString(clang_FullComment_getAsHTML(comment)).toByteArray(); } AbstractType* createDelayedType(CXType type) { auto t = new DelayedType; QString typeName = ClangString(clang_getTypeSpelling(type)).toString(); typeName.remove(QStringLiteral("const ")); typeName.remove(QStringLiteral("volatile ")); t->setIdentifier(IndexedTypeIdentifier(typeName)); return t; } void contextImportDecl(DUContext* context, const DeclarationPointer& decl) { auto top = context->topContext(); if (auto import = decl->logicalInternalContext(top)) { context->addImportedParentContext(import); context->topContext()->updateImportsCache(); } } //END helpers CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data); //BEGIN IdType template struct IdType; template struct IdType::type> { typedef StructureType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef EnumerationType Type; }; template struct IdType::type> { typedef EnumeratorType Type; }; //END IdType //BEGIN DeclType template struct DeclType; template struct DeclType::type> { typedef Declaration Type; }; template struct DeclType::type> { typedef MacroDefinition Type; }; template struct DeclType::type> { typedef ForwardDeclaration Type; }; template struct DeclType::type> { typedef ClassDeclaration Type; }; template struct DeclType::type> { typedef ClassFunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDefinition Type; }; template struct DeclType::type> { typedef NamespaceAliasDeclaration Type; }; template struct DeclType::type> { typedef ClassMemberDeclaration Type; }; //END DeclType //BEGIN CurrentContext struct CurrentContext { CurrentContext(DUContext* context, QSet keepAliveContexts) : context(context) , keepAliveContexts(keepAliveContexts) { DUChainReadLocker lock; previousChildContexts = context->childContexts(); previousChildDeclarations = context->localDeclarations(); } ~CurrentContext() { DUChainWriteLocker lock; foreach (auto childContext, previousChildContexts) { if (!keepAliveContexts.contains(childContext)) { delete childContext; } } qDeleteAll(previousChildDeclarations); if (resortChildContexts) { context->resortChildContexts(); } if (resortLocalDeclarations) { context->resortLocalDeclarations(); } } DUContext* context; // when updating, this contains child contexts of the current parent context QVector previousChildContexts; // when updating, this contains contexts that must not be deleted QSet keepAliveContexts; // when updating, this contains child declarations of the current parent context QVector previousChildDeclarations; bool resortChildContexts = false; bool resortLocalDeclarations = false; }; //END CurrentContext //BEGIN Visitor struct Visitor { explicit Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update); AbstractType *makeType(CXType type, CXCursor parent); AbstractType::Ptr makeAbsType(CXType type, CXCursor parent) { return AbstractType::Ptr(makeType(type, parent)); } //BEGIN dispatch* template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template AbstractType *dispatchType(CXType type, CXCursor cursor) { IF_DEBUG(clangDebug() << "TK:" << type.kind;) auto kdevType = createType(type, cursor); if (kdevType) { setTypeModifiers(type, kdevType); } return kdevType; } //BEGIN dispatch* //BEGIN build* template CXChildVisitResult buildDeclaration(CXCursor cursor); CXChildVisitResult buildUse(CXCursor cursor); CXChildVisitResult buildMacroExpansion(CXCursor cursor); CXChildVisitResult buildCompoundStatement(CXCursor cursor); CXChildVisitResult buildCXXBaseSpecifier(CXCursor cursor); CXChildVisitResult buildParmDecl(CXCursor cursor); //END build* //BEGIN create* template DeclType* createDeclarationCommon(CXCursor cursor, const Identifier& id) { auto range = ClangHelpers::cursorSpellingNameRange(cursor, id); if (id.isEmpty()) { // This is either an anonymous function parameter e.g.: void f(int); // Or anonymous struct/class/union e.g.: struct {} anonymous; // Set empty range for it range.end = range.start; } // check if cursor is inside a macro expansion auto clangRange = clang_Cursor_getSpellingNameRange(cursor, 0, 0); unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(clangRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (m_macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); // Set empty ranges for declarations inside macro expansion if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } if (m_update) { const IndexedIdentifier indexedId(id); DUChainWriteLocker lock; auto it = m_parentContext->previousChildDeclarations.begin(); while (it != m_parentContext->previousChildDeclarations.end()) { auto decl = dynamic_cast(*it); if (decl && decl->indexedIdentifier() == indexedId) { decl->setRange(range); m_parentContext->resortLocalDeclarations = true; setDeclData(cursor, decl); m_cursorToDeclarationCache[cursor] = decl; m_parentContext->previousChildDeclarations.erase(it); return decl; } ++it; } } auto decl = new DeclType(range, nullptr); decl->setIdentifier(id); m_cursorToDeclarationCache[cursor] = decl; setDeclData(cursor, decl); { DUChainWriteLocker lock; decl->setContext(m_parentContext->context); } return decl; } template Declaration* createDeclaration(CXCursor cursor, const Identifier& id, DUContext *context) { auto decl = createDeclarationCommon(cursor, id); auto type = createType(cursor); DUChainWriteLocker lock; if (context) decl->setInternalContext(context); setDeclType(decl, type); setDeclInCtxtData(cursor, decl); return decl; } template DUContext* createContext(CXCursor cursor, const QualifiedIdentifier& scopeId = {}) { // wtf: why is the DUContext API requesting a QID when it needs a plain Id?! // see: testNamespace auto range = ClangRange(clang_getCursorExtent(cursor)).toRangeInRevision(); DUChainWriteLocker lock; if (m_update) { const IndexedQualifiedIdentifier indexedScopeId(scopeId); auto it = m_parentContext->previousChildContexts.begin(); while (it != m_parentContext->previousChildContexts.end()) { auto ctx = *it; if (ctx->type() == Type && ctx->indexedLocalScopeIdentifier() == indexedScopeId) { ctx->setRange(range); m_parentContext->resortChildContexts = true; m_parentContext->previousChildContexts.erase(it); return ctx; } ++it; } } //TODO: (..type, id..) constructor for DUContext? auto context = new ClangNormalDUContext(range, m_parentContext->context); context->setType(Type); context->setLocalScopeIdentifier(scopeId); if (Type == DUContext::Other || Type == DUContext::Function) context->setInSymbolTable(false); if (CK == CXCursor_CXXMethod) { CXCursor semParent = clang_getCursorSemanticParent(cursor); // only import the semantic parent if it differs from the lexical parent if (!clang_Cursor_isNull(semParent) && !clang_equalCursors(semParent, clang_getCursorLexicalParent(cursor))) { auto semParentDecl = findDeclaration(semParent); if (semParentDecl) { contextImportDecl(context, semParentDecl); } } } return context; } template = dummy> AbstractType *createType(CXType, CXCursor) { // TODO: would be nice to instantiate a ConstantIntegralType here and set a value if possible // but unfortunately libclang doesn't offer API to that // also see http://marc.info/?l=cfe-commits&m=131609142917881&w=2 return new IntegralType(CursorKindTraits::integralType(TK)); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ptr = new PointerType; ptr->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ptr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto arr = new ArrayType; arr->setDimension((TK == CXType_IncompleteArray || TK == CXType_VariableArray || TK == CXType_DependentSizedArray) ? 0 : clang_getArraySize(type)); arr->setElementType(makeAbsType(clang_getArrayElementType(type), parent)); return arr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ref = new ReferenceType; ref->setIsRValue(type.kind == CXType_RValueReference); ref->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ref; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto func = new FunctionType; func->setReturnType(makeAbsType(clang_getResultType(type), parent)); const int numArgs = clang_getNumArgTypes(type); for (int i = 0; i < numArgs; ++i) { func->addArgument(makeAbsType(clang_getArgType(type, i), parent)); } if (clang_isFunctionTypeVariadic(type)) { auto type = new DelayedType; static const auto id = IndexedTypeIdentifier(QStringLiteral("...")); type->setIdentifier(id); type->setKind(DelayedType::Unresolved); func->addArgument(AbstractType::Ptr(type)); } return func; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { DeclarationPointer decl = findDeclaration(clang_getTypeDeclaration(type)); DUChainReadLocker lock; if (!decl) { // probably a forward-declared type decl = ClangHelpers::findForwardDeclaration(type, m_parentContext->context, parent); } if (clang_Type_getNumTemplateArguments(type) != -1) { return createClassTemplateSpecializationType(type, decl); } auto t = new StructureType; if (decl) { t->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user t->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(ClangString(clang_getTypeSpelling(type)).toString())))); } return t; } template = dummy> AbstractType *createType(CXType type, CXCursor) { auto t = new EnumerationType; setIdTypeDecl(clang_getTypeDeclaration(type), t); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto t = new TypeAliasType; CXCursor location = clang_getTypeDeclaration(type); t->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(location), parent)); setIdTypeDecl(location, t); return t; } - template = dummy> + template = dummy> AbstractType *createType(CXType, CXCursor /*parent*/) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QLatin1String(CursorKindTraits::delayedTypeName(TK))); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor /*parent*/) { return createDelayedType(type); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto numTA = clang_Type_getNumTemplateArguments(type); // TODO: We should really expose more types to libclang! if (numTA != -1 && ClangString(clang_getTypeSpelling(type)).toString().contains(QLatin1Char('<'))) { return createClassTemplateSpecializationType(type); } // Maybe it's the ElaboratedType. E.g.: "struct Type foo();" or "NS::Type foo();" or "void foo(enum Enum e);" e.t.c. auto oldType = type; type = clang_getCanonicalType(type); bool isElaboratedType = type.kind != CXType_FunctionProto && type.kind != CXType_FunctionNoProto && type.kind != CXType_Unexposed && type.kind != CXType_Invalid && type.kind != CXType_Record; return !isElaboratedType ? createDelayedType(oldType) : makeType(type, parent); } template = dummy> typename IdType::Type *createType(CXCursor) { return new typename IdType::Type; } template = dummy> EnumeratorType *createType(CXCursor cursor) { auto type = new EnumeratorType; type->setValue(clang_getEnumConstantDeclUnsignedValue(cursor)); return type; } template = dummy> TypeAliasType *createType(CXCursor cursor) { auto type = new TypeAliasType; type->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(cursor), cursor)); return type; } template = dummy> AbstractType* createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); #if CINDEX_VERSION_MINOR < 31 if (clangType.kind == CXType_Unexposed) { // Clang sometimes can return CXType_Unexposed for CXType_FunctionProto kind. E.g. if it's AttributedType. return dispatchType(clangType, cursor); } #endif return makeType(clangType, cursor); } template = dummy> AbstractType *createType(CXCursor) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QStringLiteral("Label")); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); return makeType(clangType, cursor); } #if CINDEX_VERSION_MINOR >= 31 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto deducedType = clang_getCanonicalType(type); bool isDeduced = deducedType.kind != CXType_Invalid && deducedType.kind != CXType_Auto; return !isDeduced ? createDelayedType(type) : makeType(deducedType, parent); } #endif #if CINDEX_VERSION_MINOR >= 34 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto underyingType = clang_Type_getNamedType(type); return makeType(underyingType, parent); } #endif /// @param declaration an optional declaration that will be associated with created type AbstractType* createClassTemplateSpecializationType(CXType type, const DeclarationPointer declaration = {}) { auto numTA = clang_Type_getNumTemplateArguments(type); Q_ASSERT(numTA != -1); auto typeDecl = clang_getTypeDeclaration(type); if (!declaration && typeDecl.kind == CXCursor_NoDeclFound) { // clang_getTypeDeclaration doesn't handle all types, fall back to delayed type... return createDelayedType(type); } QStringList typesStr; QString tStr = ClangString(clang_getTypeSpelling(type)).toString(); ParamIterator iter(QStringLiteral("<>"), tStr); while (iter) { typesStr.append(*iter); ++iter; } auto cst = new ClassSpecializationType; for (int i = 0; i < numTA; i++) { auto argumentType = clang_Type_getTemplateArgumentAsType(type, i); AbstractType::Ptr currentType; if (argumentType.kind == CXType_Invalid) { if(i >= typesStr.size()){ currentType = createDelayedType(argumentType); } else { auto t = new DelayedType; t->setIdentifier(IndexedTypeIdentifier(typesStr[i])); currentType = t; } } else { currentType = makeType(argumentType, typeDecl); } if (currentType) { cst->addParameter(currentType->indexed()); } } auto decl = declaration ? declaration : findDeclaration(typeDecl); DUChainReadLocker lock; if (decl) { cst->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user Identifier id(tStr); id.clearTemplateIdentifiers(); cst->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(id)))); } return cst; } //END create* //BEGIN setDeclData template void setDeclData(CXCursor cursor, Declaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, MacroDefinition* decl) const; template void setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template void setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, FunctionDefinition *decl) const; template void setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const; //END setDeclData //BEGIN setDeclInCtxtData template void setDeclInCtxtData(CXCursor, Declaration*) { //No-op } template void setDeclInCtxtData(CXCursor cursor, ClassFunctionDeclaration *decl) { // HACK to retrieve function-constness // This looks like a bug in Clang -- In theory setTypeModifiers should take care of setting the const modifier // however, clang_isConstQualifiedType() for TK == CXType_FunctionProto always returns false // TODO: Debug further auto type = decl->abstractType(); if (type) { if (clang_CXXMethod_isConst(cursor)) { type->setModifiers(type->modifiers() | AbstractType::ConstModifier); decl->setAbstractType(type); } } } template void setDeclInCtxtData(CXCursor cursor, FunctionDefinition *def) { setDeclInCtxtData(cursor, static_cast(def)); const CXCursor canon = clang_getCanonicalCursor(cursor); if (auto decl = findDeclaration(canon)) { def->setDeclaration(decl.data()); } } //END setDeclInCtxtData //BEGIN setDeclType template void setDeclType(Declaration *decl, typename IdType::Type *type) { setDeclType(decl, static_cast(type)); setDeclType(decl, static_cast(type)); } template void setDeclType(Declaration *decl, IdentifiedType *type) { type->setDeclaration(decl); } template void setDeclType(Declaration *decl, AbstractType *type) { decl->setAbstractType(AbstractType::Ptr(type)); } //END setDeclType template void setTypeModifiers(CXType type, AbstractType* kdevType) const; const CXFile m_file; const IncludeFileContexts &m_includes; DeclarationPointer findDeclaration(CXCursor cursor) const; void setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const; std::unordered_map> m_uses; /// At these location offsets (cf. @ref clang_getExpansionLocation) we encountered macro expansions QSet m_macroExpansionLocations; mutable QHash m_cursorToDeclarationCache; CurrentContext *m_parentContext; const bool m_update; }; //BEGIN setTypeModifiers template void Visitor::setTypeModifiers(CXType type, AbstractType* kdevType) const { quint64 modifiers = 0; if (clang_isConstQualifiedType(type)) { modifiers |= AbstractType::ConstModifier; } if (clang_isVolatileQualifiedType(type)) { modifiers |= AbstractType::VolatileModifier; } if (TK == CXType_Short || TK == CXType_UShort) { modifiers |= AbstractType::ShortModifier; } if (TK == CXType_Long || TK == CXType_LongDouble || TK == CXType_ULong) { modifiers |= AbstractType::LongModifier; } if (TK == CXType_LongLong || TK == CXType_ULongLong) { modifiers |= AbstractType::LongLongModifier; } if (TK == CXType_SChar) { modifiers |= AbstractType::SignedModifier; } if (TK == CXType_UChar || TK == CXType_UInt || TK == CXType_UShort || TK == CXType_UInt128 || TK == CXType_ULong || TK == CXType_ULongLong) { modifiers |= AbstractType::UnsignedModifier; } kdevType->setModifiers(modifiers); } //END setTypeModifiers //BEGIN dispatchCursor template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { const bool decision = CursorKindTraits::isClass(clang_getCursorKind(parent)); return decision ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) const bool isDefinition = clang_isCursorDefinition(cursor); return isDefinition ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) // We may end up visiting the same cursor twice in some cases // see discussion on https://git.reviewboard.kde.org/r/119526/ // TODO: Investigate why this is happening in libclang if ((CursorKindTraits::isClass(CK) || CK == CXCursor_EnumDecl) && clang_getCursorKind(parent) == CXCursor_VarDecl) { return CXChildVisit_Continue; } constexpr bool isClassMember = IsInClass == Decision::True; constexpr bool isDefinition = IsDefinition == Decision::True; // always build a context for class templates and functions, otherwise we "leak" // the function/template parameter declarations into the surrounding context, // which can lead to interesting bugs, like https://bugs.kde.org/show_bug.cgi?id=368067 constexpr bool hasContext = isDefinition || CursorKindTraits::isFunction(CK) || CursorKindTraits::isClassTemplate(CK); return buildDeclaration::Type, hasContext>(cursor); } //END dispatchCursor //BEGIN setDeclData template void Visitor::setDeclData(CXCursor cursor, Declaration *decl, bool setComment) const { if (setComment) decl->setComment(makeComment(clang_Cursor_getParsedComment(cursor))); if (CursorKindTraits::isAliasType(CK)) { decl->setIsTypeAlias(true); } if (CK == CXCursor_Namespace) decl->setKind(Declaration::Namespace); if (CK == CXCursor_EnumDecl || CK == CXCursor_EnumConstantDecl || CursorKindTraits::isClass(CK) || CursorKindTraits::isAliasType(CK)) decl->setKind(Declaration::Type); int isAlwaysDeprecated; clang_getCursorPlatformAvailability(cursor, &isAlwaysDeprecated, nullptr, nullptr, nullptr, nullptr, 0); decl->setDeprecated(isAlwaysDeprecated); } template void Visitor::setDeclData(CXCursor cursor, MacroDefinition* decl) const { setDeclData(cursor, static_cast(decl)); if (m_update) { decl->clearParameters(); } auto unit = clang_Cursor_getTranslationUnit(cursor); auto range = clang_getCursorExtent(cursor); // TODO: Quite lacking API in libclang here. // No way to find out if this macro is function-like or not // cf. http://clang.llvm.org/doxygen/classclang_1_1MacroInfo.html // And no way to get the actual definition text range // Should be quite easy to expose that in libclang, though // Let' still get some basic support for this and parse on our own, it's not that difficult const QString contents = QString::fromUtf8(ClangUtils::getRawContents(unit, range)); const int firstOpeningParen = contents.indexOf(QLatin1Char('(')); const int firstWhitespace = contents.indexOf(QLatin1Char(' ')); const bool isFunctionLike = (firstOpeningParen != -1) && (firstOpeningParen < firstWhitespace); decl->setFunctionLike(isFunctionLike); // now extract the actual definition text int start = -1; if (isFunctionLike) { const int closingParen = findClose(contents, firstOpeningParen); if (closingParen != -1) { start = closingParen + 2; // + ')' + ' ' // extract macro function parameters const QString parameters = contents.mid(firstOpeningParen, closingParen - firstOpeningParen + 1); ParamIterator paramIt(QStringLiteral("():"), parameters, 0); while (paramIt) { decl->addParameter(IndexedString(*paramIt)); ++paramIt; } } } else { start = firstWhitespace + 1; // + ' ' } if (start == -1) { // unlikely: invalid macro definition, insert the complete #define statement decl->setDefinition(IndexedString(QLatin1String("#define ") + contents)); } else if (start < contents.size()) { decl->setDefinition(IndexedString(contents.mid(start))); } // else: macro has no body => leave the definition text empty } template void Visitor::setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); //A CXCursor_VarDecl in a class is static (otherwise it'd be a CXCursor_FieldDecl) if (CK == CXCursor_VarDecl) decl->setStatic(true); decl->setAccessPolicy(CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor))); #if CINDEX_VERSION_MINOR >= 31 decl->setMutable(clang_CXXField_isMutable(cursor)); #endif #if CINDEX_VERSION_MINOR >= 30 if (!jsonTestRun()) { auto offset = clang_Cursor_getOffsetOfField(cursor); if (offset >= 0) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignedTo = clang_Type_getAlignOf(type); const auto byteOffset = offset / 8; const auto bitOffset = offset % 8; const QString byteOffsetStr = i18np("1 Byte", "%1 Bytes", byteOffset); const QString bitOffsetStr = bitOffset ? i18np("1 Bit", "%1 Bits", bitOffset) : QString(); const QString offsetStr = bitOffset ? i18nc("%1: bytes, %2: bits", "%1, %2", byteOffsetStr, bitOffsetStr) : byteOffsetStr; decl->setComment(decl->comment() + i18n("

offset in parent: %1; " "size: %2 Bytes; " "aligned to: %3 Bytes

", offsetStr, sizeOf, alignedTo).toUtf8()); } } #endif } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { CXCursorKind kind = clang_getTemplateCursorKind(cursor); switch (kind) { case CXCursor_UnionDecl: setDeclData(cursor, decl); break; case CXCursor_StructDecl: setDeclData(cursor, decl); break; case CXCursor_ClassDecl: setDeclData(cursor, decl); break; default: Q_ASSERT(false); break; } } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { if (m_update) { decl->clearBaseClasses(); } setDeclData(cursor, static_cast(decl)); if (CK == CXCursor_UnionDecl) decl->setClassType(ClassDeclarationData::Union); if (CK == CXCursor_StructDecl) decl->setClassType(ClassDeclarationData::Struct); if (clang_isCursorDefinition(cursor)) { decl->setDeclarationIsDefinition(true); } if (!jsonTestRun()) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0 && alignOf >= 0) { decl->setComment(decl->comment() + i18n("

size: %1 Bytes; " "aligned to: %2 Bytes

", sizeOf, alignOf).toUtf8()); } } } template void Visitor::setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const { if (m_update) { decl->clearDefaultParameters(); } // No setDeclData(...) here: AbstractFunctionDeclaration is an interface // TODO: Can we get the default arguments directly from Clang? // also see http://clang-developers.42468.n3.nabble.com/Finding-default-value-for-function-argument-with-clang-c-API-td4036919.html const QVector defaultArgs = ClangUtils::getDefaultArguments(cursor, ClangUtils::MinimumSize); foreach (const QString& defaultArg, defaultArgs) { decl->addDefaultParameter(IndexedString(defaultArg)); } } template void Visitor::setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl)); decl->setAbstract(clang_CXXMethod_isPureVirtual(cursor)); decl->setStatic(clang_CXXMethod_isStatic(cursor)); decl->setVirtual(clang_CXXMethod_isVirtual(cursor)); const auto qtAttribute = ClangUtils::specialQtAttributes(cursor); decl->setIsSignal(qtAttribute == ClangUtils::QtSignalAttribute); decl->setIsSlot(qtAttribute == ClangUtils::QtSlotAttribute); } template void Visitor::setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, FunctionDefinition *decl) const { bool setComment = clang_equalCursors(clang_getCanonicalCursor(cursor), cursor); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor parent, CXClientData data) -> CXChildVisitResult { if (clang_getCursorKind(cursor) == CXCursor_NamespaceRef) { const auto id = QualifiedIdentifier(ClangString(clang_getCursorSpelling(cursor)).toString()); reinterpret_cast(data)->setImportIdentifier(id); return CXChildVisit_Break; } else { return visitCursor(cursor, parent, data); } }, decl); } //END setDeclData //BEGIN build* template CXChildVisitResult Visitor::buildDeclaration(CXCursor cursor) { auto id = makeId(cursor); if (CK == CXCursor_UnexposedDecl && id.isEmpty()) { // skip unexposed declarations that have no identifier set // this is useful to skip e.g. friend declarations return CXChildVisit_Recurse; } IF_DEBUG(clangDebug() << "id:" << id << "- CK:" << CK << "- DeclType:" << typeid(DeclType).name() << "- hasContext:" << hasContext;) // Code path for class declarations that may be defined "out-of-line", e.g. // "SomeNameSpace::SomeClass {};" QScopedPointer helperContext; if (CursorKindTraits::isClass(CK) || CursorKindTraits::isFunction(CK)) { const auto lexicalParent = clang_getCursorLexicalParent(cursor); const auto semanticParent = clang_getCursorSemanticParent(cursor); const bool isOutOfLine = !clang_equalCursors(lexicalParent, semanticParent); if (isOutOfLine) { const QString scope = ClangUtils::getScope(cursor); auto context = createContext(cursor, QualifiedIdentifier(scope)); helperContext.reset(new CurrentContext(context, m_parentContext->keepAliveContexts)); } } // if helperContext is null, this is a no-op PushValue pushCurrent(m_parentContext, helperContext.isNull() ? m_parentContext : helperContext.data()); if (hasContext) { auto context = createContext(cursor, QualifiedIdentifier(id)); createDeclaration(cursor, id, context); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } createDeclaration(cursor, id, nullptr); return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildParmDecl(CXCursor cursor) { return buildDeclaration::Type, false>(cursor); } CXChildVisitResult Visitor::buildUse(CXCursor cursor) { m_uses[m_parentContext->context].push_back(cursor); return cursor.kind == CXCursor_DeclRefExpr || cursor.kind == CXCursor_MemberRefExpr ? CXChildVisit_Recurse : CXChildVisit_Continue; } CXChildVisitResult Visitor::buildMacroExpansion(CXCursor cursor) { buildUse(cursor); // cache that we encountered a macro expansion at this location unsigned int offset; clang_getSpellingLocation(clang_getCursorLocation(cursor), nullptr, nullptr, nullptr, &offset); m_macroExpansionLocations << offset; return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCompoundStatement(CXCursor cursor) { if (m_parentContext->context->type() == DUContext::Function) { auto context = createContext(cursor); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCXXBaseSpecifier(CXCursor cursor) { auto currentContext = m_parentContext->context; bool virtualInherited = clang_isVirtualBase(cursor); Declaration::AccessPolicy access = CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor)); auto classDeclCursor = clang_getCursorReferenced(cursor); auto decl = findDeclaration(classDeclCursor); if (!decl) { // this happens for templates with template-dependent base classes e.g. - dunno whether we can/should do more here clangDebug() << "failed to find declaration for base specifier:" << clang_getCursorDisplayName(cursor); return CXChildVisit_Recurse; } DUChainWriteLocker lock; contextImportDecl(currentContext, decl); auto classDecl = dynamic_cast(currentContext->owner()); Q_ASSERT(classDecl); classDecl->addBaseClass({decl->indexedType(), access, virtualInherited}); return CXChildVisit_Recurse; } //END build* DeclarationPointer Visitor::findDeclaration(CXCursor cursor) const { const auto it = m_cursorToDeclarationCache.constFind(cursor); if (it != m_cursorToDeclarationCache.constEnd()) { return *it; } // fallback, and cache result auto decl = ClangHelpers::findDeclaration(cursor, m_includes); m_cursorToDeclarationCache.insert(cursor, decl); return decl; } void Visitor::setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const { DeclarationPointer decl = findDeclaration(typeCursor); DUChainReadLocker lock; if (decl) { idType->setDeclaration(decl.data()); } } AbstractType *Visitor::makeType(CXType type, CXCursor parent) { #define UseKind(TypeKind) case TypeKind: return dispatchType(type, parent) switch (type.kind) { UseKind(CXType_Void); UseKind(CXType_Bool); UseKind(CXType_Short); UseKind(CXType_UShort); UseKind(CXType_Int); UseKind(CXType_UInt); UseKind(CXType_Long); UseKind(CXType_ULong); UseKind(CXType_LongLong); UseKind(CXType_ULongLong); UseKind(CXType_Float); UseKind(CXType_LongDouble); UseKind(CXType_Double); UseKind(CXType_Char_U); UseKind(CXType_Char_S); UseKind(CXType_UChar); UseKind(CXType_SChar); UseKind(CXType_Char16); UseKind(CXType_Char32); UseKind(CXType_Pointer); UseKind(CXType_BlockPointer); UseKind(CXType_MemberPointer); UseKind(CXType_ObjCObjectPointer); UseKind(CXType_ConstantArray); UseKind(CXType_VariableArray); UseKind(CXType_IncompleteArray); UseKind(CXType_DependentSizedArray); UseKind(CXType_LValueReference); UseKind(CXType_RValueReference); UseKind(CXType_FunctionNoProto); UseKind(CXType_FunctionProto); UseKind(CXType_Record); UseKind(CXType_Enum); UseKind(CXType_Typedef); UseKind(CXType_Int128); UseKind(CXType_UInt128); UseKind(CXType_Vector); UseKind(CXType_Unexposed); UseKind(CXType_WChar); UseKind(CXType_ObjCInterface); UseKind(CXType_ObjCId); UseKind(CXType_ObjCClass); UseKind(CXType_ObjCSel); UseKind(CXType_NullPtr); #if CINDEX_VERSION_MINOR >= 31 UseKind(CXType_Auto); #endif #if CINDEX_VERSION_MINOR >= 34 UseKind(CXType_Elaborated); #endif case CXType_Invalid: return nullptr; default: qCWarning(KDEV_CLANG) << "Unhandled type:" << type.kind << clang_getTypeSpelling(type); return nullptr; } #undef UseKind } RangeInRevision rangeInRevisionForUse(CXCursor cursor, CXCursorKind referencedCursorKind, CXSourceRange useRange, const QSet& macroExpansionLocations) { auto range = ClangRange(useRange).toRangeInRevision(); //TODO: Fix in clang, happens for operator<<, operator<, probably more if (clang_Range_isNull(useRange)) { useRange = clang_getCursorExtent(cursor); range = ClangRange(useRange).toRangeInRevision(); } if (referencedCursorKind == CXCursor_ConversionFunction) { range.end = range.start; range.start.column--; } // For uses inside macro expansions, create an empty use range at the spelling location // the empty range is required in order to not "overlap" the macro expansion range // and to allow proper navigation for the macro expansion // also see JSON test 'macros.cpp' if (clang_getCursorKind(cursor) != CXCursor_MacroExpansion) { unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(useRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } } else { // Workaround for wrong use range returned by clang for macro expansions const auto contents = ClangUtils::getRawContents(clang_Cursor_getTranslationUnit(cursor), useRange); const int firstOpeningParen = contents.indexOf('('); if (firstOpeningParen != -1) { range.end.column = range.start.column + firstOpeningParen; range.end.line = range.start.line; } } return range; } Visitor::Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) : m_file(file) , m_includes(includes) , m_parentContext(nullptr) , m_update(update) { CXCursor tuCursor = clang_getTranslationUnitCursor(tu); auto top = includes[file]; // when updating, this contains child contexts that should be kept alive // even when they are not part of the AST anymore // this is required for some assistants, such as the signature assistant QSet keepAliveContexts; { DUChainReadLocker lock; foreach (const auto& problem, top->problems()) { const auto& desc = problem->description(); if (desc.startsWith(QLatin1String("Return type of out-of-line definition of '")) && desc.endsWith(QLatin1String("' differs from that in the declaration"))) { auto ctx = top->findContextAt(problem->range().start); // keep the context and its parents alive // this also keeps declarations in this context alive while (ctx) { keepAliveContexts << ctx; ctx = ctx->parentContext(); } } } } CurrentContext parent(top, keepAliveContexts); m_parentContext = &parent; clang_visitChildren(tuCursor, &visitCursor, this); if (m_update) { DUChainWriteLocker lock; top->deleteUsesRecursively(); } for (const auto &contextUses : m_uses) { for (const auto &cursor : contextUses.second) { auto referenced = referencedCursor(cursor); if (clang_Cursor_isNull(referenced)) { continue; } // first, try the canonical referenced cursor // this is important to get the correct function declaration e.g. auto canonicalReferenced = clang_getCanonicalCursor(referenced); auto used = findDeclaration(canonicalReferenced); if (!used) { // if the above failed, try the non-canonicalized version as a fallback // this is required for friend declarations that occur before // the real declaration. there, the canonical cursor points to // the friend declaration which is not what we are looking for used = findDeclaration(referenced); } if (!used) { // as a last resort, try to resolve the forward declaration DUChainReadLocker lock; DeclarationPointer decl = ClangHelpers::findForwardDeclaration(clang_getCursorType(referenced), contextUses.first, referenced); used = decl; if (!used) { continue; } } #if CINDEX_VERSION_MINOR >= 29 if (clang_Cursor_getNumTemplateArguments(referenced) >= 0) { // Ideally, we don't need this, but for function templates clang_getCanonicalCursor returns a function definition // See also the testUsesCreatedForDeclarations test DUChainReadLocker lock; used = DUChainUtils::declarationForDefinition(used.data()); } #endif const auto useRange = clang_getCursorReferenceNameRange(cursor, 0, 0); const auto range = rangeInRevisionForUse(cursor, referenced.kind, useRange, m_macroExpansionLocations); DUChainWriteLocker lock; auto usedIndex = top->indexForUsedDeclaration(used.data()); contextUses.first->createUse(usedIndex, range); } } } //END Visitor CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data) { Visitor *visitor = static_cast(data); const auto kind = clang_getCursorKind(cursor); auto location = clang_getCursorLocation(cursor); CXFile file; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); // don't skip MemberRefExpr with invalid location, see also: // http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-May/043114.html if (!ClangUtils::isFileEqual(file, visitor->m_file) && (file || kind != CXCursor_MemberRefExpr)) { return CXChildVisit_Continue; } #define UseCursorKind(CursorKind, ...) case CursorKind: return visitor->dispatchCursor(__VA_ARGS__); switch (kind) { UseCursorKind(CXCursor_UnexposedDecl, cursor, parent); UseCursorKind(CXCursor_StructDecl, cursor, parent); UseCursorKind(CXCursor_UnionDecl, cursor, parent); UseCursorKind(CXCursor_ClassDecl, cursor, parent); UseCursorKind(CXCursor_EnumDecl, cursor, parent); UseCursorKind(CXCursor_FieldDecl, cursor, parent); UseCursorKind(CXCursor_EnumConstantDecl, cursor, parent); UseCursorKind(CXCursor_FunctionDecl, cursor, parent); UseCursorKind(CXCursor_VarDecl, cursor, parent); UseCursorKind(CXCursor_TypeAliasDecl, cursor, parent); UseCursorKind(CXCursor_TypedefDecl, cursor, parent); UseCursorKind(CXCursor_CXXMethod, cursor, parent); UseCursorKind(CXCursor_Namespace, cursor, parent); UseCursorKind(CXCursor_NamespaceAlias, cursor, parent); UseCursorKind(CXCursor_Constructor, cursor, parent); UseCursorKind(CXCursor_Destructor, cursor, parent); UseCursorKind(CXCursor_ConversionFunction, cursor, parent); UseCursorKind(CXCursor_TemplateTypeParameter, cursor, parent); UseCursorKind(CXCursor_NonTypeTemplateParameter, cursor, parent); UseCursorKind(CXCursor_TemplateTemplateParameter, cursor, parent); UseCursorKind(CXCursor_FunctionTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplatePartialSpecialization, cursor, parent); UseCursorKind(CXCursor_ObjCInterfaceDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryDecl, cursor, parent); UseCursorKind(CXCursor_ObjCProtocolDecl, cursor, parent); UseCursorKind(CXCursor_ObjCPropertyDecl, cursor, parent); UseCursorKind(CXCursor_ObjCIvarDecl, cursor, parent); UseCursorKind(CXCursor_ObjCInstanceMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCClassMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCImplementationDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryImplDecl, cursor, parent); UseCursorKind(CXCursor_MacroDefinition, cursor, parent); UseCursorKind(CXCursor_LabelStmt, cursor, parent); case CXCursor_TypeRef: case CXCursor_TemplateRef: case CXCursor_NamespaceRef: case CXCursor_MemberRef: case CXCursor_LabelRef: case CXCursor_OverloadedDeclRef: case CXCursor_VariableRef: case CXCursor_DeclRefExpr: case CXCursor_MemberRefExpr: case CXCursor_ObjCClassRef: return visitor->buildUse(cursor); case CXCursor_MacroExpansion: return visitor->buildMacroExpansion(cursor); case CXCursor_CompoundStmt: return visitor->buildCompoundStatement(cursor); case CXCursor_CXXBaseSpecifier: return visitor->buildCXXBaseSpecifier(cursor); case CXCursor_ParmDecl: return visitor->buildParmDecl(cursor); default: return CXChildVisit_Recurse; } } } namespace Builder { void visit(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) { Visitor visitor(tu, file, includes, update); } } diff --git a/languages/clang/duchain/clangducontext.cpp b/languages/clang/duchain/clangducontext.cpp index cb28239100..ab40a94aa9 100644 --- a/languages/clang/duchain/clangducontext.cpp +++ b/languages/clang/duchain/clangducontext.cpp @@ -1,62 +1,62 @@ /* * Copyright 2014 Kevin Funk * * 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 "clangducontext.h" #include "duchain/navigationwidget.h" #include "../util/clangdebug.h" #include #include using namespace KDevelop; template<> QWidget* ClangTopDUContext::createNavigationWidget(Declaration* decl, TopDUContext* topContext, const QString& htmlPrefix, const QString& htmlSuffix, KDevelop::AbstractNavigationWidget::DisplayHints hints) const { if (!decl) { const QUrl u = url().toUrl(); IncludeItem item; item.pathNumber = -1; item.name = u.fileName(); item.isDirectory = false; item.basePath = u.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); return new ClangNavigationWidget(item, TopDUContextPointer(topContext ? topContext : this->topContext()), htmlPrefix, htmlSuffix, hints); } return new ClangNavigationWidget(DeclarationPointer(decl), hints); } template<> QWidget* ClangNormalDUContext::createNavigationWidget(Declaration* decl, TopDUContext* /*topContext*/, const QString& /*htmlPrefix*/, const QString& /*htmlSuffix*/, KDevelop::AbstractNavigationWidget::DisplayHints hints) const { if (!decl) { clangDebug() << "no declaration, not returning navigationwidget"; - return 0; + return nullptr; } return new ClangNavigationWidget(DeclarationPointer(decl), hints); } DUCHAIN_DEFINE_TYPE_WITH_DATA(ClangNormalDUContext, DUContextData) DUCHAIN_DEFINE_TYPE_WITH_DATA(ClangTopDUContext, TopDUContextData) diff --git a/languages/clang/duchain/clangindex.cpp b/languages/clang/duchain/clangindex.cpp index b967158968..c971da6017 100644 --- a/languages/clang/duchain/clangindex.cpp +++ b/languages/clang/duchain/clangindex.cpp @@ -1,149 +1,149 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "clangindex.h" #include "clangpch.h" #include "clangparsingenvironment.h" #include "documentfinderhelpers.h" #include #include #include #include #include #include #include using namespace KDevelop; ClangIndex::ClangIndex() // NOTE: We don't exclude PCH declarations. That way we could retrieve imports manually, as clang_getInclusions returns nothing on reparse with CXTranslationUnit_PrecompiledPreamble flag. : m_index(clang_createIndex(0 /*Exclude PCH Decls*/, qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DIAGS") /*Display diags*/)) { // demote the priority of the clang parse threads to reduce potential UI lockups // but the code completion threads still retain their normal priority to return // the results as quickly as possible clang_CXIndex_setGlobalOptions(m_index, clang_CXIndex_getGlobalOptions(m_index) | CXGlobalOpt_ThreadBackgroundPriorityForIndexing); } CXIndex ClangIndex::index() const { return m_index; } QSharedPointer ClangIndex::pch(const ClangParsingEnvironment& environment) { const auto& pchInclude = environment.pchInclude(); if (!pchInclude.isValid()) { return {}; } UrlParseLock pchLock(IndexedString(pchInclude.pathOrUrl())); static const QString pchExt = QStringLiteral(".pch"); if (QFile::exists(pchInclude.toLocalFile() + pchExt)) { QReadLocker lock(&m_pchLock); auto pch = m_pch.constFind(pchInclude); if (pch != m_pch.constEnd()) { return pch.value(); } } auto pch = QSharedPointer::create(environment, this); QWriteLocker lock(&m_pchLock); m_pch.insert(pchInclude, pch); return pch; } ClangIndex::~ClangIndex() { clang_disposeIndex(m_index); } IndexedString ClangIndex::translationUnitForUrl(const IndexedString& url) { { // try explicit pin data first QMutexLocker lock(&m_mappingMutex); auto tu = m_tuForUrl.find(url); if (tu != m_tuForUrl.end()) { if (!QFile::exists(tu.value().str())) { // TU doesn't exist, unpin m_tuForUrl.erase(tu); return url; } return tu.value(); } } // if no explicit pin data is available, follow back the duchain import chain { DUChainReadLocker lock; TopDUContext* top = DUChain::self()->chainForDocument(url); if (top) { TopDUContext* tuTop = top; QSet visited; while(true) { visited.insert(tuTop); - TopDUContext* next = NULL; + TopDUContext* next = nullptr; auto importers = tuTop->indexedImporters(); foreach(IndexedDUContext ctx, importers) { if (ctx.data()) { next = ctx.data()->topContext(); break; } } if (!next || visited.contains(next)) { break; } tuTop = next; } if (tuTop != top) { return tuTop->url(); } } } // otherwise, fallback to a simple buddy search for headers if (ClangHelpers::isHeader(url.str())) { foreach(const QUrl& buddy, DocumentFinderHelpers::getPotentialBuddies(url.toUrl(), false)) { const QString buddyPath = buddy.toLocalFile(); if (QFile::exists(buddyPath)) { return IndexedString(buddyPath); } } } return url; } void ClangIndex::pinTranslationUnitForUrl(const IndexedString& tu, const IndexedString& url) { QMutexLocker lock(&m_mappingMutex); m_tuForUrl.insert(url, tu); } void ClangIndex::unpinTranslationUnitForUrl(const IndexedString& url) { QMutexLocker lock(&m_mappingMutex); m_tuForUrl.remove(url); } diff --git a/languages/clang/duchain/navigationwidget.cpp b/languages/clang/duchain/navigationwidget.cpp index f05e5b1d96..7b240e81da 100644 --- a/languages/clang/duchain/navigationwidget.cpp +++ b/languages/clang/duchain/navigationwidget.cpp @@ -1,137 +1,137 @@ /* * This file is part of KDevelop * Copyright 2014 Milian Wolff * Copyright 2014 Kevin Funk * * 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 "navigationwidget.h" #include "macronavigationcontext.h" #include "types/classspecializationtype.h" #include #include #include using namespace KDevelop; class DeclarationNavigationContext : public AbstractDeclarationNavigationContext { public: - DeclarationNavigationContext(const DeclarationPointer& decl, AbstractNavigationContext* previousContext = 0) + DeclarationNavigationContext(const DeclarationPointer& decl, AbstractNavigationContext* previousContext = nullptr) : AbstractDeclarationNavigationContext(decl, {}, previousContext) { } void htmlIdentifiedType(AbstractType::Ptr type, const IdentifiedType* idType) override { AbstractDeclarationNavigationContext::htmlIdentifiedType(type, idType); if (auto cst = dynamic_cast(type.data())) { modifyHtml() += QStringLiteral("< ").toHtmlEscaped(); bool first = true; for (const auto& type : cst->templateParameters()) { if (first) { first = false; } else { modifyHtml() += QStringLiteral(", "); } eventuallyMakeTypeLinks(type.abstractType()); } modifyHtml() += QStringLiteral(" >").toHtmlEscaped(); } } }; class IncludeNavigationContext : public KDevelop::AbstractIncludeNavigationContext { public: IncludeNavigationContext(const KDevelop::IncludeItem& item, KDevelop::TopDUContextPointer topContext); protected: bool filterDeclaration(KDevelop::Declaration* decl) override; }; IncludeNavigationContext::IncludeNavigationContext(const IncludeItem& item, KDevelop::TopDUContextPointer topContext) : AbstractIncludeNavigationContext(item, topContext, StandardParsingEnvironment) {} bool IncludeNavigationContext::filterDeclaration(Declaration* decl) { QString declId = decl->identifier().identifier().str(); //filter out forward-declarations and macro-expansions without a range //And filter out declarations with reserved identifiers return !decl->qualifiedIdentifier().toString().isEmpty() && !decl->range().isEmpty() && !decl->isForwardDeclaration() && !(declId.startsWith(QLatin1String("__")) || (declId.startsWith(QLatin1Char('_')) && declId.length() > 1 && declId[1].isUpper()) ); } ClangNavigationWidget::ClangNavigationWidget(const DeclarationPointer& declaration, KDevelop::AbstractNavigationWidget::DisplayHints hints) : AbstractNavigationWidget() { setDisplayHints(hints); if (auto macro = declaration.dynamicCast()) { initBrowser(200); //The first context is registered so it is kept alive by the shared-pointer mechanism m_startContext = NavigationContextPointer(new MacroNavigationContext(macro)); setContext( m_startContext ); } else { initBrowser(400); //The first context is registered so it is kept alive by the shared-pointer mechanism m_startContext = NavigationContextPointer(new DeclarationNavigationContext(declaration)); setContext( m_startContext ); } } ClangNavigationWidget::ClangNavigationWidget(const MacroDefinition::Ptr& macro, const KDevelop::DocumentCursor& expansionLocation, KDevelop::AbstractNavigationWidget::DisplayHints hints) : AbstractNavigationWidget() { setDisplayHints(hints); initBrowser(400); //The first context is registered so it is kept alive by the shared-pointer mechanism m_startContext = NavigationContextPointer(new MacroNavigationContext(macro, expansionLocation)); setContext(m_startContext); } ClangNavigationWidget::ClangNavigationWidget(const IncludeItem& includeItem, KDevelop::TopDUContextPointer topContext, const QString& htmlPrefix, const QString& htmlSuffix, KDevelop::AbstractNavigationWidget::DisplayHints hints) : AbstractNavigationWidget() { setDisplayHints(hints); m_topContext = topContext; initBrowser(200); //The first context is registered so it is kept alive by the shared-pointer mechanism m_startContext = NavigationContextPointer(new IncludeNavigationContext(includeItem, m_topContext)); m_startContext->setPrefixSuffix( htmlPrefix, htmlSuffix ); setContext( m_startContext ); } QString ClangNavigationWidget::shortDescription(const IncludeItem& includeItem) { NavigationContextPointer ctx(new IncludeNavigationContext(includeItem, {})); return ctx->html(true); } diff --git a/languages/clang/tests/test_assistants.cpp b/languages/clang/tests/test_assistants.cpp index 7cfb66065d..2e83859c04 100644 --- a/languages/clang/tests/test_assistants.cpp +++ b/languages/clang/tests/test_assistants.cpp @@ -1,789 +1,789 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 2014 David Stevens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_assistants.h" #include "codegen/clangrefactoring.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KTextEditor; QTEST_MAIN(TestAssistants) -ForegroundLock *globalTestLock = 0; +ForegroundLock *globalTestLock = nullptr; StaticAssistantsManager *staticAssistantsManager() { return Core::self()->languageController()->staticAssistantsManager(); } void TestAssistants::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral( "*.debug=false\n" "default.debug=true\n" "kdevelop.plugins.clang.debug=true\n" )); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport"), QStringLiteral("kdevproblemreporter")}); TestCore::initialize(); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); Core::self()->sourceFormatterController()->disableSourceFormatting(true); CodeRepresentation::setDiskChangesForbidden(true); globalTestLock = new ForegroundLock; } void TestAssistants::cleanupTestCase() { Core::self()->cleanup(); delete globalTestLock; - globalTestLock = 0; + globalTestLock = nullptr; } static QUrl createFile(const QString& fileContents, QString extension, int id) { static QTemporaryDir tempDirA; Q_ASSERT(tempDirA.isValid()); static QDir dirA(tempDirA.path()); QFile file(dirA.filePath(QString::number(id) + extension)); file.open(QIODevice::WriteOnly | QIODevice::Text); file.write(fileContents.toUtf8()); file.close(); return QUrl::fromLocalFile(file.fileName()); } class Testbed { public: enum TestDoc { HeaderDoc, CppDoc }; enum IncludeBehavior { NoAutoInclude, AutoInclude, }; Testbed(const QString& headerContents, const QString& cppContents, IncludeBehavior include = AutoInclude) : m_includeBehavior(include) { static int i = 0; int id = i; ++i; m_headerDocument.url = createFile(headerContents,".h",id); m_headerDocument.textDoc = openDocument(m_headerDocument.url); QString preamble; if (include == AutoInclude) preamble = QStringLiteral("#include \"%1\"\n").arg(m_headerDocument.url.toLocalFile()); m_cppDocument.url = createFile(preamble + cppContents,".cpp",id); m_cppDocument.textDoc = openDocument(m_cppDocument.url); } ~Testbed() { Core::self()->documentController()->documentForUrl(m_cppDocument.url)->textDocument(); Core::self()->documentController()->documentForUrl(m_cppDocument.url)->close(KDevelop::IDocument::Discard); Core::self()->documentController()->documentForUrl(m_headerDocument.url)->close(KDevelop::IDocument::Discard); } void changeDocument(TestDoc which, Range where, const QString& what, bool waitForUpdate = false) { TestDocument document; if (which == CppDoc) { document = m_cppDocument; if (m_includeBehavior == AutoInclude) { where = Range(where.start().line() + 1, where.start().column(), where.end().line() + 1, where.end().column()); //The include adds a line } } else { document = m_headerDocument; } // we must activate the document, otherwise we cannot find the correct active view auto kdevdoc = ICore::self()->documentController()->documentForUrl(document.url); QVERIFY(kdevdoc); ICore::self()->documentController()->activateDocument(kdevdoc); auto view = ICore::self()->documentController()->activeTextDocumentView(); QCOMPARE(view->document(), document.textDoc); view->setSelection(where); view->removeSelectionText(); view->setCursorPosition(where.start()); view->insertText(what); QCoreApplication::processEvents(); if (waitForUpdate) { DUChain::self()->waitForUpdate(IndexedString(document.url), KDevelop::TopDUContext::AllDeclarationsAndContexts); } } QString documentText(TestDoc which) { if (which == CppDoc) { //The CPP document text shouldn't include the autogenerated include line QString text = m_cppDocument.textDoc->text(); return m_includeBehavior == AutoInclude ? text.mid(text.indexOf("\n") + 1) : text; } else return m_headerDocument.textDoc->text(); } QString includeFileName() const { return m_headerDocument.url.toLocalFile(); } KTextEditor::Document *document(TestDoc which) const { return Core::self()->documentController()->documentForUrl( which == CppDoc ? m_cppDocument.url : m_headerDocument.url)->textDocument(); } private: struct TestDocument { QUrl url; Document *textDoc; }; Document* openDocument(const QUrl& url) { Core::self()->documentController()->openDocument(url); DUChain::self()->waitForUpdate(IndexedString(url), KDevelop::TopDUContext::AllDeclarationsAndContexts); return Core::self()->documentController()->documentForUrl(url)->textDocument(); } IncludeBehavior m_includeBehavior; TestDocument m_headerDocument; TestDocument m_cppDocument; }; /** * A StateChange describes an insertion/deletion/replacement and the expected result **/ struct StateChange { StateChange(){}; StateChange(Testbed::TestDoc document, const Range& range, const QString& newText, const QString& result) : document(document) , range(range) , newText(newText) , result(result) { } Testbed::TestDoc document; Range range; QString newText; QString result; }; Q_DECLARE_METATYPE(StateChange) Q_DECLARE_METATYPE(QList) void TestAssistants::testRenameAssistant_data() { QTest::addColumn("fileContents"); QTest::addColumn("oldDeclarationName"); QTest::addColumn >("stateChanges"); QTest::addColumn("finalFileContents"); QTest::newRow("Prepend Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << QList{ StateChange(Testbed::CppDoc, Range(0,12,0,12), "u", "ui"), StateChange(Testbed::CppDoc, Range(0,13,0,13), "z", "uzi"), } << "int foo(int uzi)\n { uzi = 0; return uzi; }"; QTest::newRow("Append Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,13,0,13), "d", "id")) << "int foo(int id)\n { id = 0; return id; }"; QTest::newRow("Replace Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,13), "u", "u")) << "int foo(int u)\n { u = 0; return u; }"; QTest::newRow("Paste Replace") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,15), "abcdefg", "abcdefg")) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; QTest::newRow("Paste Insert") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,14,0,14), "cdef", "abcdefg")) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; QTest::newRow("Letter-by-Letter Prepend") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,12), "a", "ai") << StateChange(Testbed::CppDoc, Range(0,13,0,13), "b", "abi") << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abci") ) << "int foo(int abci)\n { abci = 0; return abci; }"; QTest::newRow("Letter-by-Letter Insert") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abcg") << StateChange(Testbed::CppDoc, Range(0,15,0,15), "d", "abcdg") << StateChange(Testbed::CppDoc, Range(0,16,0,16), "e", "abcdeg") << StateChange(Testbed::CppDoc, Range(0,17,0,17), "f", "abcdefg") ) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; } ProblemPointer findStaticAssistantProblem(const QVector& problems) { const auto renameProblemIt = std::find_if(problems.cbegin(), problems.cend(), [](const ProblemPointer& p) { return dynamic_cast(p.constData()); }); if (renameProblemIt != problems.cend()) return *renameProblemIt; return {}; } void TestAssistants::testRenameAssistant() { QFETCH(QString, fileContents); Testbed testbed("", fileContents); const auto document = testbed.document(Testbed::CppDoc); QVERIFY(document); QExplicitlySharedDataPointer assistant; QFETCH(QString, oldDeclarationName); QFETCH(QList, stateChanges); foreach(StateChange stateChange, stateChanges) { testbed.changeDocument(Testbed::CppDoc, stateChange.range, stateChange.newText, true); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); const auto problem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); if (problem) assistant = problem->solutionAssistant(); if (stateChange.result.isEmpty()) { QVERIFY(!assistant || !assistant->actions().size()); } else { qWarning() << assistant.data() << stateChange.result; QVERIFY(assistant && assistant->actions().size()); RenameAction *r = qobject_cast(assistant->actions().first().data()); QCOMPARE(r->oldDeclarationName(), oldDeclarationName); QCOMPARE(r->newDeclarationName(), stateChange.result); } } if (assistant && assistant->actions().size()) { assistant->actions().first()->execute(); } QFETCH(QString, finalFileContents); QCOMPARE(testbed.documentText(Testbed::CppDoc), finalFileContents); } void TestAssistants::testRenameAssistantUndoRename() { Testbed testbed("", "int foo(int i)\n { i = 0; return i; }"); testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,13), "d", true); const auto document = testbed.document(Testbed::CppDoc); QVERIFY(document); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); auto firstProblem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); QVERIFY(firstProblem); auto assistant = firstProblem->solutionAssistant(); QVERIFY(assistant); QVERIFY(assistant->actions().size() > 0); RenameAction *r = qobject_cast(assistant->actions().first().data()); qWarning() << topCtx->problems() << assistant->actions().first().data() << assistant->actions().size(); QVERIFY(r); // now rename the variable back to its original identifier testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,14), ""); // there should be no assistant anymore QVERIFY(!assistant || assistant->actions().isEmpty()); } const QString SHOULD_ASSIST = "SHOULD_ASSIST"; //An assistant will be visible const QString NO_ASSIST = "NO_ASSIST"; //No assistant visible void TestAssistants::testSignatureAssistant_data() { QTest::addColumn("headerContents"); QTest::addColumn("cppContents"); QTest::addColumn >("stateChanges"); QTest::addColumn("finalHeaderContents"); QTest::addColumn("finalCppContents"); QTest::newRow("change_argument_type") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::HeaderDoc, Range(1,8,1,11), "char", SHOULD_ASSIST)) << "class Foo {\nint bar(char a, char* b, int c = 10); \n};" << "int Foo::bar(char a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("prepend_arg_header") << "class Foo { void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 21, 0, 21), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, int i); };" << "void Foo::bar(char c, int i)\n{}"; QTest::newRow("prepend_arg_cpp") << "class Foo { void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, int i); };" << "void Foo::bar(char c, int i)\n{}"; QTest::newRow("change_default_parameter") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::HeaderDoc, Range(1,29,1,34), "", NO_ASSIST)) << "class Foo {\nint bar(int a, char* b, int c); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("change_function_type") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::CppDoc, Range(0,0,0,3), "char", SHOULD_ASSIST)) << "class Foo {\nchar bar(int a, char* b, int c = 10); \n};" << "char Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("swap_args_definition_side") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::CppDoc, Range(0,13,0,28), "char* b, int a,", SHOULD_ASSIST)) << "class Foo {\nint bar(char* b, int a, int c = 10); \n};" << "int Foo::bar(char* b, int a, int c)\n{ a = c; b = new char; return a + *b; }"; // see https://bugs.kde.org/show_bug.cgi?id=299393 // actually related to the whitespaces in the header... QTest::newRow("change_function_constness") << "class Foo {\nvoid bar(const Foo&) const;\n};" << "void Foo::bar(const Foo&) const\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0,25,0,31), "", SHOULD_ASSIST)) << "class Foo {\nvoid bar(const Foo&);\n};" << "void Foo::bar(const Foo&)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=356179 QTest::newRow("keep_static_cpp") << "class Foo { static void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 19, 0, 19), ", char c", SHOULD_ASSIST)) << "class Foo { static void bar(int i, char c); };" << "void Foo::bar(int i, char c)\n{}"; QTest::newRow("keep_static_header") << "class Foo { static void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 33, 0, 33), ", char c", SHOULD_ASSIST)) << "class Foo { static void bar(int i, char c); };" << "void Foo::bar(int i, char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=356178 QTest::newRow("keep_default_args_cpp_before") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, bool b, int i = 0); };" << "void Foo::bar(char c, bool b, int i)\n{}"; QTest::newRow("keep_default_args_cpp_after") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 27, 0, 27), ", char c", SHOULD_ASSIST)) << "class Foo { void bar(bool b, int i = 0, char c = {} /* TODO */); };" << "void Foo::bar(bool b, int i, char c)\n{}"; QTest::newRow("keep_default_args_header_before") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 29, 0, 29), "char c = 'A', ", SHOULD_ASSIST)) << "class Foo { void bar(bool b, char c = 'A', int i = 0); };" << "void Foo::bar(bool b, char c, int i)\n{}"; QTest::newRow("keep_default_args_header_after") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 38, 0, 38), ", char c = 'A'", SHOULD_ASSIST)) << "class Foo { void bar(bool b, int i = 0, char c = 'A'); };" << "void Foo::bar(bool b, int i, char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=355356 QTest::newRow("no_retval_on_ctor") << "class Foo { Foo(); };" << "Foo::Foo()\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 16, 0, 16), "char c", SHOULD_ASSIST)) << "class Foo { Foo(char c); };" << "Foo::Foo(char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=298511 QTest::newRow("change_return_type_header") << "struct Foo { int bar(); };" << "int Foo::bar()\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 13, 0, 16), "char", SHOULD_ASSIST)) << "struct Foo { char bar(); };" << "char Foo::bar()\n{}"; QTest::newRow("change_return_type_impl") << "struct Foo { int bar(); };" << "int Foo::bar()\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 0, 0, 3), "char", SHOULD_ASSIST)) << "struct Foo { char bar(); };" << "char Foo::bar()\n{}"; } void TestAssistants::testSignatureAssistant() { QFETCH(QString, headerContents); QFETCH(QString, cppContents); Testbed testbed(headerContents, cppContents); QExplicitlySharedDataPointer assistant; QFETCH(QList, stateChanges); foreach (StateChange stateChange, stateChanges) { testbed.changeDocument(stateChange.document, stateChange.range, stateChange.newText, true); const auto document = testbed.document(stateChange.document); QVERIFY(document); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); const auto problem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); if (problem) { assistant = problem->solutionAssistant(); } if (stateChange.result == SHOULD_ASSIST) { #if CINDEX_VERSION_MINOR < 35 QEXPECT_FAIL("change_function_type", "Clang sees that return type of out-of-line definition differs from that in the declaration and won't parse the code...", Abort); QEXPECT_FAIL("change_return_type_impl", "Clang sees that return type of out-of-line definition differs from that in the declaration and won't include the function's AST and thus we never get updated about the new return type...", Abort); #endif QVERIFY(assistant && !assistant->actions().isEmpty()); } else { QVERIFY(!assistant || assistant->actions().isEmpty()); } } if (assistant && !assistant->actions().isEmpty()) assistant->actions().first()->execute(); QFETCH(QString, finalHeaderContents); QFETCH(QString, finalCppContents); QCOMPARE(testbed.documentText(Testbed::HeaderDoc), finalHeaderContents); QCOMPARE(testbed.documentText(Testbed::CppDoc), finalCppContents); } enum UnknownDeclarationAction { NoUnknownDeclarationAction = 0x0, ForwardDecls = 0x1, MissingInclude = 0x2 }; Q_DECLARE_FLAGS(UnknownDeclarationActions, UnknownDeclarationAction) Q_DECLARE_METATYPE(UnknownDeclarationActions) void TestAssistants::testUnknownDeclarationAssistant_data() { QTest::addColumn("headerContents"); QTest::addColumn("globalText"); QTest::addColumn("functionText"); QTest::addColumn("actions"); QTest::newRow("unincluded_struct") << "struct test{};" << "" << "test" << UnknownDeclarationActions(ForwardDecls | MissingInclude); QTest::newRow("forward_declared_struct") << "struct test{};" << "struct test;" << "test *f; f->" << UnknownDeclarationActions(MissingInclude); QTest::newRow("unknown_struct") << "" << "" << "test" << UnknownDeclarationActions(); } void TestAssistants::testUnknownDeclarationAssistant() { QFETCH(QString, headerContents); QFETCH(QString, globalText); QFETCH(QString, functionText); QFETCH(UnknownDeclarationActions, actions); static const auto cppContents = QStringLiteral("%1\nvoid f_u_n_c_t_i_o_n() {\n}"); Testbed testbed(headerContents, cppContents.arg(globalText), Testbed::NoAutoInclude); const auto document = testbed.document(Testbed::CppDoc); QVERIFY(document); const int line = document->lines() - 1; testbed.changeDocument(Testbed::CppDoc, Range(line, 0, line, 0), functionText, true); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); const auto problems = topCtx->problems(); if (actions == NoUnknownDeclarationAction) { QVERIFY(!problems.isEmpty()); return; } auto firstProblem = problems.first(); auto assistant = firstProblem->solutionAssistant(); QVERIFY(assistant); const auto assistantActions = assistant->actions(); QStringList actionDescriptions; for (auto action: assistantActions) { actionDescriptions << action->description(); } { const bool hasForwardDecls = actionDescriptions.contains(QObject::tr("Forward declare as 'struct'")) && actionDescriptions.contains(QObject::tr("Forward declare as 'class'")); QCOMPARE(hasForwardDecls, static_cast(actions & ForwardDecls)); } { auto fileName = testbed.includeFileName(); fileName = fileName.mid(fileName.lastIndexOf('/') + 1); const auto description = QObject::tr("Insert \'%1\'") .arg(QStringLiteral("#include \"%1\"").arg(fileName)); const bool hasMissingInclude = actionDescriptions.contains(description); QCOMPARE(hasMissingInclude, static_cast(actions & MissingInclude)); } } void TestAssistants::testMoveIntoSource() { QFETCH(QString, origHeader); QFETCH(QString, origImpl); QFETCH(QString, newHeader); QFETCH(QString, newImpl); QFETCH(QualifiedIdentifier, id); TestFile header(origHeader, "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" + origImpl, "cpp", &header); { TopDUContext* headerCtx = nullptr; { DUChainReadLocker lock; headerCtx = DUChain::self()->chainForDocument(header.url()); } // Here is a problem: when launching tests one by one, we can reuse the same tmp file for headers. // But because of document chain for header wasn't unloaded properly in previous run we reuse it here // Therefore when using headerCtx->findDeclarations below we find declarations from the previous launch -> tests fail if (headerCtx) { // TODO: Investigate why this chain doesn't get updated when parsing source file DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(headerCtx); } } impl.parse(KDevelop::TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(impl.waitForParsed()); IndexedDeclaration declaration; { DUChainReadLocker lock; auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); auto decls = headerCtx->findDeclarations(id); Q_ASSERT(!decls.isEmpty()); declaration = IndexedDeclaration(decls.first()); QVERIFY(declaration.isValid()); } CodeRepresentation::setDiskChangesForbidden(false); ClangRefactoring refactoring; QCOMPARE(refactoring.moveIntoSource(declaration), QString()); CodeRepresentation::setDiskChangesForbidden(true); QCOMPARE(header.fileContents(), newHeader); QVERIFY(impl.fileContents().endsWith(newImpl)); } void TestAssistants::testMoveIntoSource_data() { QTest::addColumn("origHeader"); QTest::addColumn("origImpl"); QTest::addColumn("newHeader"); QTest::addColumn("newImpl"); QTest::addColumn("id"); const QualifiedIdentifier fooId("foo"); QTest::newRow("globalfunction") << QString("int foo()\n{\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo();\n") << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("staticfunction") << QString("static int foo()\n{\n int i = 0;\n return 0;\n}\n") << QString() << QString("static int foo();\n") << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("funcsameline") << QString("int foo() {\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo();\n") << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("func-comment") << QString("int foo()\n/* foobar */ {\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo()\n/* foobar */;\n") << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("func-comment2") << QString("int foo()\n/*asdf*/\n{\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo()\n/*asdf*/;\n") << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; const QualifiedIdentifier aFooId("a::foo"); QTest::newRow("class-method") << QString("class a {\n int foo(){\n return 0;\n }\n};\n") << QString() << QString("class a {\n int foo();\n};\n") << QString("\nint a::foo() {\n return 0;\n }\n") << aFooId; QTest::newRow("class-method-const") << QString("class a {\n int foo() const\n {\n return 0;\n }\n};\n") << QString() << QString("class a {\n int foo() const;\n};\n") << QString("\nint a::foo() const\n {\n return 0;\n }\n") << aFooId; QTest::newRow("class-method-const-sameline") << QString("class a {\n int foo() const{\n return 0;\n }\n};\n") << QString() << QString("class a {\n int foo() const;\n};\n") << QString("\nint a::foo() const {\n return 0;\n }\n") << aFooId; QTest::newRow("elaborated-type") << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const{\nreturn 0;\n}\n};\n") << QString() << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const;\n};\n") << QString("\nint a::foo(const NS::C c) const {\nreturn 0;\n}\n") << aFooId; QTest::newRow("add-into-namespace") << QString("namespace NS{class a {\nint foo() const {\nreturn 0;\n}\n};\n}") << QString("namespace NS{\n}") << QString("namespace NS{class a {\nint foo() const;\n};\n}") << QString("namespace NS{\n\nint a::foo() const {\nreturn 0;\n}\n}") << QualifiedIdentifier("NS::a::foo"); QTest::newRow("class-template-parameter") << QString(R"( namespace first { template class Test{}; namespace second { template class List; } class MoveIntoSource { public: void f(const second::List>*>& param){} };} )") << QString("") << QString(R"( namespace first { template class Test{}; namespace second { template class List; } class MoveIntoSource { public: void f(const second::List>*>& param); };} )") << QString("namespace first {\nvoid MoveIntoSource::f(const first::second::List< const volatile first::Test< first::second::List< int* > >* >& param) {}}\n\n") << QualifiedIdentifier("first::MoveIntoSource::f"); QTest::newRow("move-unexposed-type") << QString("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i){}") << QString("") << QString("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i);") << QString("void move(std::string i) {}\n") << QualifiedIdentifier("move"); QTest::newRow("move-constructor") << QString("class Class{Class(){}\n};") << QString("") << QString("class Class{Class();\n};") << QString("Class::Class() {}\n") << QualifiedIdentifier("Class::Class"); } diff --git a/languages/clang/tests/test_buddies.cpp b/languages/clang/tests/test_buddies.cpp index d3b9f8c46b..b22c76a6fa 100644 --- a/languages/clang/tests/test_buddies.cpp +++ b/languages/clang/tests/test_buddies.cpp @@ -1,516 +1,516 @@ /*************************************************************************** * Copyright 2011 Martin Heide * * Copyright 2012 Milian Wolff * * * * 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 "test_buddies.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 using namespace KDevelop; #include #include #include Sublime::MainWindow* toSublimeWindow(KParts::MainWindow* window) { Sublime::MainWindow* ret = dynamic_cast(window); Q_ASSERT(ret); return ret; } void TestBuddies::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); TestCore::initialize(); m_documentController = ICore::self()->documentController(); m_uiController = ICore::self()->uiController(); m_sublimeController = m_uiController->controller(); } void TestBuddies::cleanupTestCase() { TestCore::shutdown(); } void TestBuddies::init() { // Make sure we start with an empty document set QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestBuddies::cleanup() { m_documentController->closeAllDocuments(); } void TestBuddies::verifyFilename(Sublime::View *view, const QString& endOfFilename) { QVERIFY(view); if (view) { Sublime::UrlDocument *urlDoc = dynamic_cast(view->document()); QVERIFY(urlDoc); if (urlDoc) { qDebug() << urlDoc->url().toLocalFile() << endOfFilename; QVERIFY(urlDoc->url().toLocalFile().endsWith(endOfFilename)); } } } void TestBuddies::createFile(const QDir& dir, const QString& filename) { QFile file(dir.filePath(filename)); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); file.close(); } void TestBuddies::enableBuddies(bool enable) { { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); uiGroup.writeEntry("TabBarArrangeBuddies", (enable ? 1 : 0)); uiGroup.sync(); } m_sublimeController->loadSettings(); QCOMPARE(m_sublimeController->arrangeBuddies(), enable); } void TestBuddies::enableOpenAfterCurrent(bool enable) { { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); uiGroup.writeEntry("TabBarOpenAfterCurrent", (enable ? 1 : 0)); uiGroup.sync(); } m_sublimeController->loadSettings(); QCOMPARE(m_sublimeController->openAfterCurrent(), enable); } void TestBuddies::testDeclarationDefinitionOrder() { enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.cpp"); createFile(dirA, "b.cpp"); createFile(dirA, "c.cpp"); createFile(dirA, "a.h"); createFile(dirA, "b.h"); createFile(dirA, "c.h"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.h"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("c.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.h"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("c.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); QCOMPARE(m_documentController->openDocuments().count(), 6); //QCOMPARE(m_uiController->documents().count(), 6); QCOMPARE(areaIndex->viewCount(), 6); verifyFilename(areaIndex->views().value(0), "a.h"); verifyFilename(areaIndex->views().value(1), "a.cpp"); verifyFilename(areaIndex->views().value(2), "b.h"); verifyFilename(areaIndex->views().value(3), "b.cpp"); verifyFilename(areaIndex->views().value(4), "c.h"); verifyFilename(areaIndex->views().value(5), "c.cpp"); } void TestBuddies::testMultiDotFilenames() { enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.cpp"); createFile(dirA, "lots.of.dots.cpp"); createFile(dirA, "b.cpp"); createFile(dirA, "lots.of.dots.h"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("lots.of.dots.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("lots.of.dots.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); QCOMPARE(m_documentController->openDocuments().count(), 4); //QCOMPARE(m_sublimeController->documents().count(), 4); QCOMPARE(areaIndex->viewCount(), 4); verifyFilename(areaIndex->views().value(0), "a.cpp"); verifyFilename(areaIndex->views().value(1), "lots.of.dots.h"); verifyFilename(areaIndex->views().value(2), "lots.of.dots.cpp"); verifyFilename(areaIndex->views().value(3), "b.cpp"); } void TestBuddies::testActivation() { enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.h"); createFile(dirA, "a.cpp"); createFile(dirA, "b.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.h"))); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "a.h"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.cpp"))); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "b.cpp"); } void TestBuddies::testDisableBuddies() { /* 3. Disactivate buddy option, Activate open next to active tab Open a.cpp a.h Verify order (a.cpp a.h) Verify that a.h is activated Activate a.cpp Open b.cpp Verify order (a.cpp b.cpp a.h) */ enableBuddies(false); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.h"); createFile(dirA, "a.cpp"); createFile(dirA, "b.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); // Buddies disabled => order of tabs should be the order of file opening verifyFilename(areaIndex->views().value(0), "a.cpp"); verifyFilename(areaIndex->views().value(1), "a.h"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "a.h"); //activate a.cpp => new doc should be opened right next to it toSublimeWindow(m_uiController->activeMainWindow())->activateView(areaIndex->views().value(0)); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.cpp"))); verifyFilename(areaIndex->views().value(0), "a.cpp"); verifyFilename(areaIndex->views().value(1), "b.cpp"); verifyFilename(areaIndex->views().value(2), "a.h"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "b.cpp"); } void TestBuddies::testDisableOpenAfterCurrent() { /* 5. Enable buddy option, Disable open next to active tab Open foo.h bar.cpp foo.cpp Verify order (foo.h foo.cpp bar.cpp) Verify that foo.cpp is activated Open x.cpp => tab must be placed at the end Verify order (foo.h foo.cpp bar.cpp x.cpp) Verify that x.cpp is activated*/ enableBuddies(); enableOpenAfterCurrent(false); QTemporaryDir tempDirA; QDir dirA; createFile(dirA, "foo.h"); createFile(dirA, "bar.cpp"); createFile(dirA, "foo.cpp"); createFile(dirA, "x.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("foo.h"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("bar.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("foo.cpp"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); verifyFilename(areaIndex->views().value(0), "foo.h"); verifyFilename(areaIndex->views().value(1), "foo.cpp"); verifyFilename(areaIndex->views().value(2), "bar.cpp"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "foo.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("x.cpp"))); verifyFilename(areaIndex->views().value(0), "foo.h"); verifyFilename(areaIndex->views().value(1), "foo.cpp"); verifyFilename(areaIndex->views().value(2), "bar.cpp"); verifyFilename(areaIndex->views().value(3), "x.cpp"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "x.cpp"); } void TestBuddies::testDisableAll() { /* 6. Disable buddy option, Disable open next to active tab Open foo.cpp bar.h foo.h Activate bar.h Open bar.cpp Verify order (foo.cpp bar.h foo.h bar.cpp) Verify that bar.cpp is activated*/ enableBuddies(false); enableOpenAfterCurrent(false); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "foo.h"); createFile(dirA, "foo.cpp"); createFile(dirA, "bar.h"); createFile(dirA, "bar.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("foo.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("bar.h"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("foo.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); //activate bar.h toSublimeWindow(m_uiController->activeMainWindow())->activateView(areaIndex->views().value(1)); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("bar.cpp"))); verifyFilename(areaIndex->views().value(0), "foo.cpp"); verifyFilename(areaIndex->views().value(1), "bar.h"); verifyFilename(areaIndex->views().value(2), "foo.h"); verifyFilename(areaIndex->views().value(3), "bar.cpp"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "bar.cpp"); } void TestBuddies::testMultipleFolders() { /* 4. Multiple folders: Activate buddy option Open f/a.cpp f/xyz.cpp g/a.h Verify g/a.h is activated Verify order (f/a.cpp f/xyz.cpp g/a.h)*/ enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.cpp"); createFile(dirA, "x.cpp"); QTemporaryDir tempDirB; QDir dirB(tempDirB.path()); createFile(dirB, "a.h"); // different folder => not dirA/a.cpp's buddy! m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("x.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirB.filePath("a.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); verifyFilename(areaIndex->views().value(0), "a.cpp"); verifyFilename(areaIndex->views().value(1), "x.cpp"); verifyFilename(areaIndex->views().value(2), "a.h"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "a.h"); } void TestBuddies::testSplitViewBuddies() { Sublime::MainWindow *pMainWindow = toSublimeWindow(m_uiController->activeMainWindow()); enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "classA.cpp"); createFile(dirA, "classA.h"); Sublime::Area *pCodeArea = m_uiController->activeArea(); QVERIFY(pCodeArea); IDocument *pClassAHeader = m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("classA.h"))); QVERIFY(pClassAHeader); pMainWindow->activeView()->setObjectName("classA.h"); // now, create a splitted view of the active view (pClassAHeader) Sublime::View *pNewView = pMainWindow->activeView()->document()->createView(); pNewView->setObjectName("splitOf" + pMainWindow->activeView()->objectName()); pCodeArea->addView(pNewView, pMainWindow->activeView(), Qt::Vertical); // and activate it pMainWindow->activateView(pNewView); // get the current view's container from the mainwindow QWidget *pCentral = pMainWindow->centralWidget(); QVERIFY(pCentral); QVERIFY(pCentral->inherits("QWidget")); QWidget *pSplitter = pCentral->findChild(); QVERIFY(pSplitter); QVERIFY(pSplitter->inherits("QSplitter")); Sublime::Container *pContainer = pSplitter->findChild(); QVERIFY(pContainer); // check that it only contains pNewView QVERIFY(pContainer->count() == 1 && pContainer->hasWidget(pNewView->widget())); // now open the correponding definition file, classA.cpp IDocument *pClassAImplem = m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("classA.cpp"))); QVERIFY(pClassAImplem); pMainWindow->activeView()->setObjectName("classA.cpp"); // and check its presence alongside pNewView in pContainer QVERIFY(pContainer->hasWidget(pNewView->widget())); QVERIFY(pContainer->hasWidget(pMainWindow->activeView()->widget())); } void TestBuddies::testDUChainBuddy() { enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; - TestFile header("void myfunction();\n", "h", 0, dirA.path()); - TestFile other("void otherfunction() {}\n", "cpp", 0, dirA.path()); + TestFile header("void myfunction();\n", "h", nullptr, dirA.path()); + TestFile other("void otherfunction() {}\n", "cpp", nullptr, dirA.path()); TestFile source( QString("#include \"%1\"\nvoid myfunction() {}\n").arg(header.url().toUrl().fileName()), - "cpp", 0, dirA.path() + "cpp", nullptr, dirA.path() ); header.parseAndWait(); other.parseAndWait(); source.parseAndWait(); // Test IBuddyDocumentFinder::getPotentialBuddies() QMimeDatabase db; IBuddyDocumentFinder* sourceBuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(source.url().toUrl()).name()); QVector< QUrl > sourceBuddies = sourceBuddyFinder->getPotentialBuddies(source.url().toUrl()); if (!sourceBuddies.contains(header.url().toUrl())) { qDebug() << "got source buddies: " << sourceBuddies; qDebug() << "expected: " << header.url().toUrl(); QFAIL("Failed to find buddy for source file"); } QVERIFY2(!sourceBuddies.contains(other.url().toUrl()), "source buddy list contains unrelated file"); IBuddyDocumentFinder* headerBuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(header.url().toUrl()).name()); QVector< QUrl > headerBuddies = headerBuddyFinder->getPotentialBuddies(header.url().toUrl()); if (!headerBuddies.contains(source.url().toUrl())) { qDebug() << "got header buddies: " << headerBuddies; qDebug() << "expected: " << source.url().toUrl(); QFAIL("Failed to find buddy for header file"); } QVERIFY2(!headerBuddies.contains(other.url().toUrl()), "header buddy list contains unrelated file"); // Test IBuddyDocumentFinder::areBuddies() QVERIFY(sourceBuddyFinder->areBuddies(source.url().toUrl(), header.url().toUrl())); } void TestBuddies::testDUChainBuddyVote() { /* * Test that the DUChain buddy system finds the buddy file with the most * common declarations/definitions */ enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; - TestFile header("void func1();\nvoid func2();\nvoid func3();\n", "h", 0, dirA.path()); + TestFile header("void func1();\nvoid func2();\nvoid func3();\n", "h", nullptr, dirA.path()); TestFile source1( QString("#include \"%1\"\nvoid func1() {}\n").arg(header.url().toUrl().fileName()), - "cpp", 0, dirA.path() + "cpp", nullptr, dirA.path() ); TestFile source2( QString("#include \"%1\"\nvoid func2() {}\nvoid func3() {}\n").arg(header.url().toUrl().fileName()), - "cpp", 0, dirA.path() + "cpp", nullptr, dirA.path() ); // -> buddy(header) should resolve to source2 header.parseAndWait(); source1.parseAndWait(); source2.parseAndWait(); // Test IBuddyDocumentFinder::getPotentialBuddies() QMimeDatabase db; IBuddyDocumentFinder* sourceBuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(source1.url().toUrl()).name()); QVector< QUrl > sourceBuddies = sourceBuddyFinder->getPotentialBuddies(source1.url().toUrl()); if (!sourceBuddies.contains(header.url().toUrl())) { qDebug() << "got source buddies: " << sourceBuddies; qDebug() << "expected: " << header.url().toUrl(); QFAIL("Failed to find buddy for source file"); } IBuddyDocumentFinder* source2BuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(source2.url().toUrl()).name()); QVector< QUrl > source2Buddies = source2BuddyFinder->getPotentialBuddies(source2.url().toUrl()); if (!source2Buddies.contains(header.url().toUrl())) { qDebug() << "got source2 buddies: " << source2Buddies; qDebug() << "expected: " << header.url().toUrl(); QFAIL("Failed to find buddy for source2 file"); } IBuddyDocumentFinder* headerBuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(header.url().toUrl()).name()); QVector< QUrl > headerBuddies = headerBuddyFinder->getPotentialBuddies(header.url().toUrl()); if (!headerBuddies.contains(source2.url().toUrl())) { qDebug() << "got header buddies: " << headerBuddies; qDebug() << "expected: " << source2.url().toUrl(); QFAIL("Failed to find buddy for header file"); } QVERIFY2(!headerBuddies.contains(source1.url().toUrl()), "header buddy list contains weaker file"); } QTEST_MAIN(TestBuddies) diff --git a/languages/clang/tests/test_codecompletion.cpp b/languages/clang/tests/test_codecompletion.cpp index 69a288a0ce..bdcb659c40 100644 --- a/languages/clang/tests/test_codecompletion.cpp +++ b/languages/clang/tests/test_codecompletion.cpp @@ -1,1222 +1,1222 @@ /* * Copyright 2014 David Stevens * Copyright 2014 Kevin Funk * Copyright 2015 Milian Wolff * Copyright 2015 Sergey Kalinichev * * 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 "test_codecompletion.h" #include #include #include #include #include #include "duchain/parsesession.h" #include "util/clangtypes.h" #include #include #include #include #include #include "codecompletion/completionhelper.h" #include "codecompletion/context.h" #include "codecompletion/includepathcompletioncontext.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include #include QTEST_MAIN(TestCodeCompletion); static const auto NoMacroOrBuiltin = ClangCodeCompletionContext::ContextFilters( ClangCodeCompletionContext::NoBuiltins | ClangCodeCompletionContext::NoMacros); using namespace KDevelop; using ClangCodeCompletionItemTester = CodeCompletionItemTester; struct CompletionItems { CompletionItems(){} CompletionItems(const KTextEditor::Cursor& position, const QStringList& completions, const QStringList& declarationItems = {}) : position(position) , completions(completions) , declarationItems(declarationItems) {}; KTextEditor::Cursor position; QStringList completions; QStringList declarationItems; ///< completion items that have associated declarations. Declarations with higher match quality at the top. @sa KTextEditor::CodeCompletionModel::MatchQuality }; Q_DECLARE_TYPEINFO(CompletionItems, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(CompletionItems); struct CompletionPriorityItem { CompletionPriorityItem(const QString name, int matchQuality = 0, int inheritanceDepth = 0, const QString failMessage = {}) : name(name) , failMessage(failMessage) , matchQuality(matchQuality) , inheritanceDepth(inheritanceDepth) {} QString name; QString failMessage; int matchQuality; int inheritanceDepth; }; struct CompletionPriorityItems : public CompletionItems { CompletionPriorityItems(){} CompletionPriorityItems(const KTextEditor::Cursor& position, const QList& completions) : CompletionItems(position, {}) , completions(completions) {}; QList completions; }; Q_DECLARE_TYPEINFO(CompletionPriorityItems, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(CompletionPriorityItems); namespace { struct NoopTestFunction { void operator()(const ClangCodeCompletionItemTester& /*tester*/) const { } }; template void executeCompletionTest(const ReferencedTopDUContext& top, const CompletionItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin, CustomTestFunction customTestFunction = {}) { DUChainReadLocker lock; const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); lock.unlock(); QString text; if (auto doc = ICore::self()->documentController()->documentForUrl(top->url().toUrl())) { text = doc->textDocument()->text({{0, 0}, expectedCompletionItems.position}); } // TODO: We should not need to pass 'session' to the context, should just use the base class ctor auto context = new ClangCodeCompletionContext(DUContextPointer(top), sessionData, top->url().toUrl(), expectedCompletionItems.position, text); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); int previousMatchQuality = 10; for(const auto& declarationName : expectedCompletionItems.declarationItems){ const auto declarationItem = tester.findItem(declarationName); QVERIFY(declarationItem); QVERIFY(declarationItem->declaration()); auto matchQuality = tester.itemData(declarationItem, KTextEditor::CodeCompletionModel::Name, KTextEditor::CodeCompletionModel::MatchQuality).toInt(); QVERIFY(matchQuality <= previousMatchQuality); previousMatchQuality = matchQuality; } tester.names.sort(); QEXPECT_FAIL("look-ahead function primary type argument", "No API in LibClang to determine expected code completion type", Continue); QEXPECT_FAIL("look-ahead template parameter substitution", "No parameters substitution so far", Continue); #if CINDEX_VERSION_MINOR < 30 QEXPECT_FAIL("look-ahead auto item", "Auto type, like many other types, is not exposed through LibClang. We assign DelayedType to it instead of IdentifiedType", Continue); #endif if (QTest::currentTestFunction() == QByteArrayLiteral("testImplementAfterEdit") && expectedCompletionItems.position.line() == 3) { QEXPECT_FAIL("", "TU is not properly updated after edit", Continue); } if (tester.names.size() != expectedCompletionItems.completions.size()) { qDebug() << "different results:\nactual:" << tester.names << "\nexpected:" << expectedCompletionItems.completions; } QCOMPARE(tester.names, expectedCompletionItems.completions); customTestFunction(tester); } template void executeCompletionTest(const QString& code, const CompletionItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin, CustomTestFunction customTestFunction = {}) { TestFile file(code, "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(file.topContext(), expectedCompletionItems, filters, customTestFunction); } void executeCompletionPriorityTest(const QString& code, const CompletionPriorityItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin) { TestFile file(code, "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); // don't hold DUChain lock when constructing ClangCodeCompletionContext lock.unlock(); auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, QString()); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); for(const auto& declaration : expectedCompletionItems.completions){ const auto declarationItem = tester.findItem(declaration.name); QVERIFY(declarationItem); QVERIFY(declarationItem->declaration()); auto matchQuality = tester.itemData(declarationItem, KTextEditor::CodeCompletionModel::Name, KTextEditor::CodeCompletionModel::MatchQuality).toInt(); auto inheritanceDepth = declarationItem->inheritanceDepth(); if(!declaration.failMessage.isEmpty()){ QEXPECT_FAIL("", declaration.failMessage.toUtf8().constData(), Continue); } QVERIFY(matchQuality == declaration.matchQuality && inheritanceDepth == declaration.inheritanceDepth); } } void executeMemberAccessReplacerTest(const QString& code, const CompletionItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin) { TestFile file(code, "cpp"); auto document = ICore::self()->documentController()->openDocument(file.url().toUrl()); QVERIFY(document); ICore::self()->documentController()->activateDocument(document); auto view = ICore::self()->documentController()->activeTextDocumentView(); Q_ASSERT(view); view->setCursorPosition(expectedCompletionItems.position); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); lock.unlock(); QExplicitlySharedDataPointer context( new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, code)); QApplication::processEvents(); document->close(KDevelop::IDocument::Silent); // The previous ClangCodeCompletionContext call should replace member access. // That triggers an update request in the duchain which we are not interested in, // so let's stop that request. ICore::self()->languageController()->backgroundParser()->removeDocument(file.url()); context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, QString()); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(context); tester.names.sort(); QCOMPARE(tester.names, expectedCompletionItems.completions); } using IncludeTester = CodeCompletionItemTester; QExplicitlySharedDataPointer executeIncludePathCompletion(TestFile* file, const KTextEditor::Cursor& position) { if (!file->parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)) { QTest::qFail("Failed to parse source file.", __FILE__, __LINE__); return {}; } DUChainReadLocker lock; auto top = file->topContext(); if (!top) { QTest::qFail("Failed to parse source file.", __FILE__, __LINE__); return {}; } const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); if (!sessionData) { QTest::qFail("Failed to acquire parse session data.", __FILE__, __LINE__); return {}; } DUContextPointer topPtr(top); lock.unlock(); auto text = file->fileContents(); int textLength = -1; if (position.isValid()) { textLength = 0; for (int i = 0; i < position.line(); ++i) { textLength = text.indexOf('\n', textLength) + 1; } textLength += position.column(); } auto context = new IncludePathCompletionContext(topPtr, sessionData, file->url().toUrl(), position, text.mid(0, textLength)); return QExplicitlySharedDataPointer{context}; } } void TestCodeCompletion::testClangCodeCompletion() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems); } void TestCodeCompletion::testClangCodeCompletion_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("assignment") << "int foo = 5; \nint bar = " << CompletionItems{{1,9}, { "bar", "foo", }, {"bar","foo"}}; QTest::newRow("dotmemberaccess") << "class Foo { public: void foo() {} bool operator=(Foo &&) }; int main() { Foo f; \nf. " << CompletionItems{{1, 2}, { "foo", "operator=" }, {"foo", "operator="}}; QTest::newRow("arrowmemberaccess") << "class Foo { public: void foo() {} }; int main() { Foo* f = new Foo; \nf-> }" << CompletionItems{{1, 3}, { "foo" }, {"foo"}}; QTest::newRow("enum-case") << "enum Foo { foo, bar }; int main() { Foo f; switch (f) {\ncase " << CompletionItems{{1,4}, { "bar", "foo", }, {"foo", "bar"}}; QTest::newRow("only-private") << "class SomeStruct { private: void priv() {} };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { }}; QTest::newRow("private-friend") << "class SomeStruct { private: void priv() {} friend int main(); };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { "priv", }, {"priv"}}; QTest::newRow("private-public") << "class SomeStruct { public: void pub() {} private: void priv() {} };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { "pub", }, {"pub"}}; QTest::newRow("protected-public") << "class SomeStruct { public: void pub() {} protected: void prot() {} };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { "pub", }, {"pub"}}; QTest::newRow("localVariable") << "int main() { int localVariable;\nloc " << CompletionItems{{1, 3}, {"localVariable","main"}, {"localVariable", "main"} }; QTest::newRow("globalVariable") << "int globalVariable;\nint main() { \ngl " << CompletionItems{{2, 2}, {"globalVariable","main"}, {"globalVariable", "main"} }; QTest::newRow("namespaceVariable") << "namespace NameSpace{int variable};\nint main() { \nNameSpace:: " << CompletionItems{{2, 11}, {"variable"}, {"variable"} }; QTest::newRow("parentVariable") << "class A{public: int m_variable;};class B : public A{};\nint main() { B b;\nb. " << CompletionItems{{2, 2}, {"m_variable"}, {"m_variable"} }; QTest::newRow("itemsPriority") << "class A; class B; void f(A); int main(){ A c; B b;f(\n} " << CompletionItems{{1, 0}, {"A", "B", "b", "c", "f", #if CINDEX_VERSION_MINOR >= 30 "f", #endif "main"}, {"c", "A", "b", "B"} }; QTest::newRow("function-arguments") << "class Abc; int f(Abc){\n " << CompletionItems{{1, 0}, { "Abc", "f", }}; QTest::newRow("look-ahead int") << "struct LookAhead { int intItem;}; int main() {LookAhead* pInstance; LookAhead instance; int i =\n }" << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.intItem", "main", "pInstance", "pInstance->intItem", }}; QTest::newRow("look-ahead class") << "class Class{}; struct LookAhead {Class classItem;}; int main() {LookAhead* pInstance; LookAhead instance; Class cl =\n }" << CompletionItems{{1, 0}, { "Class", "LookAhead", "cl", "instance", "instance.classItem", "main", "pInstance", "pInstance->classItem", }}; QTest::newRow("look-ahead function argument") << "class Class{}; struct LookAhead {Class classItem;}; void function(Class cl);" "int main() {LookAhead* pInstance; LookAhead instance; function(\n }" << CompletionItems{{1, 0}, { "Class", "LookAhead", "function", #if CINDEX_VERSION_MINOR >= 30 "function", #endif "instance", "instance.classItem", "main", "pInstance", "pInstance->classItem", }}; QTest::newRow("look-ahead function primary type argument") << "struct LookAhead {double doubleItem;}; void function(double double);" "int main() {LookAhead* pInstance; LookAhead instance; function(\n }" << CompletionItems{{1, 0}, { "LookAhead", "function", "instance", "instance.doubleItem", "main", "pInstance", "pInstance->doubleItem", }}; QTest::newRow("look-ahead typedef") << "typedef double DOUBLE; struct LookAhead {DOUBLE doubleItem;};" "int main() {LookAhead* pInstance; LookAhead instance; double i =\n " << CompletionItems{{1, 0}, { "DOUBLE", "LookAhead", "i", "instance", "instance.doubleItem", "main", "pInstance", "pInstance->doubleItem", }}; QTest::newRow("look-ahead pointer") << "struct LookAhead {int* pInt;};" "int main() {LookAhead* pInstance; LookAhead instance; int* i =\n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.pInt", "main", "pInstance", "pInstance->pInt", }}; QTest::newRow("look-ahead template") << "template struct LookAhead {int intItem;};" "int main() {LookAhead* pInstance; LookAhead instance; int i =\n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.intItem", "main", "pInstance", "pInstance->intItem", }}; QTest::newRow("look-ahead template parameter substitution") << "template struct LookAhead {T itemT;};" "int main() {LookAhead* pInstance; LookAhead instance; int i =\n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.itemT", "main", "pInstance", "pInstance->itemT", }}; QTest::newRow("look-ahead item access") << "class Class { public: int publicInt; protected: int protectedInt; private: int privateInt;};" "int main() {Class cl; int i =\n " << CompletionItems{{1, 0}, { "Class", "cl", "cl.publicInt", "i", "main", }}; QTest::newRow("look-ahead auto item") << "struct LookAhead { int intItem; };" "int main() {auto instance = LookAhead(); int i = \n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.intItem", "main" }}; QTest::newRow("variadic template recursive class") << R"( template struct my_class : Head, my_class { using base = Head; };)" << CompletionItems{{3, 17}, { "Head", "Tail", "my_class" }}; } void TestCodeCompletion::testReplaceMemberAccess() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeMemberAccessReplacerTest(code, expectedItems); } void TestCodeCompletion::testReplaceMemberAccess_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("replace arrow to dot") << "struct Struct { void function(); };" "int main() { Struct s; \ns-> " << CompletionItems{{1, 3}, { "function" }}; QTest::newRow("replace dot to arrow") << "struct Struct { void function(); };" "int main() { Struct* s; \ns. " << CompletionItems{{1, 3}, { "function" }}; QTest::newRow("no replacement needed") << "int main() { double a = \n0. " << CompletionItems{{1, 2}, { }}; } void TestCodeCompletion::testVirtualOverride() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems, ClangCodeCompletionContext::NoClangCompletion); } void TestCodeCompletion::testVirtualOverride_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("basic") << "class Foo { virtual void foo(); virtual void foo(char c); virtual char foo(char c, int i, double d); };\n" "class Bar : Foo \n{void foo(char c) override;\n}" << CompletionItems{{3, 1}, {"foo()", "foo(char c, int i, double d)"}}; QTest::newRow("template") << "template class Foo { virtual T2 foo(T1 a, T2 b, int i); virtual T2 overridden(T1 a); } ;\n" "class Bar : Foo \n{double overridden(char a) override;\n}" << CompletionItems{{3, 1}, {"foo(char a, double b, int i)"}}; QTest::newRow("nested-template") << "template class Foo { virtual T2 foo(T1 a, T2 b, int i); virtual T2 overridden(T1 a, T2 b, int i); } ;\n" "template class Baz { };\n" "class Bar : Foo> \n{Baz overridden(char a, Baz b, int i) override;\n}" << CompletionItems{{4, 1}, {"foo(char a, Baz b, int i)"}}; QTest::newRow("multi") << "class Foo { virtual int foo(int i); virtual int overridden(int i); };\n" "class Baz { virtual char baz(char c); };\n" "class Bar : Foo, Baz \n{int overridden(int i) override;\n}" << CompletionItems{{4, 1}, {"baz(char c)", "foo(int i)"}}; QTest::newRow("deep") << "class Foo { virtual int foo(int i); virtual int overridden(int i); };\n" "class Baz : Foo { };\n" "class Bar : Baz \n{int overridden(int i) overriden;\n}" << CompletionItems{{4, 1}, {"foo(int i)"}}; QTest::newRow("pure") << "class Foo { virtual void foo() = 0; virtual void overridden() = 0;};\n" "class Bar : Foo \n{void overridden() override;\n};" << CompletionItems{{3, 0}, {"foo() = 0"}}; QTest::newRow("const") << "class Foo { virtual void foo(const int b) const; virtual void overridden(const int b) const; }\n;" "class Bar : Foo \n{void overridden(const int b) const override;\n}" << CompletionItems{{3, 1}, {"foo(const int b) const"}}; QTest::newRow("dont-override") << R"(class A { virtual void something() = 0; }; class B : public A { public: void foo(); }; void B::foo() {} )" << CompletionItems{{8, 14}, {}}; } void TestCodeCompletion::testImplement() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems, ClangCodeCompletionContext::NoClangCompletion); } void TestCodeCompletion::testImplement_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("basic") << "int foo(char c, int i); \n" << CompletionItems{{1, 0}, {"foo(char c, int i)"}}; QTest::newRow("class") << "class Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{2, 0}, {}}; QTest::newRow("class2") << "class Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{4, 0}, {"Foo::bar(char c, int i)"}}; QTest::newRow("namespace") << "namespace Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{2, 0}, {"bar(char c, int i)"}}; QTest::newRow("anonymous-namespace") << R"( namespace { int bar(char c, int i); }; )" << CompletionItems{{3, 0}, {"bar(char c, int i)"}}; QTest::newRow("anonymous-namespace2") << R"( namespace { int bar(char c, int i); }; )" << CompletionItems{{4, 0}, {}}; QTest::newRow("namespace2") << "namespace Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{4, 0}, {"Foo::bar(char c, int i)"}}; QTest::newRow("two-namespace") << "namespace Foo { int bar(char c, int i); };\n" "namespace Foo {\n" "};\n" << CompletionItems{{2, 0}, {"bar(char c, int i)"}}; QTest::newRow("destructor") << "class Foo { ~Foo(); }\n" << CompletionItems{{1, 0}, {"Foo::~Foo()"}}; QTest::newRow("constructor") << "class Foo { \n" "Foo(int i); \n" "}; \n" << CompletionItems{{3, 1}, {"Foo::Foo(int i)"}}; QTest::newRow("template") << "template class Foo { T bar(T t); };\n" << CompletionItems{{1, 1}, {"Foo::bar(T t)"}}; QTest::newRow("specialized-template") << "template class Foo { \n" "T bar(T t); \n" "}; \n" "template T Foo::bar(T t){} \n" "template<> class Foo { \n" "int bar(int t); \n" "}\n" << CompletionItems{{6, 1}, {"Foo::bar(int t)"}}; QTest::newRow("nested-class") << "class Foo { \n" "class Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{3, 1}, {}}; QTest::newRow("nested-class2") << "class Foo { \n" "class Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{5, 1}, {}}; QTest::newRow("nested-class3") << "class Foo { \n" "class Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{7, 1}, {"Foo::Bar::baz(char c, int i)"}}; QTest::newRow("nested-namespace") << "namespace Foo { \n" "namespace Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{3, 1}, {"baz(char c, int i)"}}; QTest::newRow("nested-namespace2") << "namespace Foo { \n" "namespace Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{5, 1}, {"Bar::baz(char c, int i)"}}; QTest::newRow("nested-namespace3") << "namespace Foo { \n" "namespace Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{7, 1}, {"Foo::Bar::baz(char c, int i)"}}; QTest::newRow("partial-template") << "template class Foo { \n" "template class Bar;\n" "template class Bar { void baz(T t, U u); }\n" "}\n" << CompletionItems{{5,1}, {"Foo::Bar::baz(T t, U u)"}}; QTest::newRow("variadic") << "int foo(...); int bar(int i, ...); \n" << CompletionItems{{1, 1}, {"bar(int i, ...)", "foo(...)"}}; QTest::newRow("const") << "class Foo { int bar() const; };" << CompletionItems{{3, 1}, {"Foo::bar() const"}}; QTest::newRow("multiple-methods") << "class Foo { int bar(); void foo(); char asdf() const; };" << CompletionItems{{1, 1}, {"Foo::asdf() const", "Foo::bar()", "Foo::foo()"}}; // explicitly deleted/defaulted functions should not appear in the implements completion QTest::newRow("deleted-copy-ctor") << "struct S { S(); S(const S&) = /*some comment*/ delete; };" << CompletionItems{{1,1}, {"S::S()"}}; QTest::newRow("deleted-overload-member") << "struct Foo {\n" " int x();\n" " int x(int) =delete;\n" "};\n" << CompletionItems{{5,1}, {"Foo::x()"}}; QTest::newRow("deleted-overload-global") << "int x();\n" "int x(int)= delete;\n" << CompletionItems{{2,1}, {"x()"}}; QTest::newRow("defaulted-copy-ctor") << "struct S { S(); S(const S&) = default; };" << CompletionItems{{1,1}, {"S::S()"}}; QTest::newRow("defaulted-dtor") << "struct Foo {\n" " Foo();\n" " ~Foo() =default;\n" "};\n" << CompletionItems{{5,1}, {"Foo::Foo()"}}; QTest::newRow("bug355163") << R"( #include namespace test { template struct IsSafeConversion : public std::is_same::type> { }; } // namespace test )" << CompletionItems{{7,0}, {}}; QTest::newRow("bug355954") << R"( struct Hello { struct Private; }; struct Hello::Private { void test(); }; )" << CompletionItems{{8,0}, {"Hello::Private::test()"}}; QTest::newRow("lineOfNextFunction") << "void foo();\nvoid bar() {}" << CompletionItems{{1,0}, {"foo()"}}; QTest::newRow("pure") << R"( struct Hello { virtual void foo() = 0; virtual void bar(); }; )" << CompletionItems{{5, 0}, {"Hello::bar()"}}; } void TestCodeCompletion::testImplementOtherFile() { TestFile header1("void foo();", "h"); QVERIFY(header1.parseAndWait()); TestFile header2("void bar();", "h"); QVERIFY(header2.parseAndWait()); TestFile impl(QString("#include \"%1\"\n" "#include \"%2\"\n" "void asdf();\n\n") .arg(header1.url().str()) .arg(header2.url().str()), "cpp", &header1); CompletionItems expectedItems{{3,1}, {"asdf()", "foo()"}}; QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(impl.topContext(), expectedItems); } void TestCodeCompletion::testImplementAfterEdit() { TestFile header1("void foo();", "h"); QVERIFY(header1.parseAndWait()); TestFile impl(QString("#include \"%1\"\n" "void asdf() {}\nvoid bar() {}") .arg(header1.url().str()), "cpp", &header1); auto document = ICore::self()->documentController()->openDocument(impl.url().toUrl()); QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); CompletionItems expectedItems{{2,0}, {"foo()"}}; executeCompletionTest(impl.topContext(), expectedItems); document->textDocument()->insertText(expectedItems.position, "\n"); expectedItems.position.setLine(3); executeCompletionTest(impl.topContext(), expectedItems); document->close(IDocument::Discard); } void TestCodeCompletion::testInvalidCompletions() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems); } void TestCodeCompletion::testInvalidCompletions_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("invalid-context-incomment") << "class Foo { int bar() const; };\n/*\n*/" << CompletionItems{{2, 0}, {}}; } void TestCodeCompletion::testIncludePathCompletion_data() { QTest::addColumn("code"); QTest::addColumn("cursor"); QTest::addColumn("itemId"); QTest::addColumn("result"); QTest::newRow("global-1") << QString("#include ") << KTextEditor::Cursor(0, 9) << QString("iostream") << QString("#include "); QTest::newRow("global-2") << QString("#include <") << KTextEditor::Cursor(0, 9) << QString("iostream") << QString("#include "); QTest::newRow("global-3") << QString("#include <") << KTextEditor::Cursor(0, 10) << QString("iostream") << QString("#include "); QTest::newRow("global-4") << QString("# include <") << KTextEditor::Cursor(0, 12) << QString("iostream") << QString("# include "); QTest::newRow("global-5") << QString("# include <") << KTextEditor::Cursor(0, 14) << QString("iostream") << QString("# include "); QTest::newRow("global-6") << QString("# include <> /* 1 */") << KTextEditor::Cursor(0, 14) << QString("iostream") << QString("# include /* 1 */"); QTest::newRow("global-7") << QString("# include /* 1 */ <> /* 1 */") << KTextEditor::Cursor(0, 21) << QString("iostream") << QString("# include /* 1 */ /* 1 */"); QTest::newRow("global-8") << QString("# /* 1 */ include /* 1 */ <> /* 1 */") << KTextEditor::Cursor(0, 28) << QString("iostream") << QString("# /* 1 */ include /* 1 */ /* 1 */"); QTest::newRow("global-9") << QString("#include ") << KTextEditor::Cursor(0, 10) << QString("iostream") << QString("#include "); QTest::newRow("global-10") << QString("#include ") << KTextEditor::Cursor(0, 14) << QString("cstdint") << QString("#include "); QTest::newRow("global-11") << QString("#include ") << KTextEditor::Cursor(0, 17) << QString("cstdint") << QString("#include "); QTest::newRow("local-0") << QString("#include \"") << KTextEditor::Cursor(0, 10) << QString("foo/") << QString("#include \"foo/\""); QTest::newRow("local-1") << QString("#include \"foo/\"") << KTextEditor::Cursor(0, 14) << QString("bar/") << QString("#include \"foo/bar/\""); QTest::newRow("local-2") << QString("#include \"foo/") << KTextEditor::Cursor(0, 14) << QString("bar/") << QString("#include \"foo/bar/\""); QTest::newRow("local-3") << QString("# /* 1 */ include /* 1 */ \"\" /* 1 */") << KTextEditor::Cursor(0, 28) << QString("foo/") << QString("# /* 1 */ include /* 1 */ \"foo/\" /* 1 */"); QTest::newRow("local-4") << QString("# /* 1 */ include /* 1 */ \"foo/\" /* 1 */") << KTextEditor::Cursor(0, 31) << QString("bar/") << QString("# /* 1 */ include /* 1 */ \"foo/bar/\" /* 1 */"); QTest::newRow("local-5") << QString("#include \"foo/\"") << KTextEditor::Cursor(0, 10) << QString("foo/") << QString("#include \"foo/\""); QTest::newRow("local-6") << QString("#include \"foo/asdf\"") << KTextEditor::Cursor(0, 10) << QString("foo/") << QString("#include \"foo/\""); QTest::newRow("local-7") << QString("#include \"foo/asdf\"") << KTextEditor::Cursor(0, 14) << QString("bar/") << QString("#include \"foo/bar/\""); } void TestCodeCompletion::testIncludePathCompletion() { QFETCH(QString, code); QFETCH(KTextEditor::Cursor, cursor); QFETCH(QString, itemId); QFETCH(QString, result); QTemporaryDir tempDir; QDir dir(tempDir.path()); QVERIFY(dir.mkpath("foo/bar/asdf")); - TestFile file(code, "cpp", 0, tempDir.path()); + TestFile file(code, "cpp", nullptr, tempDir.path()); IncludeTester tester(executeIncludePathCompletion(&file, cursor)); QVERIFY(tester.completionContext); QVERIFY(tester.completionContext->isValid()); auto item = tester.findItem(itemId); QVERIFY(item); auto view = createView(file.url().toUrl(), this); qDebug() << view.get(); QVERIFY(view.get()); auto doc = view->document(); item->execute(view.get(), KTextEditor::Range(cursor, cursor)); QCOMPARE(doc->text(), result); const auto newCursor = view->cursorPosition(); QCOMPARE(newCursor.line(), cursor.line()); if (!itemId.endsWith('/')) { // file inserted, cursor should be at end of line QCOMPARE(newCursor.column(), doc->lineLength(cursor.line())); } else { // directory inserted, cursor should be before the " or > const auto cursorChar = doc->characterAt(newCursor); QVERIFY(cursorChar == '"' || cursorChar == '>'); } } void TestCodeCompletion::testIncludePathCompletionLocal() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"", "cpp", &header); IncludeTester tester(executeIncludePathCompletion(&impl, {0, 10})); QVERIFY(tester.names.contains(header.url().toUrl().fileName())); QVERIFY(!tester.names.contains("iostream")); } void TestCodeCompletion::testOverloadedFunctions() { TestFile file("void f(); int f(int); void f(int, double){\n ", "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); lock.unlock(); const auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), {1, 0}, QString()); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); QCOMPARE(tester.items.size(), 3); for (const auto& item : tester.items) { auto function = item->declaration()->type(); const QString display = item->declaration()->identifier().toString() + function->partToString(FunctionType::SignatureArguments); const QString itemDisplay = tester.itemData(item).toString() + tester.itemData(item, KTextEditor:: CodeCompletionModel::Arguments).toString(); QCOMPARE(display, itemDisplay); } QVERIFY(tester.items[0]->declaration().data() != tester.items[1]->declaration().data()); QVERIFY(tester.items[0]->declaration().data() != tester.items[2]->declaration().data()); QVERIFY(tester.items[1]->declaration().data() != tester.items[2]->declaration().data()); } void TestCodeCompletion::testCompletionPriority() { QFETCH(QString, code); QFETCH(CompletionPriorityItems, expectedItems); executeCompletionPriorityTest(code, expectedItems); } void TestCodeCompletion::testCompletionPriority_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("pointer") << "class A{}; class B{}; class C : public B{}; int main(){A* a; B* b; C* c; b =\n " << CompletionPriorityItems{{1,0}, {{"a", 0, 21}, {"b", 9, 0}, {"c", 8, 0, QStringLiteral("Pointer to derived class is not added to the Best Matches group")}}}; QTest::newRow("class") << "class A{}; class B{}; class C : public B{}; int main(){A a; B b; C c; b =\n " << CompletionPriorityItems{{1,0}, {{"a", 0, 21}, {"b", 9, 0}, {"c", 8, 0, QStringLiteral("Derived class is not added to the Best Matches group")}}}; QTest::newRow("primary-types") << "class A{}; int main(){A a; int b; bool c = \n " << CompletionPriorityItems{{1,0}, {{"a", 0, 34}, {"b", 8, 0}, {"c", 9, 0}}}; QTest::newRow("reference") << "class A{}; class B{}; class C : public B{};" "int main(){A tmp; A& a = tmp; C tmp2; C& c = tmp2; B& b =\n ;}" << CompletionPriorityItems{{1,0}, {{"a", 0, 21}, {"b", 9, 0}, {"c", 8, 0, QStringLiteral("Reference to derived class is not added to the Best Matches group")}}}; QTest::newRow("typedef") << "struct A{}; struct B{}; typedef A AA; typedef B BB; void f(A p);" "int main(){ BB b; AA a; f(\n }" << CompletionPriorityItems{{1,0}, {{"a", 9, 0}, {"b", 0, 21}}}; QTest::newRow("returnType") << "struct A{}; struct B{}; struct Test{A f();B g(); Test() { A a =\n }};" << CompletionPriorityItems{{1,0}, {{"f", 9, 0}, {"g", 0, 21}}}; QTest::newRow("template") << "template class Class{}; template class Class2{};" "int main(){ Class a; Class2 b =\n }" << CompletionPriorityItems{{1,0}, {{"b", 9, 0}, {"a", 0, 21}}}; QTest::newRow("protected-access") << "class Base { protected: int m_protected; };" "class Derived: public Base {public: void g(){\n }};" << CompletionPriorityItems{{1,0}, {{"m_protected", 0, 37}}}; QTest::newRow("protected-access2") << "class Base { protected: int m_protected; };" "class Derived: public Base {public: void f();};" "void Derived::f(){\n }" << CompletionPriorityItems{{1,0}, {{"m_protected", 0, 37}}}; } void TestCodeCompletion::testVariableScope() { TestFile file("int var; \nvoid test(int var) {int tmp =\n }", "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); lock.unlock(); const auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), {2, 0}, QString()); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); QCOMPARE(tester.items.size(), 4); auto item = tester.findItem(QStringLiteral("var")); VERIFY(item); QCOMPARE(item->declaration()->range().start, CursorInRevision(1, 14)); } void TestCodeCompletion::testArgumentHintCompletion() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems); } void TestCodeCompletion::testArgumentHintCompletion_data() { #if CINDEX_VERSION_MINOR < 30 QSKIP("You need at least LibClang 3.7"); #endif QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("global function") << "void foo(int);\n" "int main() { \nfoo( " << CompletionItems{{2,4}, { "foo", "foo", "main" }}; QTest::newRow("member function") << "struct Struct{ void foo(int);}\n" "int main() {Struct s; \ns.foo( " << CompletionItems{{2,6}, { "Struct", "foo", "main", "s" }}; QTest::newRow("template function") << "template void foo(T);\n" "int main() { \nfoo( " << CompletionItems{{2,6}, { "foo", "foo", "main" }}; QTest::newRow("overloaded functions") << "void foo(int); void foo(int, double)\n" "int main() { \nfoo( " << CompletionItems{{2,6}, { "foo", "foo", "foo", "foo", "main" }}; QTest::newRow("overloaded functions2") << "void foo(int); void foo(int, double)\n" "int main() { foo(1,\n " << CompletionItems{{2,1}, { "foo", "foo", "foo", "main" }}; } void TestCodeCompletion::testArgumentHintCompletionDefaultParameters() { #if CINDEX_VERSION_MINOR < 30 QSKIP("You need at least LibClang 3.7"); #endif TestFile file("void f(int i, int j = 0, double k =1){\nf( ", "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); lock.unlock(); const auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), {1, 2}, QString()); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); QExplicitlySharedDataPointer f; for (const auto& item : tester.items) { if (item->argumentHintDepth() == 1) { f = item; break; } } QVERIFY(f.data()); const QString itemDisplay = tester.itemData(f).toString() + tester.itemData(f, KTextEditor:: CodeCompletionModel::Arguments).toString(); QCOMPARE(QStringLiteral("f(int i, int j, double k)"), itemDisplay); } void TestCodeCompletion::testCompleteFunction() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); QFETCH(QString, itemToExecute); QFETCH(QString, expectedCode); auto executeItem = [=] (const ClangCodeCompletionItemTester& tester) { auto item = tester.findItem(itemToExecute); QVERIFY(item); auto view = createView(tester.completionContext->duContext()->url().toUrl(), this); item->execute(view.get(), view->document()->wordRangeAt(expectedItems.position)); QCOMPARE(view->document()->text(), expectedCode); }; executeCompletionTest(code, expectedItems, NoMacroOrBuiltin, executeItem); } void TestCodeCompletion::testCompleteFunction_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::addColumn("itemToExecute"); QTest::addColumn("expectedCode"); QTest::newRow("add-parens") << "int foo();\nint main() {\n\n}" << CompletionItems({2, 0}, {"foo", "main"}) << "foo" << "int foo();\nint main() {\nfoo()\n}"; QTest::newRow("keep-parens") << "int foo();\nint main() {\nfoo();\n}" << CompletionItems({2, 0}, {"foo", "main"}) << "main" << "int foo();\nint main() {\nmain();\n}"; } void TestCodeCompletion::testIgnoreGccBuiltins() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Compiler").writeEntry("Name", "GCC"); m_projectController->addProject(project); { TestFile file("", "cpp", project, dir.path()); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(file.topContext(), {}); } } diff --git a/languages/clang/tests/test_duchain.cpp b/languages/clang/tests/test_duchain.cpp index 987b5eef10..04305cb6d3 100644 --- a/languages/clang/tests/test_duchain.cpp +++ b/languages/clang/tests/test_duchain.cpp @@ -1,1961 +1,1961 @@ /* * Copyright 2014 Milian Wolff * Copyright 2014 Kevin Funk * Copyright 2015 Sergey Kalinichev * * 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 "test_duchain.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 "duchain/clangparsingenvironmentfile.h" #include "duchain/clangparsingenvironment.h" #include "duchain/parsesession.h" #include #include QTEST_MAIN(TestDUChain); using namespace KDevelop; class TestEnvironmentProvider final : public IDefinesAndIncludesManager::BackgroundProvider { public: ~TestEnvironmentProvider() override = default; QHash< QString, QString > definesInBackground(const QString& /*path*/) const override { return defines; } Path::List includesInBackground(const QString& /*path*/) const override { return includes; } Path::List frameworkDirectoriesInBackground(const QString&) const override { return {}; } IDefinesAndIncludesManager::Type type() const override { return IDefinesAndIncludesManager::UserDefined; } QHash defines; Path::List includes; }; TestDUChain::~TestDUChain() = default; void TestDUChain::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); auto core = TestCore::initialize(); delete core->projectController(); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } void TestDUChain::cleanup() { if (m_provider) { IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data()); } } void TestDUChain::init() { m_provider.reset(new TestEnvironmentProvider); IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data()); } struct ExpectedComment { QString identifier; QString comment; }; Q_DECLARE_METATYPE(ExpectedComment) Q_DECLARE_METATYPE(AbstractType::WhichType) void TestDUChain::testComments() { QFETCH(QString, code); QFETCH(ExpectedComment, expectedComment); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto candidates = top->findDeclarations(QualifiedIdentifier(expectedComment.identifier)); QVERIFY(!candidates.isEmpty()); auto decl = candidates.first(); QString comment = QString::fromLocal8Bit(decl->comment()); comment = KDevelop::htmlToPlainText(comment, KDevelop::CompleteMode); QCOMPARE(comment, expectedComment.comment); } void TestDUChain::testComments_data() { QTest::addColumn("code"); QTest::addColumn("expectedComment"); // note: Clang only retrieves the comments when in doxygen-style format (i.e. '///', '/**', '///<') QTest::newRow("invalid1") << "//this is foo\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("invalid2") << "/*this is foo*/\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("basic1") << "///this is foo\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("basic2") << "/**this is foo*/\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("enumerator") << "enum Foo { bar1, ///localDeclarations().size(), 2); auto decl = file.topContext()->localDeclarations()[1]; QVERIFY(decl); auto function = dynamic_cast(decl); QVERIFY(function); auto functionType = function->type(); QVERIFY(functionType); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("namespace", "The ElaboratedType is not exposed through the libclang interface, not much we can do here", Abort); #endif QVERIFY(functionType->returnType()->whichType() != AbstractType::TypeDelayed); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("typedef", "After using clang_getCanonicalType on ElaboratedType all typedef information get's stripped away", Continue); #endif QCOMPARE(functionType->returnType()->whichType(), type); } void TestDUChain::testElaboratedType_data() { QTest::addColumn("code"); QTest::addColumn("type"); QTest::newRow("namespace") << "namespace NS{struct Type{};} struct NS::Type foo();" << AbstractType::TypeStructure; QTest::newRow("enum") << "enum Enum{}; enum Enum foo();" << AbstractType::TypeEnumeration; QTest::newRow("typedef") << "namespace NS{typedef int type;} NS::type foo();" << AbstractType::TypeAlias; } void TestDUChain::testInclude() { TestFile header("int foo() { return 42; }\n", "h"); // NOTE: header is _not_ explictly being parsed, instead the impl job does that TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); impl.parse(TopDUContext::AllDeclarationsContextsAndUses); auto implCtx = impl.topContext(); QVERIFY(implCtx); DUChainReadLocker lock; QCOMPARE(implCtx->localDeclarations().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); } void TestDUChain::testMissingInclude() { auto code = R"( #pragma once #include "missing1.h" template class A { T a; }; #include "missing2.h" class B : public A { }; )"; // NOTE: This fails and needs fixing. If the include of "missing2.h" // above is commented out, then it doesn't fail. Maybe // clang stops processing when it encounters the second missing // header, or similar. TestFile header(code, "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n", "cpp", &header); QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); DUChainReadLocker lock; auto top = impl.topContext(); QVERIFY(top); QCOMPARE(top->importedParentContexts().count(), 1); TopDUContext* headerCtx = dynamic_cast(top->importedParentContexts().first().context(top)); QVERIFY(headerCtx); QCOMPARE(headerCtx->url(), header.url()); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("", "Second missing header isn't reported", Continue); #endif QCOMPARE(headerCtx->problems().count(), 2); QCOMPARE(headerCtx->localDeclarations().count(), 2); auto a = dynamic_cast(headerCtx->localDeclarations().first()); QVERIFY(a); auto b = dynamic_cast(headerCtx->localDeclarations().last()); QVERIFY(b); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("", "Base class isn't assigned correctly", Continue); #endif QCOMPARE(b->baseClassesSize(), 1u); #if CINDEX_VERSION_MINOR < 34 // at least the one problem we have should have been propagated QCOMPARE(top->problems().count(), 1); #else // two errors: // /tmp/testfile_f32415.h:3:10: error: 'missing1.h' file not found // /tmp/testfile_f32415.h:11:10: error: 'missing2.h' file not found QCOMPARE(top->problems().count(), 2); #endif } QByteArray createCode(const QByteArray& prefix, const int functions) { QByteArray code; code += "#ifndef " + prefix + "_H\n"; code += "#define " + prefix + "_H\n"; for (int i = 0; i < functions; ++i) { code += "void myFunc_" + prefix + "(int arg1, char arg2, const char* arg3);\n"; } code += "#endif\n"; return code; } void TestDUChain::testIncludeLocking() { TestFile header1(createCode("Header1", 1000), "h"); TestFile header2(createCode("Header2", 1000), "h"); TestFile header3(createCode("Header3", 1000), "h"); ICore::self()->languageController()->backgroundParser()->setThreadCount(3); TestFile impl1("#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl2("#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl3("#include \"" + header3.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); impl1.parse(TopDUContext::AllDeclarationsContextsAndUses); impl2.parse(TopDUContext::AllDeclarationsContextsAndUses); impl3.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(impl1.waitForParsed(5000)); QVERIFY(impl2.waitForParsed(5000)); QVERIFY(impl3.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(DUChain::self()->chainForDocument(header1.url())); QVERIFY(DUChain::self()->chainForDocument(header2.url())); QVERIFY(DUChain::self()->chainForDocument(header3.url())); } void TestDUChain::testReparse() { TestFile file("int main() { int i = 42; return i; }", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); DeclarationPointer mainDecl; DeclarationPointer iDecl; for (int i = 0; i < 3; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 1); DUContext *exprContext = file.topContext()->childContexts().first()->childContexts().first(); QCOMPARE(exprContext->localDeclarations().size(), 1); if (i) { QVERIFY(mainDecl); QCOMPARE(mainDecl.data(), file.topContext()->localDeclarations().first()); QVERIFY(iDecl); QCOMPARE(iDecl.data(), exprContext->localDeclarations().first()); } mainDecl = file.topContext()->localDeclarations().first(); iDecl = exprContext->localDeclarations().first(); QVERIFY(mainDecl->uses().isEmpty()); QCOMPARE(iDecl->uses().size(), 1); QCOMPARE(iDecl->uses().begin()->size(), 1); if (i == 1) { file.setFileContents("int main()\n{\nfloat i = 13; return i - 5;\n}\n"); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseError() { TestFile file("int i = 1 / 0;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); if (!i) { QCOMPARE(file.topContext()->problems().size(), 1); file.setFileContents("int i = 0;\n"); } else { QCOMPARE(file.topContext()->problems().size(), 0); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testTemplate() { TestFile file("template struct foo { T bar; };\n" "int main() { foo myFoo; return myFoo.bar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 2); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo< T >")).size(), 1); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo< T >::bar")).size(), 1); auto mainCtx = file.topContext()->localDeclarations().last()->internalContext()->childContexts().first(); QVERIFY(mainCtx); auto myFoo = mainCtx->localDeclarations().first(); QVERIFY(myFoo); QCOMPARE(myFoo->abstractType()->toString().remove(' '), QStringLiteral("foo")); } void TestDUChain::testNamespace() { TestFile file("namespace foo { struct bar { int baz; }; }\n" "int main() { foo::bar myBar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 1); DUContext* top = file.topContext().data(); DUContext* mainCtx = file.topContext()->childContexts().last(); auto foo = top->localDeclarations().first(); QCOMPARE(foo->qualifiedIdentifier().toString(), QString("foo")); DUContext* fooCtx = file.topContext()->childContexts().first(); QCOMPARE(fooCtx->localScopeIdentifier().toString(), QString("foo")); QCOMPARE(fooCtx->scopeIdentifier(true).toString(), QString("foo")); QCOMPARE(fooCtx->localDeclarations().size(), 1); auto bar = fooCtx->localDeclarations().first(); QCOMPARE(bar->qualifiedIdentifier().toString(), QString("foo::bar")); QCOMPARE(fooCtx->childContexts().size(), 1); DUContext* barCtx = fooCtx->childContexts().first(); QCOMPARE(barCtx->localScopeIdentifier().toString(), QString("bar")); QCOMPARE(barCtx->scopeIdentifier(true).toString(), QString("foo::bar")); QCOMPARE(barCtx->localDeclarations().size(), 1); auto baz = barCtx->localDeclarations().first(); QCOMPARE(baz->qualifiedIdentifier().toString(), QString("foo::bar::baz")); for (auto ctx : {top, mainCtx}) { QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar::baz")).size(), 1); } } void TestDUChain::testAutoTypeDeduction() { TestFile file(R"( const volatile auto foo = 5; template struct myTemplate {}; myTemplate& > templRefParam; auto autoTemplRefParam = templRefParam; )", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 4); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); Declaration* decl = ctx->findDeclarations(QualifiedIdentifier("foo"))[0]; QCOMPARE(decl->identifier(), Identifier("foo")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); #if CINDEX_VERSION_MINOR < 31 QCOMPARE(decl->toString(), QStringLiteral("const volatile auto foo")); #else QCOMPARE(decl->toString(), QStringLiteral("const volatile int foo")); #endif decl = ctx->findDeclarations(QualifiedIdentifier("autoTemplRefParam"))[0]; QVERIFY(decl); QVERIFY(decl->abstractType()); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "Auto type is not exposed via LibClang", Continue); #endif QCOMPARE(decl->abstractType()->toString(), QStringLiteral("myTemplate< myTemplate< int >& >")); } void TestDUChain::testTypeDeductionInTemplateInstantiation() { // see: http://clang-developers.42468.n3.nabble.com/RFC-missing-libclang-query-functions-features-td2504253.html TestFile file("template struct foo { T member; } foo f; auto i = f.member;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 3); - Declaration* decl = 0; + Declaration* decl = nullptr; // check 'foo' declaration decl = ctx->localDeclarations()[0]; QVERIFY(decl); QCOMPARE(decl->identifier(), Identifier("foo< T >")); // check type of 'member' inside declaration-scope QCOMPARE(ctx->childContexts().size(), 1); DUContext* fooCtx = ctx->childContexts().first(); QVERIFY(fooCtx); // Should there really be two declarations? QCOMPARE(fooCtx->localDeclarations().size(), 2); decl = fooCtx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("member")); // check type of 'member' in definition of 'f' decl = ctx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("f")); decl = ctx->localDeclarations()[2]; QCOMPARE(decl->identifier(), Identifier("i")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); } void TestDUChain::testVirtualMemberFunction() { //Forward-declarations with "struct" or "class" are considered equal, so make sure the override is detected correctly. TestFile file("struct S {}; struct A { virtual S* ret(); }; struct B : public A { virtual S* ret(); };", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->childContexts()[2]->localDeclarations().count(), 1); Declaration* decl = top->childContexts()[2]->localDeclarations()[0]; QCOMPARE(decl->identifier(), Identifier("ret")); QVERIFY(DUChainUtils::getOverridden(decl)); } void TestDUChain::testBaseClasses() { TestFile file("class Base {}; class Inherited : public Base {};", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); ClassDeclaration* inheritedDecl = dynamic_cast(top->localDeclarations()[1]); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->baseClassesSize(), 1u); QCOMPARE(baseDecl->uses().count(), 1); QCOMPARE(baseDecl->uses().first().count(), 1); QCOMPARE(baseDecl->uses().first().first(), RangeInRevision(0, 40, 0, 44)); } void TestDUChain::testReparseBaseClasses() { TestFile file("struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseBaseClassesTemplates() { TestFile file("template struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testGetInheriters_data() { QTest::addColumn("code"); QTest::newRow("inline") << "struct Base { struct Inner {}; }; struct Inherited : Base, Base::Inner {};"; QTest::newRow("outline") << "struct Base { struct Inner; }; struct Base::Inner {}; struct Inherited : Base, Base::Inner {};"; } void TestDUChain::testGetInheriters() { QFETCH(QString, code); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); DUContext* baseCtx = baseDecl->internalContext(); QVERIFY(baseCtx); QCOMPARE(baseCtx->localDeclarations().count(), 1); Declaration* innerDecl = baseCtx->localDeclarations().first(); QCOMPARE(innerDecl->identifier(), Identifier("Inner")); if (auto forward = dynamic_cast(innerDecl)) { innerDecl = forward->resolve(top); } QVERIFY(dynamic_cast(innerDecl)); Declaration* inheritedDecl = top->localDeclarations().last(); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); uint maxAllowedSteps = uint(-1); auto baseInheriters = DUChainUtils::getInheriters(baseDecl, maxAllowedSteps); QCOMPARE(baseInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto innerInheriters = DUChainUtils::getInheriters(innerDecl, maxAllowedSteps); QCOMPARE(innerInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto inheritedInheriters = DUChainUtils::getInheriters(inheritedDecl, maxAllowedSteps); QCOMPARE(inheritedInheriters.count(), 0); } void TestDUChain::testGlobalFunctionDeclaration() { TestFile file("void foo(int arg1, char arg2);\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); file.waitForParsed(); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); QCOMPARE(file.topContext()->childContexts().size(), 1); QVERIFY(!file.topContext()->childContexts().first()->inSymbolTable()); } void TestDUChain::testFunctionDefinitionVsDeclaration() { TestFile file("void func(); void func() {}\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto funcDecl = file.topContext()->localDeclarations()[0]; QVERIFY(!funcDecl->isDefinition()); QVERIFY(!dynamic_cast(funcDecl)); auto funcDef = file.topContext()->localDeclarations()[1]; QVERIFY(dynamic_cast(funcDef)); QVERIFY(funcDef->isDefinition()); } void TestDUChain::testEnsureNoDoubleVisit() { // On some language construct, we may up visiting the same cursor multiple times // Example: "struct SomeStruct {} s;" // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // decl: "struct SomeStruct s " of kind VarDecl (9) in main.cpp@[(1,1),(1,19)] // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // // => We end up visiting the StructDecl twice (or more) // That's because we use clang_visitChildren not just on the translation unit cursor. // Apparently just "recursing" vs. "visiting children explicitly" // results in a different AST traversal TestFile file("struct SomeStruct {} s;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); // there should only be one declaration for "SomeStruct" auto candidates = top->findDeclarations(QualifiedIdentifier("SomeStruct")); QCOMPARE(candidates.size(), 1); } void TestDUChain::testParsingEnvironment() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(file.topContext()->parsingEnvironmentFile().data())); QCOMPARE(envFile->features(), astFeatures); QVERIFY(envFile->featuresSatisfied(astFeatures)); QCOMPARE(envFile->environmentQuality(), ClangParsingEnvironment::Source); // if no environment is given, no update should be triggered QVERIFY(!envFile->needsUpdate()); // same env should also not trigger a reparse ClangParsingEnvironment env = session.environment(); QCOMPARE(env.quality(), ClangParsingEnvironment::Source); QVERIFY(!envFile->needsUpdate(&env)); // but changing the environment should trigger an update env.addIncludes(Path::List() << Path("/foo/bar/baz")); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // setting the environment quality higher should require an update env.setQuality(ClangParsingEnvironment::BuildSystem); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // changing defines requires an update env.addDefines(QHash{ { "foo", "bar" } }); QVERIFY(envFile->needsUpdate(&env)); // but only when changing the defines for the envFile's TU const auto barTU = IndexedString("bar.cpp"); const auto oldTU = env.translationUnitUrl(); env.setTranslationUnitUrl(barTU); QCOMPARE(env.translationUnitUrl(), barTU); QVERIFY(!envFile->needsUpdate(&env)); env.setTranslationUnitUrl(oldTU); QVERIFY(envFile->needsUpdate(&env)); // update it again envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); lastEnv = env; // now compare against a lower quality environment // in such a case, we do not want to trigger an update env.setQuality(ClangParsingEnvironment::Unknown); env.setTranslationUnitUrl(barTU); QVERIFY(!envFile->needsUpdate(&env)); // even when the environment changes env.addIncludes(Path::List() << Path("/lalalala")); QVERIFY(!envFile->needsUpdate(&env)); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); QVERIFY(DUChain::self()->environmentFileForDocument(indexed)); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(DUChain::self()->environmentFileForDocument(indexed).data())); QVERIFY(envFile); QCOMPARE(envFile->features(), features); QVERIFY(envFile->featuresSatisfied(features)); QVERIFY(!envFile->needsUpdate(&lastEnv)); DUChain::self()->removeDocumentChain(indexed.data()); } } void TestDUChain::testActiveDocumentHasASTAttached() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); QVERIFY(top->ast()); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); } QUrl url; { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(!ctx->ast()); url = ctx->url().toUrl(); } QVERIFY(!QFileInfo::exists(url.toLocalFile())); QFile file(url.toLocalFile()); file.open(QIODevice::WriteOnly); Q_ASSERT(file.isOpen()); auto document = ICore::self()->documentController()->openDocument(url); QVERIFY(document); ICore::self()->documentController()->activateDocument(document); QApplication::processEvents(); ICore::self()->languageController()->backgroundParser()->parseDocuments(); QThread::sleep(1); document->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(ctx->ast()); } DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(indexed.data()); } void TestDUChain::testActiveDocumentsGetBestPriority() { // note: this test would make more sense in kdevplatform, but we don't have a language plugin available there // (required for background parsing) // TODO: Create a fake-language plugin in kdevplatform for testing purposes, use that. TestFile file1("int main() {}\n", "cpp"); TestFile file2("int main() {}\n", "cpp"); TestFile file3("int main() {}\n", "cpp"); DUChain::self()->storeToDisk(); auto backgroundParser = ICore::self()->languageController()->backgroundParser(); QVERIFY(!backgroundParser->isQueued(file1.url())); auto documentController = ICore::self()->documentController(); // open first document (no activation) auto doc = documentController->openDocument(file1.url().toUrl(), KTextEditor::Range::invalid(), {IDocumentController::DoNotActivate}); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file1.url())); QCOMPARE(backgroundParser->priorityForDocument(file1.url()), (int)BackgroundParser::NormalPriority); // open second document, activate doc = documentController->openDocument(file2.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file2.url())); QCOMPARE(backgroundParser->priorityForDocument(file2.url()), (int)BackgroundParser::BestPriority); // open third document, activate, too doc = documentController->openDocument(file3.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file3.url())); QCOMPARE(backgroundParser->priorityForDocument(file3.url()), (int)BackgroundParser::BestPriority); } void TestDUChain::testSystemIncludes() { ClangParsingEnvironment env; Path::List projectIncludes = { Path("/projects/1"), Path("/projects/1/sub"), Path("/projects/2"), Path("/projects/2/sub") }; env.addIncludes(projectIncludes); auto includes = env.includes(); // no project paths set, so everything is considered a system include QCOMPARE(includes.system, projectIncludes); QVERIFY(includes.project.isEmpty()); Path::List systemIncludes = { Path("/sys"), Path("/sys/sub") }; env.addIncludes(systemIncludes); includes = env.includes(); QCOMPARE(includes.system, projectIncludes + systemIncludes); QVERIFY(includes.project.isEmpty()); Path::List projects = { Path("/projects/1"), Path("/projects/2") }; env.setProjectPaths(projects); // now the list should be properly separated QCOMPARE(env.projectPaths(), projects); includes = env.includes(); QCOMPARE(includes.system, systemIncludes); QCOMPARE(includes.project, projectIncludes); } void TestDUChain::benchDUChainBuilder() { QBENCHMARK_ONCE { TestFile file( "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(60000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); } } void TestDUChain::testReparseWithAllDeclarationsContextsAndUses() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto dec = file.topContext()->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies is disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); } file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(500)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto mainDecl = file.topContext()->localDeclarations()[1]; QVERIFY(mainDecl->uses().isEmpty()); auto foo = file.topContext()->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); } } void TestDUChain::testReparseOnDocumentActivated() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; auto ctx = file.topContext(); QVERIFY(ctx); QCOMPARE(ctx->childContexts().size(), 2); QCOMPARE(ctx->localDeclarations().size(), 2); auto dec = ctx->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies was disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); QVERIFY(!ctx->ast()); } auto backgroundParser = ICore::self()->languageController()->backgroundParser(); QVERIFY(!backgroundParser->isQueued(file.url())); auto doc = ICore::self()->documentController()->openDocument(file.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file.url())); QSignalSpy spy(backgroundParser, &BackgroundParser::parseJobFinished); spy.wait(); doc->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = file.topContext(); QCOMPARE(ctx->features() & TopDUContext::AllDeclarationsContextsAndUses, static_cast(TopDUContext::AllDeclarationsContextsAndUses)); QVERIFY(ctx->topContext()->ast()); } } void TestDUChain::testReparseInclude() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); // Use TopDUContext::AST to imitate that document is opened in the editor, so that ClangParseJob can store translation unit, that'll be used for reparsing. impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsAndContexts|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->importedParentContexts().size(), 1); } impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->localDeclarations().size(), 1); QCOMPARE(implCtx->importedParentContexts().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); } void TestDUChain::testReparseChangeEnvironment() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); uint hashes[3] = {0, 0, 0}; for (int i = 0; i < 3; ++i) { impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(impl.topContext()); auto env = dynamic_cast(impl.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); hashes[i] = env->environmentHash(); QVERIFY(hashes[i]); // we should never end up with multiple env files or chains in memory for these files QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); } // in every run, we expect the environment to have changed for (int j = 0; j < i; ++j) { QVERIFY(hashes[i] != hashes[j]); } if (i == 0) { // 1) change defines m_provider->defines.insert("foooooooo", "baaar!"); } else if (i == 1) { // 2) change includes m_provider->includes.append(Path("/foo/bar/asdf/lalala")); } // 3) stop } } void TestDUChain::testMacroDependentHeader() { TestFile header("struct MY_CLASS { class Q{Q(); int m;}; int m; };\n", "h"); TestFile impl("#define MY_CLASS A\n" "#include \"" + header.url().byteArray() + "\"\n" "#undef MY_CLASS\n" "#define MY_CLASS B\n" "#include \"" + header.url().byteArray() + "\"\n" "#undef MY_CLASS\n" "A a;\n" "const A::Q aq;\n" "B b;\n" "const B::Q bq;\n" "int am = a.m;\n" "int aqm = aq.m;\n" "int bm = b.m;\n" "int bqm = bq.m;\n" , "cpp", &header); impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(500000)); DUChainReadLocker lock; TopDUContext* top = impl.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().size(), 10); // 2x macro, then a, aq, b, bq QCOMPARE(top->importedParentContexts().size(), 1); AbstractType::Ptr type = top->localDeclarations()[2]->abstractType(); StructureType* sType = dynamic_cast(type.data()); QVERIFY(sType); QCOMPARE(sType->toString(), QString("A")); Declaration* decl = sType->declaration(top); QVERIFY(decl); AbstractType::Ptr type2 = top->localDeclarations()[4]->abstractType(); StructureType* sType2 = dynamic_cast(type2.data()); QVERIFY(sType2); QCOMPARE(sType2->toString(), QString("B")); Declaration* decl2 = sType2->declaration(top); QVERIFY(decl2); TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[0].context(top)); QVERIFY(top2); QCOMPARE(top2->localDeclarations().size(), 2); QCOMPARE(top2->localDeclarations()[0], decl); QCOMPARE(top2->localDeclarations()[1], decl2); qDebug() << "DECL RANGE:" << top2->localDeclarations()[0]->range().castToSimpleRange(); qDebug() << "CTX RANGE:" << top2->localDeclarations()[0]->internalContext()->range().castToSimpleRange(); // validate uses: QCOMPARE(top->usesCount(), 14); QCOMPARE(top->uses()[0].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A")); QCOMPARE(top->uses()[1].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A")); QCOMPARE(top->uses()[2].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q")); QCOMPARE(top->uses()[3].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B")); QCOMPARE(top->uses()[4].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B")); QCOMPARE(top->uses()[5].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q")); QCOMPARE(top->uses()[6].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(top->uses()[7].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::m")); QCOMPARE(top->uses()[8].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("aq")); QCOMPARE(top->uses()[9].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q::m")); QCOMPARE(top->uses()[10].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(top->uses()[11].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::m")); QCOMPARE(top->uses()[12].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("bq")); QCOMPARE(top->uses()[13].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q::m")); } void TestDUChain::testHeaderParsingOrder1() { TestFile header("typedef const A B;\n", "h"); TestFile impl("template class A{};\n" "#include \"" + header.url().byteArray() + "\"\n" "B c;", "cpp", &header); impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(500000)); DUChainReadLocker lock; TopDUContext* top = impl.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().size(), 2); QCOMPARE(top->importedParentContexts().size(), 1); AbstractType::Ptr type = top->localDeclarations()[1]->abstractType(); TypeAliasType* aType = dynamic_cast(type.data()); QVERIFY(aType); AbstractType::Ptr targetType = aType->type(); QVERIFY(targetType); IdentifiedType *idType = dynamic_cast(targetType.data()); QVERIFY(idType); // this declaration could be resolved, because it was created with an // indirect DeclarationId that is resolved from the perspective of 'top' Declaration* decl = idType->declaration(top); // NOTE: the decl. doesn't know (yet) about the template insantiation QVERIFY(decl); QCOMPARE(decl, top->localDeclarations()[0]); // now ensure that a use was build for 'A' in header1 TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[0].context(top)); QVERIFY(top2); QEXPECT_FAIL("", "the use could not be created because the corresponding declaration didn't exist yet", Continue); QCOMPARE(top2->usesCount(), 1); // Declaration* decl2 = top2->uses()[0].usedDeclaration(top2); // QVERIFY(decl2); // QCOMPARE(decl, decl2); } void TestDUChain::testHeaderParsingOrder2() { TestFile header("template class A{};\n", "h"); TestFile header2("typedef const A B;\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "B c;", "cpp", &header); impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(500000)); DUChainReadLocker lock; TopDUContext* top = impl.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().size(), 1); QCOMPARE(top->importedParentContexts().size(), 2); AbstractType::Ptr type = top->localDeclarations()[0]->abstractType(); TypeAliasType* aType = dynamic_cast(type.data()); QVERIFY(aType); AbstractType::Ptr targetType = aType->type(); QVERIFY(targetType); IdentifiedType *idType = dynamic_cast(targetType.data()); QVERIFY(idType); Declaration* decl = idType->declaration(top); // NOTE: the decl. doesn't know (yet) about the template insantiation QVERIFY(decl); // now ensure that a use was build for 'A' in header2 TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[1].context(top)); QVERIFY(top2); QCOMPARE(top2->usesCount(), 1); Declaration* decl2 = top2->uses()[0].usedDeclaration(top2); QCOMPARE(decl, decl2); } void TestDUChain::testMacrosRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testMacroUses() { TestFile file("#define USER(x) x\n#define USED\nUSER(USED)", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition1 = file.topContext()->localDeclarations()[0]; auto macroDefinition2 = file.topContext()->localDeclarations()[1]; QCOMPARE(macroDefinition1->uses().size(), 1); QCOMPARE(macroDefinition1->uses().begin()->first(), RangeInRevision(2,0,2,4)); #if CINDEX_VERSION_MINOR < 32 QEXPECT_FAIL("", "This appears to be a clang bug, the AST doesn't contain the macro use", Continue); #endif QCOMPARE(macroDefinition2->uses().size(), 1); if (macroDefinition2->uses().size()) { QCOMPARE(macroDefinition2->uses().begin()->first(), RangeInRevision(2,5,2,9)); } } void TestDUChain::testMultiLineMacroRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x\n);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testNestedMacroRanges() { TestFile file("#define INNER int var; var = 0;\n#define MACRO() INNER\nint main(){MACRO(\n);}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto main = file.topContext()->localDeclarations()[2]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto var = mainCtx->localDeclarations().first(); QVERIFY(var); QCOMPARE(var->range(), RangeInRevision(2,11,2,11)); QCOMPARE(var->uses().size(), 1); QCOMPARE(var->uses().begin()->first(), RangeInRevision(2,11,2,11)); } void TestDUChain::testNestedImports() { TestFile B("#pragma once\nint B();\n", "h"); TestFile C("#pragma once\n#include \"" + B.url().byteArray() + "\"\nint C();\n", "h"); TestFile A("#include \"" + B.url().byteArray() + "\"\n" + "#include \"" + C.url().byteArray() + "\"\nint A();\n", "cpp"); A.parse(); QVERIFY(A.waitForParsed(5000)); DUChainReadLocker lock; auto BCtx = DUChain::self()->chainForDocument(B.url().toUrl()); QVERIFY(BCtx); QVERIFY(BCtx->importedParentContexts().isEmpty()); auto CCtx = DUChain::self()->chainForDocument(C.url().toUrl()); QVERIFY(CCtx); QCOMPARE(CCtx->importedParentContexts().size(), 1); QVERIFY(CCtx->imports(BCtx, CursorInRevision(1, 10))); auto ACtx = A.topContext(); QVERIFY(ACtx); QCOMPARE(ACtx->importedParentContexts().size(), 2); QVERIFY(ACtx->imports(BCtx, CursorInRevision(0, 10))); QVERIFY(ACtx->imports(CCtx, CursorInRevision(1, 10))); } void TestDUChain::testEnvironmentWithDifferentOrderOfElements() { TestFile file("int main();\n", "cpp"); m_provider->includes.clear(); m_provider->includes.append(Path("/path1")); m_provider->includes.append(Path("/path2")); m_provider->defines.clear(); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); uint previousHash = 0; for (int i: {0, 1, 2, 3}) { file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); auto env = dynamic_cast(file.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); if (previousHash) { if (i == 3) { QVERIFY(previousHash != env->environmentHash()); } else { QCOMPARE(previousHash, env->environmentHash()); } } previousHash = env->environmentHash(); QVERIFY(previousHash); } if (i == 0) { //Change order of defines. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); } else if (i == 1) { //Add the same macros twice. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); } else if (i == 2) { //OTOH order of includes should change hash of the environment. m_provider->includes.clear(); m_provider->includes.append(Path("/path2")); m_provider->includes.append(Path("/path1")); } } } void TestDUChain::testReparseMacro() { TestFile file("#define DECLARE(a) typedef struct a##_ {} *a;\nDECLARE(D);\nD d;", "cpp"); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 5); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,15)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,7)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); auto structTypedef = file.topContext()->localDeclarations()[3]; QVERIFY(structTypedef); QCOMPARE(structTypedef->range(), RangeInRevision(1,8,1,9)); QCOMPARE(structTypedef->uses().size(), 1); QCOMPARE(structTypedef->uses().begin()->first(), RangeInRevision(2,0,2,1)); } void TestDUChain::testGotoStatement() { TestFile file("int main() {\ngoto label;\ngoto label;\nlabel: return 0;}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); auto main = file.topContext()->localDeclarations()[0]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto label = mainCtx->localDeclarations().first(); QVERIFY(label); QCOMPARE(label->range(), RangeInRevision(3,0,3,5)); QCOMPARE(label->uses().size(), 1); QCOMPARE(label->uses().begin()->first(), RangeInRevision(1,5,1,10)); QCOMPARE(label->uses().begin()->last(), RangeInRevision(2,5,2,10)); } void TestDUChain::testRangesOfOperatorsInsideMacro() { TestFile file("class Test{public: Test& operator++(int);};\n#define MACRO(var) var++;\nint main(){\nTest tst; MACRO(tst)}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto testClass = file.topContext()->localDeclarations()[0]; QVERIFY(testClass); auto operatorPlusPlus = testClass->internalContext()->localDeclarations().first(); QVERIFY(operatorPlusPlus); QCOMPARE(operatorPlusPlus->uses().size(), 1); QCOMPARE(operatorPlusPlus->uses().begin()->first(), RangeInRevision(3,10,3,10)); } void TestDUChain::testUsesCreatedForDeclarations() { auto code = R"(template void functionTemplate(T); template void functionTemplate(U) {} namespace NS { class Class{}; } using NS::Class; Class function(); NS::Class function() { return {}; } int main () { functionTemplate(int()); function(); } )"; TestFile file(code, "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto functionTemplate = file.topContext()->findDeclarations(QualifiedIdentifier("functionTemplate")); QVERIFY(!functionTemplate.isEmpty()); auto functionTemplateDeclaration = DUChainUtils::declarationForDefinition(functionTemplate.first()); QVERIFY(!functionTemplateDeclaration->isDefinition()); #if CINDEX_VERSION_MINOR < 29 QEXPECT_FAIL("", "No API in LibClang to determine function template type", Continue); #endif QCOMPARE(functionTemplateDeclaration->uses().count(), 1); auto function = file.topContext()->findDeclarations(QualifiedIdentifier("function")); QVERIFY(!function.isEmpty()); auto functionDeclaration = DUChainUtils::declarationForDefinition(function.first()); QVERIFY(!functionDeclaration->isDefinition()); QCOMPARE(functionDeclaration->uses().count(), 1); } void TestDUChain::testReparseIncludeGuard() { TestFile header("#ifndef GUARD\n#define GUARD\nint something;\n#endif\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n", "cpp", &header); QVERIFY(impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST ))); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } QVERIFY(impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive))); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } } void TestDUChain::testExternC() { auto code = R"(extern "C" { void foo(); })"; TestFile file(code, "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(!top->findDeclarations(QualifiedIdentifier("foo")).isEmpty()); } void TestDUChain::testReparseUnchanged_data() { QTest::addColumn("headerCode"); QTest::addColumn("implCode"); QTest::newRow("include-guards") << R"( #ifndef GUARD #define GUARD int something; #endif )" << R"( #include "%1" )"; QTest::newRow("template-default-parameters") << R"( #ifndef TEST_H #define TEST_H template class dummy; template class dummy { int field[T]; }; #endif )" << R"( #include "%1" int main(int, char **) { dummy<> x; (void)x; } )"; } void TestDUChain::testReparseUnchanged() { QFETCH(QString, headerCode); QFETCH(QString, implCode); TestFile header(headerCode, "h"); TestFile impl(implCode.arg(header.url().str()), "cpp", &header); auto checkProblems = [&] (bool reparsed) { DUChainReadLocker lock; auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(headerCtx->problems().isEmpty()); auto implCtx = DUChain::self()->chainForDocument(impl.url()); QVERIFY(implCtx); if (reparsed && CINDEX_VERSION_MINOR > 29 && CINDEX_VERSION_MINOR < 33) { QEXPECT_FAIL("template-default-parameters", "the precompiled preamble messes the default template parameters up in clang 3.7", Continue); } QVERIFY(implCtx->problems().isEmpty()); }; impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); checkProblems(false); impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); checkProblems(true); } void TestDUChain::testTypeAliasTemplate() { TestFile file("template using TypeAliasTemplate = T;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto templateAlias = file.topContext()->localDeclarations().last(); QVERIFY(templateAlias); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "TypeAliasTemplate is not exposed via LibClang", Abort); #endif QVERIFY(templateAlias->abstractType()); QCOMPARE(templateAlias->abstractType()->toString(), QStringLiteral("TypeAliasTemplate")); } void TestDUChain::testDeclarationsInsideMacroExpansion() { TestFile header("#define DECLARE(a) typedef struct a##__ {int var;} *a\nDECLARE(D);\n", "h"); TestFile file("#include \"" + header.url().byteArray() + "\"\nint main(){\nD d; d->var;}\n", "cpp"); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); auto context = file.topContext()->childContexts().first()->childContexts().first(); QVERIFY(context); QCOMPARE(context->localDeclarations().size(), 1); QCOMPARE(context->usesCount(), 3); QCOMPARE(context->uses()[0].m_range, RangeInRevision({2, 0}, {2, 1})); QCOMPARE(context->uses()[1].m_range, RangeInRevision({2, 5}, {2, 6})); QCOMPARE(context->uses()[2].m_range, RangeInRevision({2, 8}, {2, 11})); } // see also: https://bugs.kde.org/show_bug.cgi?id=368067 void TestDUChain::testForwardTemplateTypeParameterContext() { TestFile file(R"( template class Foo; class MatchingName { void bar(); }; void MatchingName::bar() { } )", "cpp"); file.parse(); QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; const auto top = file.topContext(); QVERIFY(top); DUChainDumper dumper(DUChainDumper::Features(DUChainDumper::DumpContext | DUChainDumper::DumpProblems)); dumper.dump(top); auto declarations = top->localDeclarations(); QCOMPARE(declarations.size(), 2); } // see also: https://bugs.kde.org/show_bug.cgi?id=368460 void TestDUChain::testTemplateFunctionParameterName() { TestFile file(R"( template void foo(int name); void bar(int name); )", "cpp"); file.parse(); QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; const auto top = file.topContext(); QVERIFY(top); DUChainDumper dumper(DUChainDumper::Features(DUChainDumper::DumpContext | DUChainDumper::DumpProblems)); dumper.dump(top); auto declarations = top->localDeclarations(); QCOMPARE(declarations.size(), 2); for (auto decl : declarations) { auto ctx = DUChainUtils::getArgumentContext(decl); QVERIFY(ctx); auto args = ctx->localDeclarations(); if (decl == declarations.first()) QEXPECT_FAIL("", "We get two declarations, for both template and args :(", Continue); QCOMPARE(args.size(), 1); if (decl == declarations.first()) QEXPECT_FAIL("", "see above, this then triggers T T here", Continue); QCOMPARE(args.first()->toString(), QStringLiteral("int name")); } } static bool containsErrors(const QList& problems) { auto it = std::find_if(problems.begin(), problems.end(), [] (const Problem::Ptr& problem) { return problem->severity() == Problem::Error; }); return it != problems.end(); } static bool expectedXmmintrinErrors(const QList& problems) { foreach (const auto& problem, problems) { if (problem->severity() == Problem::Error && !problem->description().contains("Cannot initialize a parameter of type")) { return false; } } return true; } static void verifyNoErrors(TopDUContext* top, QSet& checked) { const auto problems = top->problems(); if (containsErrors(problems)) { qDebug() << top->url() << top->problems(); if (top->url().str().endsWith("xmmintrin.h") && expectedXmmintrinErrors(problems)) { QEXPECT_FAIL("", "there are still some errors in xmmintrin.h b/c some clang provided intrinsincs are more strict than the GCC ones.", Continue); QVERIFY(false); } else { QFAIL("parse error detected"); } } const auto imports = top->importedParentContexts(); foreach (const auto& import, imports) { auto ctx = import.context(top); QVERIFY(ctx); auto importedTop = ctx->topContext(); if (checked.contains(importedTop)) { continue; } checked.insert(importedTop); verifyNoErrors(importedTop, checked); } } void TestDUChain::testGccCompatibility() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Compiler").writeEntry("Name", "GCC"); m_projectController->addProject(project); { // TODO: Also test in C mode. Currently it doesn't work (some intrinsics missing?) TestFile file(R"( #include int main() { return 0; } )", "cpp", project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QSet checked; verifyNoErrors(file.topContext(), checked); } m_projectController->closeAllProjects(); } void TestDUChain::testQtIntegration() { QTemporaryDir includeDir; { QDir dir(includeDir.path()); dir.mkdir("QtCore"); // create the file but don't put anything in it QFile header(includeDir.path() + "/QtCore/qobjectdefs.h"); QVERIFY(header.open(QIODevice::WriteOnly | QIODevice::Text)); } QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); m_provider->defines.clear(); m_provider->includes = {Path(includeDir.path() + "/QtCore")}; m_projectController->addProject(project); { TestFile file(R"( #define slots #define signals #define Q_SLOTS #define Q_SIGNALS #include struct MyObject { public: void other1(); public slots: void slot1(); signals: void signal1(); private Q_SLOTS: void slot2(); Q_SIGNALS: void signal2(); public: void other2(); }; )", "cpp", project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); const auto methods = top->childContexts().last()->localDeclarations(); QCOMPARE(methods.size(), 6); foreach(auto method, methods) { auto classFunction = dynamic_cast(method); QVERIFY(classFunction); auto id = classFunction->identifier().toString(); QCOMPARE(classFunction->isSignal(), id.startsWith(QLatin1String("signal"))); QCOMPARE(classFunction->isSlot(), id.startsWith(QLatin1String("slot"))); } } m_projectController->closeAllProjects(); } diff --git a/languages/clang/util/clangtypes.cpp b/languages/clang/util/clangtypes.cpp index a26502a2f9..a3a2bf73e3 100644 --- a/languages/clang/util/clangtypes.cpp +++ b/languages/clang/util/clangtypes.cpp @@ -1,233 +1,233 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangtypes.h" #include #include #include #include #include using namespace KDevelop; namespace { template inline T cursorForCXSrcLoc(CXSourceLocation loc) { uint line = 0; uint column = 0; - clang_getFileLocation(loc, 0, &line, &column, 0); + clang_getFileLocation(loc, nullptr, &line, &column, nullptr); return {static_cast(line-1), static_cast(column-1)}; } } ClangString::ClangString(CXString string) : string(string) { } ClangString::~ClangString() { clang_disposeString(string); } const char* ClangString::c_str() const { return clang_getCString(string); } bool ClangString::isEmpty() const { auto str = c_str(); return !str || !str[0]; } QString ClangString::toString() const { return QString::fromUtf8(c_str()); } QByteArray ClangString::toByteArray() const { return QByteArray(c_str()); } IndexedString ClangString::toIndexed() const { return IndexedString(c_str()); } QTextStream& operator<<(QTextStream& stream, const ClangString& str) { return stream << str.toString(); } QDebug operator<<(QDebug stream, const ClangString& str) { return stream << str.toString(); } ClangLocation::ClangLocation(CXSourceLocation location) : location(location) { } ClangLocation::operator DocumentCursor() const { uint line = 0; uint column = 0; CXFile file; - clang_getFileLocation(location, &file, &line, &column, 0); + clang_getFileLocation(location, &file, &line, &column, nullptr); ClangString fileName(clang_getFileName(file)); return {IndexedString(fileName.c_str()), {static_cast(line-1), static_cast(column-1)}}; } ClangLocation::operator KTextEditor::Cursor() const { return cursorForCXSrcLoc(location); } ClangLocation::operator CursorInRevision() const { return cursorForCXSrcLoc(location); } ClangLocation::operator CXSourceLocation() const { return location; } ClangLocation::~ClangLocation() { } QDebug operator<<(QDebug stream, const ClangLocation& location) { return stream << static_cast(location); } ClangRange::ClangRange(CXSourceRange range) : m_range(range) { } ClangLocation ClangRange::start() const { return {clang_getRangeStart(m_range)}; } ClangLocation ClangRange::end() const { return {clang_getRangeEnd(m_range)}; } CXSourceRange ClangRange::range() const { return m_range; } DocumentRange ClangRange::toDocumentRange() const { auto start = clang_getRangeStart(m_range); CXFile file; - clang_getFileLocation(start, &file, 0, 0, 0); + clang_getFileLocation(start, &file, nullptr, nullptr, nullptr); ClangString fileName(clang_getFileName(file)); return {IndexedString(QUrl::fromLocalFile(fileName.toString()).adjusted(QUrl::NormalizePathSegments)), toRange()}; } KTextEditor::Range ClangRange::toRange() const { return {start(), end()}; } RangeInRevision ClangRange::toRangeInRevision() const { return {start(), end()}; } ClangRange::~ClangRange() { } QDebug operator<<(QDebug stream, const ClangRange& range) { return stream << range.toDocumentRange(); } ClangTokens::ClangTokens(CXTranslationUnit unit, CXSourceRange range) : m_unit(unit) { clang_tokenize(m_unit, range, &m_tokens, &m_numTokens); } ClangTokens::~ClangTokens() { clang_disposeTokens(m_unit, m_tokens, m_numTokens); } CXToken* ClangTokens::begin() const { return m_tokens; } CXToken* ClangTokens::end() const { return m_tokens + m_numTokens; } std::reverse_iterator ClangTokens::rbegin() const { return std::reverse_iterator(end()); } std::reverse_iterator ClangTokens::rend() const { return std::reverse_iterator(begin()); } uint ClangTokens::size() const { return m_numTokens; } CXToken ClangTokens::at(uint index) const { Q_ASSERT(index < m_numTokens); return m_tokens[index]; } CXTranslationUnit ClangTokens::unit() const { return m_unit; } QDebug operator<<(QDebug stream, const ClangTokens& tokens) { stream << "ClangTokens {"; for (uint i = 0; i < tokens.size(); ++i) { stream << i << tokens.at(i) << clang_getTokenSpelling(tokens.unit(), tokens.at(i)) << ","; } return stream << "}"; } QDebug operator<<(QDebug stream, const CXToken& token) { return stream << clang_getTokenKind(token); } diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilersmodel.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilersmodel.cpp index 542511d0ba..04195b5bfa 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilersmodel.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilersmodel.cpp @@ -1,308 +1,308 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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 "compilersmodel.h" #include #include //Represents a single row in the table class TreeItem { public: TreeItem(const QList &data, TreeItem *parent = nullptr) :m_itemData(data) ,m_parentItem(parent) {} virtual ~TreeItem() { removeChilds(); } void appendChild(TreeItem *item) { m_childItems.append(item); } void removeChild(int row) { m_childItems.removeAt(row); } TreeItem *child(int row) { return m_childItems.value(row); } int childCount() const { return m_childItems.count(); } int columnCount() const { return m_itemData.count(); } virtual QVariant data(int column) const { return m_itemData.value(column); } TreeItem *parent() { return m_parentItem; } int row() const { if (m_parentItem) { return m_parentItem->m_childItems.indexOf(const_cast(this)); } return 0; } void removeChilds() { qDeleteAll(m_childItems); m_childItems.clear(); } private: QList m_childItems; QList m_itemData; TreeItem *m_parentItem; }; class CompilerItem : public TreeItem { public: CompilerItem(const CompilerPointer& compiler, TreeItem* parent) : TreeItem(QList{compiler->name(), compiler->factoryName()}, parent) , m_compiler(compiler) {} CompilerPointer compiler() { return m_compiler; } QVariant data(int column) const override { return !column ? m_compiler->name() : m_compiler->factoryName(); } private: CompilerPointer m_compiler; }; namespace { TreeItem* autoDetectedRootItem(TreeItem* root) { return root->child(0); } TreeItem* manualRootItem(TreeItem* root) { return root->child(1); } } CompilersModel::CompilersModel(QObject* parent) : QAbstractItemModel(parent) , m_rootItem(new TreeItem( QList{i18n("Name"), i18n("Type")})) { m_rootItem->appendChild(new TreeItem( QList{i18n("Auto-detected"), QString()}, m_rootItem)); m_rootItem->appendChild(new TreeItem( QList{i18n("Manual"), QString()}, m_rootItem)); } QVariant CompilersModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || (role != Qt::DisplayRole && role != CompilerDataRole)) { return QVariant(); } TreeItem *item = static_cast(index.internalPointer()); if (role == CompilerDataRole) { QVariant v; if (auto c = dynamic_cast(item)) { if (item->parent() == manualRootItem(m_rootItem)) { v.setValue(c->compiler()); } } return v; } return item->data(index.column()); } int CompilersModel::rowCount(const QModelIndex& parent) const { TreeItem *parentItem; if (parent.column() > 0) { return 0; } if (!parent.isValid()) { parentItem = m_rootItem; } else { parentItem = static_cast(parent.internalPointer()); } return parentItem->childCount(); } int CompilersModel::columnCount(const QModelIndex& parent) const { if (parent.isValid()) { return static_cast(parent.internalPointer())->columnCount(); } else { return m_rootItem->columnCount(); } } QVariant CompilersModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { return m_rootItem->data(section); } return QVariant(); } Qt::ItemFlags CompilersModel::flags(const QModelIndex& index) const { if (!index.isValid()) { - return 0; + return nullptr; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } QModelIndex CompilersModel::index(int row, int column, const QModelIndex& parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } TreeItem *parentItem; if (!parent.isValid()) { parentItem = m_rootItem; } else { parentItem = static_cast(parent.internalPointer()); } TreeItem* childItem = parentItem->child(row); if (childItem) { return createIndex(row, column, childItem); } else { return QModelIndex(); } } QModelIndex CompilersModel::parent(const QModelIndex& index) const { if (!index.isValid()) { return QModelIndex(); } TreeItem *childItem = static_cast(index.internalPointer()); TreeItem *parentItem = childItem->parent(); if (parentItem == m_rootItem) { return QModelIndex(); } return createIndex(parentItem->row(), 0, parentItem); } QVector< CompilerPointer > CompilersModel::compilers() const { QVector compilers; for (int idx = 0; idx < 2; idx++) { for (int i = 0; i< m_rootItem->child(idx)->childCount(); i++) { auto compiler = static_cast(m_rootItem->child(idx)->child(i))->compiler(); if (!compiler->name().isEmpty() && !compiler->path().isEmpty()) { compilers.append(compiler); } } } return compilers; } void CompilersModel::setCompilers(const QVector< CompilerPointer >& compilers) { beginResetModel(); autoDetectedRootItem(m_rootItem)->removeChilds(); manualRootItem(m_rootItem)->removeChilds(); for (auto compiler: compilers) { if (compiler->factoryName().isEmpty()) { continue; } TreeItem* parent = autoDetectedRootItem(m_rootItem); if (compiler->editable()) { parent = manualRootItem(m_rootItem); } parent->appendChild(new CompilerItem(compiler, parent)); } endResetModel(); } QModelIndex CompilersModel::addCompiler(const CompilerPointer& compiler) { beginInsertRows(index(1, 0), manualRootItem(m_rootItem)->childCount(), manualRootItem(m_rootItem)->childCount()); Q_ASSERT(!compiler->factoryName().isEmpty()); manualRootItem(m_rootItem)->appendChild(new CompilerItem(compiler, manualRootItem(m_rootItem))); endInsertRows(); emit compilerChanged(); return index(manualRootItem(m_rootItem)->childCount()-1, 0, index(1, 0)); } bool CompilersModel::removeRows(int row, int count, const QModelIndex& parent) { if (row >= 0 && count > 0 && parent.isValid() && static_cast(parent.internalPointer()) == manualRootItem(m_rootItem)) { beginRemoveRows(parent, row, row + count - 1); for (int i = 0; i < count; ++i) { manualRootItem(m_rootItem)->removeChild(row); } endRemoveRows(); emit compilerChanged(); return true; } return false; } void CompilersModel::updateCompiler(const QItemSelection& compiler) { for (const auto& idx: compiler.indexes()) { emit dataChanged(idx, idx); } emit compilerChanged(); } diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilersmodel.h b/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilersmodel.h index bb681dba5c..a36375bf4d 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilersmodel.h +++ b/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilersmodel.h @@ -1,69 +1,69 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef COMPILERMODEL_H #define COMPILERMODEL_H #include #include #include #include "../compilerprovider/icompiler.h" class TreeItem; class CompilersModel : public QAbstractItemModel { Q_OBJECT public: enum SpecialRole { CompilerDataRole = Qt::UserRole + 1 }; - CompilersModel( QObject* parent = 0 ); + CompilersModel( QObject* parent = nullptr ); void setCompilers( const QVector& compilers ); QVector compilers() const; QModelIndex addCompiler(const CompilerPointer& compiler); void updateCompiler(const QItemSelection& compiler); signals: /// emitted whenever new compiler added or existing one modified/deleted. void compilerChanged(); public: QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const override; int rowCount( const QModelIndex& parent = QModelIndex() ) const override; Qt::ItemFlags flags( const QModelIndex& index ) const override; int columnCount( const QModelIndex& parent = QModelIndex() ) const override; QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& child) const override; bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; private: TreeItem* m_rootItem; }; #endif // COMPILERMODEL_H diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilerswidget.h b/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilerswidget.h index b55fabafb6..7cddfab899 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilerswidget.h +++ b/languages/plugins/custom-definesandincludes/compilerprovider/widget/compilerswidget.h @@ -1,85 +1,85 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef COMPILERWIDGET_H #define COMPILERWIDGET_H #include #include #include #include #include "../compilerprovider/icompiler.h" namespace Ui { class CompilersWidget; } class CompilersModel; class QMenu; class QSignalMapper; class CompilersWidget : public KDevelop::ConfigPage { Q_OBJECT public: - explicit CompilersWidget(QWidget* parent = 0); + explicit CompilersWidget(QWidget* parent = nullptr); ~CompilersWidget() override; void setCompilers(const QVector& compilers); QVector compilers() const; void clear(); QString name() const override; QString fullName() const override; QIcon icon() const override; KDevelop::ConfigPage::ConfigPageType configPageType() const override; void apply() override; void reset() override; void defaults() override; private slots: void deleteCompiler(); void addCompiler(const QString& factoryName); void compilerSelected(const QModelIndex& index); void compilerEdited(); void selectCompilerPathDialog(); signals: void compilerChanged(); private: void enableItems(bool enable); QScopedPointer m_ui; CompilersModel* m_compilersModel; QMenu *m_addMenu; QSignalMapper *m_mapper; }; #endif diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/definesmodel.cpp b/languages/plugins/custom-definesandincludes/kcm_widget/definesmodel.cpp index f92dd707c8..315d13cec6 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/definesmodel.cpp +++ b/languages/plugins/custom-definesandincludes/kcm_widget/definesmodel.cpp @@ -1,171 +1,171 @@ /************************************************************************ * * * 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 "definesmodel.h" #include using namespace KDevelop; DefinesModel::DefinesModel( QObject* parent ) : QAbstractTableModel( parent ) { } QVariant DefinesModel::data( const QModelIndex& index, int role ) const { if( !index.isValid() || ( role != Qt::DisplayRole && role != Qt::EditRole ) ) { return QVariant(); } if( index.row() < 0 || index.row() >= rowCount() || index.column() < 0 || index.column() >= columnCount() ) { return QVariant(); } // Only show the hint for display, once the user goes into edit mode leave an empty line // makes the setData check easier and follows common behaviour of this in lineedits etc. if( index.row() == m_defines.count() && index.column() == 0 && role == Qt::DisplayRole ) { return i18n( "Double-click here to insert a new define to be used for the path" ); } else if( index.row() < m_defines.count() ) { switch( index.column() ) { case 0: return m_defines.at( index.row() ).first; case 1: return m_defines.at( index.row() ).second; default: Q_ASSERT_X( 0, "DefinesModel::data", "Invalid column requested" ); break; } } return QVariant(); } int DefinesModel::rowCount( const QModelIndex& parent ) const { if( parent.isValid() ) { return 0; } return m_defines.count() + 1; } int DefinesModel::columnCount(const QModelIndex& parent) const { if( parent.isValid() ) { return 0; } return 2; } QVariant DefinesModel::headerData(int section, Qt::Orientation orientation, int role) const { if( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { switch( section ) { case 0: return i18n("Define"); case 1: return i18n("Value"); default: Q_ASSERT_X( 0, "DefinesModel::headerData", "Invalid column requested" ); break; } } return QVariant(); } bool DefinesModel::setData( const QModelIndex& index, const QVariant& value, int role ) { if( !index.isValid() || role != Qt::EditRole ) { return false; } if( index.row() < 0 || index.row() >= rowCount() || index.column() < 0 || index.column() >= columnCount() ) { return false; } if( index.row() == m_defines.count() ) { if( index.column() == 0 && !value.toString().isEmpty() ) { beginInsertRows( QModelIndex(), m_defines.count(), m_defines.count() ); m_defines << qMakePair( value.toString(), "" ); endInsertRows(); } } else { switch( index.column() ) { case 0: m_defines[ index.row() ].first = value.toString(); break; case 1: m_defines[ index.row() ].second = value.toString(); break; default: Q_ASSERT_X( 0, "DefinesModel::setData", "Invalid column requested" ); return false; } emit dataChanged( index, index ); return true; } return false; } Qt::ItemFlags DefinesModel::flags( const QModelIndex& index ) const { if( !index.isValid() ) { - return 0; + return nullptr; } if( index.row() == m_defines.count() && index.column() == 1 ) { - return 0; + return nullptr; } return Qt::ItemFlags( Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled ); } Defines DefinesModel::defines() const { Defines ret; ret.reserve(m_defines.size()); for (const auto& pair : m_defines) { ret[pair.first] = pair.second; } return ret; } void DefinesModel::setDefines(const Defines& includes ) { beginResetModel(); m_defines.clear(); m_defines.reserve(includes.size()); for ( auto it = includes.begin(); it != includes.end(); ++it ) { m_defines << qMakePair( it.key(), it.value() ); } endResetModel(); } bool DefinesModel::removeRows( int row, int count, const QModelIndex& parent ) { if( row >= 0 && count > 0 && row < m_defines.count() ) { beginRemoveRows( parent, row, row + count - 1 ); for( int i = 0; i < count; ++i ) { m_defines.removeAt( row ); } endRemoveRows(); return true; } return false; } diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/definesmodel.h b/languages/plugins/custom-definesandincludes/kcm_widget/definesmodel.h index 8375c82d1d..286ab839e3 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/definesmodel.h +++ b/languages/plugins/custom-definesandincludes/kcm_widget/definesmodel.h @@ -1,45 +1,45 @@ /************************************************************************ * * * 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 . * ************************************************************************/ #ifndef DEFINESMODEL_H #define DEFINESMODEL_H #include #include #include "idefinesandincludesmanager.h" class DefinesModel : public QAbstractTableModel { Q_OBJECT public: - DefinesModel( QObject* parent = 0 ); + DefinesModel( QObject* parent = nullptr ); void setDefines( const KDevelop::Defines& defines ); KDevelop::Defines defines() const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool removeRows( int row, int count, const QModelIndex& parent = QModelIndex() ) override; private: QList > m_defines; }; #endif // DEFINESMODEL_H diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/defineswidget.h b/languages/plugins/custom-definesandincludes/kcm_widget/defineswidget.h index 4f4254e762..39e0cb577d 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/defineswidget.h +++ b/languages/plugins/custom-definesandincludes/kcm_widget/defineswidget.h @@ -1,60 +1,60 @@ /************************************************************************ * * * 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 . * ************************************************************************/ #ifndef KDEVELOP_PROJECTMANAGERS_CUSTOM_BUILDSYSTEM_DEFINESWIDGET_H #define KDEVELOP_PROJECTMANAGERS_CUSTOM_BUILDSYSTEM_DEFINESWIDGET_H #include #include #include "idefinesandincludesmanager.h" namespace Ui { class DefinesWidget; } namespace KDevelop { class IProject; } class DefinesModel; class QItemSelection; class DefinesWidget : public QWidget { Q_OBJECT public: - DefinesWidget( QWidget* parent = 0 ); + DefinesWidget( QWidget* parent = nullptr ); void setDefines( const KDevelop::Defines& defines ); void clear(); signals: void definesChanged( const KDevelop::Defines& defines ); private slots: // Forward defines model changes void definesChanged(); // Handle Del key in defines list void deleteDefine(); private: Ui::DefinesWidget* ui; DefinesModel* definesModel; }; #endif diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/includesmodel.cpp b/languages/plugins/custom-definesandincludes/kcm_widget/includesmodel.cpp index 71846f1ba6..54027e5583 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/includesmodel.cpp +++ b/languages/plugins/custom-definesandincludes/kcm_widget/includesmodel.cpp @@ -1,122 +1,122 @@ /************************************************************************ * * * 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 "includesmodel.h" #include IncludesModel::IncludesModel( QObject* parent ) : QAbstractListModel( parent ) { } QVariant IncludesModel::data( const QModelIndex& index, int role ) const { if( !index.isValid() || ( role != Qt::DisplayRole && role != Qt::EditRole ) ) { return QVariant(); } if( index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) { return QVariant(); } return m_includes.at( index.row() ); } int IncludesModel::rowCount( const QModelIndex& parent ) const { return parent.isValid() ? 0 : m_includes.count(); } bool IncludesModel::setData( const QModelIndex& index, const QVariant& value, int role ) { if( !index.isValid() || role != Qt::EditRole ) { return false; } if( index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) { return false; } m_includes[index.row()] = value.toString().trimmed(); emit dataChanged( index, index ); return true; } Qt::ItemFlags IncludesModel::flags( const QModelIndex& index ) const { if( !index.isValid() ) { - return 0; + return nullptr; } return Qt::ItemFlags( Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled ); } QStringList IncludesModel::includes() const { return m_includes; } void IncludesModel::setIncludes(const QStringList& includes ) { beginResetModel(); m_includes.clear(); foreach( const QString includePath, includes ) { addIncludeInternal( includePath.trimmed() ); } endResetModel(); } bool IncludesModel::removeRows( int row, int count, const QModelIndex& parent ) { if( row >= 0 && count > 0 && row < m_includes.count() ) { beginRemoveRows( parent, row, row + count - 1 ); for( int i = 0; i < count; ++i ) { m_includes.removeAt( row ); } endRemoveRows(); return true; } return false; } void IncludesModel::addInclude( const QString& includePath ) { if( !includePath.isEmpty() ) { beginInsertRows( QModelIndex(), rowCount(), rowCount() ); addIncludeInternal( includePath ); endInsertRows(); } } void IncludesModel::addIncludeInternal( const QString& includePath ) { if ( includePath.isEmpty() ) { return; } // Do not allow duplicates foreach( const QString &include, m_includes ) { if( include == includePath ) { return; } } m_includes << includePath; } diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/includesmodel.h b/languages/plugins/custom-definesandincludes/kcm_widget/includesmodel.h index 2ac5c3982d..a9658d1731 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/includesmodel.h +++ b/languages/plugins/custom-definesandincludes/kcm_widget/includesmodel.h @@ -1,43 +1,43 @@ /************************************************************************ * * * 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 . * ************************************************************************/ #ifndef INCLUDESMODEL_H #define INCLUDESMODEL_H #include #include class IncludesModel : public QAbstractListModel { Q_OBJECT public: - IncludesModel( QObject* parent = 0 ); + IncludesModel( QObject* parent = nullptr ); void setIncludes( const QStringList& ); QStringList includes() const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; bool removeRows( int row, int count, const QModelIndex& parent = QModelIndex() ) override; void addInclude( const QString& ); private: QStringList m_includes; void addIncludeInternal( const QString& includePath ); }; #endif // INCLUDESMODEL_H diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/includeswidget.h b/languages/plugins/custom-definesandincludes/kcm_widget/includeswidget.h index 9ad6d6d62b..e49e3df7ee 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/includeswidget.h +++ b/languages/plugins/custom-definesandincludes/kcm_widget/includeswidget.h @@ -1,72 +1,72 @@ /************************************************************************ * * * 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 . * ************************************************************************/ #ifndef KDEVELOP_PROJECTMANAGERS_CUSTOM_BUILDSYSTEM_INCLUDESWIDGET_H #define KDEVELOP_PROJECTMANAGERS_CUSTOM_BUILDSYSTEM_INCLUDESWIDGET_H #include #include class KUrlRequester; namespace Ui { class IncludesWidget; } namespace KDevelop { class IProject; } class QUrl; class ProjectPathsModel; class IncludesModel; class QItemSelection; class IncludesWidget : public QWidget { Q_OBJECT public: - IncludesWidget( QWidget* parent = 0 ); + IncludesWidget( QWidget* parent = nullptr ); void setProject(KDevelop::IProject* w_project); void setIncludes( const QStringList& ); void clear(); signals: void includesChanged( const QStringList& ); private slots: // Handling of include-path url-requester, add and remove buttons void includePathSelected( const QModelIndex& selected ); void includePathEdited(); void includePathUrlSelected(const QUrl&); void addIncludePath(); // Handles action and also Del-key in list void deleteIncludePath(); void checkIfIncludePathExist(); // Forward includes model changes void includesChanged(); private: Ui::IncludesWidget* ui; IncludesModel* includesModel; QString makeIncludeDirAbsolute( const QUrl &url ) const; // Enables/Disables widgets based on UI state/selection void updateEnablements(); void updatePathsModel( const QVariant& newData, int role ); }; #endif diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp index 99871ee29c..3171246473 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp +++ b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp @@ -1,239 +1,239 @@ /************************************************************************ * * * 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 "projectpathsmodel.h" #include #include #include #include using namespace KDevelop; ProjectPathsModel::ProjectPathsModel( QObject* parent ) - : QAbstractListModel( parent ), project( 0 ) + : QAbstractListModel( parent ), project( nullptr ) { } void ProjectPathsModel::setProject(IProject* w_project) { project = w_project; } QVariant ProjectPathsModel::data( const QModelIndex& index, int role ) const { if( !index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) { return QVariant(); } const ConfigEntry& pathConfig = projectPaths.at( index.row() ); switch( role ) { case IncludesDataRole: return pathConfig.includes; break; case DefinesDataRole: return QVariant::fromValue(pathConfig.defines); break; case Qt::EditRole: return sanitizePath( pathConfig.path, true, false ); break; case Qt::DisplayRole: { const QString& path = pathConfig.path; return (path == ".") ? "(project root)" : path; break; } case FullUrlDataRole: return QVariant::fromValue(QUrl::fromUserInput( sanitizePath( pathConfig.path, true, false ) )); break; case CompilerDataRole: return QVariant::fromValue(pathConfig.compiler); break; case ParserArgumentsRole: return QVariant::fromValue(pathConfig.parserArguments); break; default: break; } return QVariant(); } int ProjectPathsModel::rowCount( const QModelIndex& parent ) const { if( parent.isValid() ) { return 0; } return projectPaths.count(); } bool ProjectPathsModel::setData( const QModelIndex& index, const QVariant& value, int role ) { if( !index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) { return false; } // Do not allow to change path of the first entry; instead, add a new one in that case if( index.row() == 0 && ( role == Qt::EditRole || role == Qt::DisplayRole || role == FullUrlDataRole ) ) { QString addedPath = sanitizePath( value.toString(), false ); // Do not allow duplicates foreach( const ConfigEntry& existingConfig, projectPaths ) { if( addedPath == existingConfig.path ) { return false; } } projectPaths.insert( 1, sanitizePath( value.toString(), false ) ); emit dataChanged( this->index( 1, 0 ), this->index( projectPaths.count() - 1, 0 ) ); return true; } ConfigEntry& pathConfig = projectPaths[ index.row() ]; switch( role ) { case IncludesDataRole: pathConfig.includes = value.toStringList(); break; case DefinesDataRole: pathConfig.defines = value.value(); break; case Qt::EditRole: pathConfig.path = sanitizePath( value.toString(), false ); break; case Qt::DisplayRole: pathConfig.path = sanitizePath( value.toString(), true ); break; case FullUrlDataRole: pathConfig.path = sanitizeUrl( value.value() ); break; case CompilerDataRole: pathConfig.compiler = value.value(); break; case ParserArgumentsRole: pathConfig.parserArguments = value.value(); break; default: return false; break; } emit dataChanged( index, index ); return true; } Qt::ItemFlags ProjectPathsModel::flags( const QModelIndex& index ) const { if( !index.isValid() ) { - return 0; + return nullptr; } if( index.row() == 0 ) { return Qt::ItemFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled ); } return Qt::ItemFlags( Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled ); } QList< ConfigEntry > ProjectPathsModel::paths() const { return projectPaths; } void ProjectPathsModel::setPaths(const QList< ConfigEntry >& paths ) { beginResetModel(); projectPaths.clear(); foreach( const ConfigEntry& existingPathConfig, paths ) { // Sanitize the path of loaded config ConfigEntry config = existingPathConfig; bool rootPath = config.path == "." ? true : false; config.path = sanitizePath(rootPath ? QString() : config.path ); addPathInternal(config, rootPath); } addPathInternal( sanitizePath( QString() ), true ); // add an empty "root" config entry if one does not exist endResetModel(); } bool ProjectPathsModel::removeRows( int row, int count, const QModelIndex& parent ) { if( row >= 0 && count > 0 && row < rowCount() ) { beginRemoveRows( parent, row, row + count - 1 ); for( int i = 0; i < count; ++i ) { if( projectPaths.at(row).path == "." ) { continue; // we won't remove the root item } projectPaths.removeAt(row); } endRemoveRows(); return true; } return false; } void ProjectPathsModel::addPath( const QUrl &url ) { if( !project->path().isParentOf(KDevelop::Path(url)) ) { return; } beginInsertRows( QModelIndex(), rowCount(), rowCount() ); addPathInternal( sanitizeUrl(url), false ); endInsertRows(); } void ProjectPathsModel::addPathInternal( const ConfigEntry& config, bool prepend ) { Q_ASSERT(!config.parserArguments.cppArguments.isEmpty()); Q_ASSERT(!config.parserArguments.cArguments.isEmpty()); // Do not allow duplicates foreach( const ConfigEntry& existingConfig, projectPaths ) { if( config.path == existingConfig.path ) { return; } } if( prepend ) { projectPaths.prepend( config ); } else { projectPaths.append( config ); } } QString ProjectPathsModel::sanitizeUrl( QUrl url, bool needRelative ) const { Q_ASSERT( project ); if (needRelative) { const auto relativePath = project->path().relativePath(KDevelop::Path(url)); return relativePath.isEmpty() ? QStringLiteral(".") : relativePath; } return url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments).toString(QUrl::PreferLocalFile); } QString ProjectPathsModel::sanitizePath( const QString& path, bool expectRelative, bool needRelative ) const { Q_ASSERT( project ); Q_ASSERT( expectRelative || project->inProject(IndexedString(path)) ); QUrl url; if( expectRelative ) { url = KDevelop::Path(project->path(), path).toUrl(); } else { url = QUrl::fromUserInput(path); } return sanitizeUrl( url, needRelative ); } diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.h b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.h index 689c9d2bc7..ff8d1d4505 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.h +++ b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.h @@ -1,64 +1,64 @@ /************************************************************************ * * * 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 . * ************************************************************************/ #ifndef PROJECTPATHSMODEL_H #define PROJECTPATHSMODEL_H #include #include #include #include "../compilerprovider/settingsmanager.h" namespace KDevelop { class IProject; } class ProjectPathsModel : public QAbstractListModel { Q_OBJECT public: enum SpecialRoles { IncludesDataRole = Qt::UserRole + 1, DefinesDataRole = Qt::UserRole + 2, FullUrlDataRole = Qt::UserRole + 3, CompilerDataRole = Qt::UserRole + 4, ParserArgumentsRole = Qt::UserRole + 5 }; - ProjectPathsModel( QObject* parent = 0 ); + ProjectPathsModel( QObject* parent = nullptr ); void setProject( KDevelop::IProject* w_project ); void setPaths( const QList< ConfigEntry >& paths ); void addPath( const QUrl &url ); QList paths() const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; bool removeRows( int row, int count, const QModelIndex& parent = QModelIndex() ) override; private: QList projectPaths; KDevelop::IProject* project; void addPathInternal( const ConfigEntry& config, bool prepend ); QString sanitizePath( const QString& path, bool expectRelative = true, bool needRelative = true ) const; QString sanitizeUrl( QUrl url, bool needRelative = true ) const; }; #endif // PROJECTPATHSMODEL_H diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.h b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.h index 0fd73a5932..7dc69897bd 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.h +++ b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.h @@ -1,87 +1,87 @@ /************************************************************************ * * * 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 . * ************************************************************************/ #ifndef KDEVELOP_PROJECTMANAGERS_CUSTOM_BUILDSYSTEM_PROJECTPATHSWIDGET_H #define KDEVELOP_PROJECTMANAGERS_CUSTOM_BUILDSYSTEM_PROJECTPATHSWIDGET_H #include #include #include "../compilerprovider/icompiler.h" #include "../compilerprovider/settingsmanager.h" class KUrlRequester; namespace Ui { class ProjectPathsWidget; } namespace KDevelop { class IProject; } class ProjectPathsModel; class QItemSelection; class ProjectPathsWidget : public QWidget { Q_OBJECT public: - ProjectPathsWidget( QWidget* parent = 0 ); + ProjectPathsWidget( QWidget* parent = nullptr ); void setProject(KDevelop::IProject* w_project); void setPaths( const QList& ); QList paths() const; void clear(); signals: void changed(); private: void setCurrentCompiler(const QString& name); CompilerPointer currentCompiler() const; private slots: // Handling of project-path combobox, add and remove buttons void projectPathSelected( int index ); void addProjectPath(); void deleteProjectPath(); void batchEdit(); void tabChanged(int); void changeCompilerForPath(); // Forward includes model changes into the pathsModel void includesChanged( const QStringList& includes ); // Forward defines model changes into the pathsModel void definesChanged( const KDevelop::Defines& defines ); void parserArgumentsChanged(); private: Ui::ProjectPathsWidget* ui; ProjectPathsModel* pathsModel; // Enables/Disables widgets based on UI state/selection void updateEnablements(); void updatePathsModel( const QVariant& newData, int role ); }; #endif diff --git a/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.h b/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.h index 1e689524e7..9b21a7d04e 100644 --- a/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.h +++ b/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.h @@ -1,54 +1,54 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef NOPROJECTCUSTOMINCLUDEPATHS_H #define NOPROJECTCUSTOMINCLUDEPATHS_H #include namespace Ui { class CustomIncludePaths; } class NoProjectCustomIncludePaths : public QDialog { Q_OBJECT public: - NoProjectCustomIncludePaths( QWidget* parent = 0 ); + NoProjectCustomIncludePaths( QWidget* parent = nullptr ); void setStorageDirectory( const QString& path ); QString storageDirectory() const; void appendCustomIncludePath( const QString& path ); QStringList customIncludePaths() const; void setCustomIncludePaths( const QStringList& paths ); private: Ui::CustomIncludePaths* m_ui; private Q_SLOTS: void openAddIncludeDirectoryDialog(); }; #endif diff --git a/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp b/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp index bc2942102c..fffaba469d 100644 --- a/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp +++ b/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp @@ -1,155 +1,155 @@ /************************************************************************ * * * Copyright 2010 Andreas Pakulat * * Copyright 2014 Sergey Kalinichev * * * * 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 "test_definesandincludes.h" #include "projectsgenerator.h" #include #include #include #include #include #include #include #include #include "idefinesandincludesmanager.h" using namespace KDevelop; static IProject* s_currentProject = nullptr; void TestDefinesAndIncludes::cleanupTestCase() { TestCore::shutdown(); } void TestDefinesAndIncludes::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void TestDefinesAndIncludes::cleanup() { ICore::self()->projectController()->closeProject( s_currentProject ); } void TestDefinesAndIncludes::loadSimpleProject() { s_currentProject = ProjectsGenerator::GenerateSimpleProject(); QVERIFY( s_currentProject ); auto manager = IDefinesAndIncludesManager::manager(); QVERIFY( manager ); const auto actualIncludes = manager->includes( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ); const auto actualDefines = manager->defines( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ); qDebug() << actualDefines << actualIncludes; QCOMPARE( actualIncludes, Path::List() << Path( "/usr/include/mydir") ); Defines defines; defines.insert( "_DEBUG", "" ); defines.insert( "VARIABLE", "VALUE" ); QCOMPARE( actualDefines, defines ); QVERIFY(!manager->parserArguments(s_currentProject->projectItem()).isEmpty()); QVERIFY(!manager->parserArguments(QStringLiteral("/some/path/to/file.cpp")).isEmpty()); } void TestDefinesAndIncludes::loadMultiPathProject() { s_currentProject = ProjectsGenerator::GenerateMultiPathProject(); QVERIFY( s_currentProject ); auto manager = IDefinesAndIncludesManager::manager(); QVERIFY( manager ); Path::List includes = Path::List() << Path("/usr/include/otherdir"); QHash defines; defines.insert("SOURCE", "CONTENT"); defines.insert("_COPY", ""); QCOMPARE( manager->includes( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ), includes ); QCOMPARE( manager->defines( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ), defines ); QVERIFY(!manager->parserArguments(s_currentProject->projectItem()).isEmpty()); - ProjectBaseItem* mainfile = 0; + ProjectBaseItem* mainfile = nullptr; for (const auto& file: s_currentProject->fileSet() ) { for (auto i: s_currentProject->filesForPath(file)) { if( i->text() == "main.cpp" ) { mainfile = i; break; } } } QVERIFY(mainfile); includes.prepend(Path("/usr/local/include/mydir")); defines.insert("BUILD", "debug"); qDebug() << includes << "VS" << manager->includes( mainfile, IDefinesAndIncludesManager::UserDefined ); qDebug() << mainfile << mainfile->path(); QCOMPARE(manager->includes( mainfile, IDefinesAndIncludesManager::UserDefined ), includes); QCOMPARE(defines, manager->defines( mainfile, IDefinesAndIncludesManager::UserDefined )); QVERIFY(!manager->parserArguments(mainfile).isEmpty()); } void TestDefinesAndIncludes::testNoProjectIncludeDirectories() { s_currentProject = ProjectsGenerator::GenerateSimpleProjectWithOutOfProjectFiles(); QVERIFY(s_currentProject); auto manager = KDevelop::IDefinesAndIncludesManager::manager(); QVERIFY(manager); auto projectIncludes = manager->includes(s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined); Path includePath1(s_currentProject->path().path() + QDir::separator() + "include1.h"); Path includePath2(s_currentProject->path().path() + QDir::separator() + "include2.h"); QVERIFY(!projectIncludes.contains(includePath1)); QVERIFY(!projectIncludes.contains(includePath2)); auto noProjectIncludes = manager->includes(s_currentProject->path().path() + "/src/main.cpp"); QVERIFY(noProjectIncludes.contains(includePath1)); QVERIFY(noProjectIncludes.contains(includePath2)); QVERIFY(!manager->parserArguments(s_currentProject->projectItem()).isEmpty()); } void TestDefinesAndIncludes::testEmptyProject() { s_currentProject = ProjectsGenerator::GenerateEmptyProject(); QVERIFY(s_currentProject); auto manager = KDevelop::IDefinesAndIncludesManager::manager(); QVERIFY(manager); auto projectIncludes = manager->includes(s_currentProject->projectItem()); auto projectDefines = manager->defines(s_currentProject->projectItem()); auto parserArguments = manager->parserArguments(s_currentProject->projectItem()); QVERIFY(!projectIncludes.isEmpty()); QVERIFY(!projectDefines.isEmpty()); QVERIFY(!parserArguments.isEmpty()); } QTEST_MAIN(TestDefinesAndIncludes) diff --git a/projectbuilders/cmakebuilder/cmakebuilder.cpp b/projectbuilders/cmakebuilder/cmakebuilder.cpp index 691ab43ed7..f4113a9888 100644 --- a/projectbuilders/cmakebuilder/cmakebuilder.cpp +++ b/projectbuilders/cmakebuilder/cmakebuilder.cpp @@ -1,310 +1,310 @@ /* KDevelop CMake Support * * Copyright 2006-2007 Andreas Pakulat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakebuilder.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(CMAKEBUILDER, "kdevelop.projectbuilders.cmakebuilder") #include "cmakejob.h" #include "prunejob.h" #include "cmakebuilderpreferences.h" #include "cmakeutils.h" #include K_PLUGIN_FACTORY_WITH_JSON(CMakeBuilderFactory, "kdevcmakebuilder.json", registerPlugin(); ) class ErrorJob : public KJob { public: ErrorJob(QObject* parent, const QString& error) : KJob(parent) , m_error(error) {} void start() override { setError(!m_error.isEmpty()); setErrorText(m_error); emitResult(); } private: QString m_error; }; CMakeBuilder::CMakeBuilder(QObject *parent, const QVariantList &) : KDevelop::IPlugin("kdevcmakebuilder", parent) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectBuilder ) addBuilder("Makefile", QStringList("Unix Makefiles") << "NMake Makefiles", core()->pluginController()->pluginForExtension("org.kdevelop.IMakeBuilder")); addBuilder("build.ninja", QStringList("Ninja"), core()->pluginController()->pluginForExtension("org.kdevelop.IProjectBuilder", "KDevNinjaBuilder")); } CMakeBuilder::~CMakeBuilder() { } void CMakeBuilder::addBuilder(const QString& neededfile, const QStringList& generators, KDevelop::IPlugin* i) { if( i ) { IProjectBuilder* b = i->extension(); if( b ) { m_builders[neededfile] = b; foreach(const QString& gen, generators) { m_buildersForGenerator[gen] = b; } // can't use new signal/slot syntax here, IProjectBuilder is not a QObject connect(i, SIGNAL(built(KDevelop::ProjectBaseItem*)), this, SIGNAL(built(KDevelop::ProjectBaseItem*))); connect(i, SIGNAL(failed(KDevelop::ProjectBaseItem*)), this, SIGNAL(failed(KDevelop::ProjectBaseItem*))); connect(i, SIGNAL(cleaned(KDevelop::ProjectBaseItem*)), this, SIGNAL(cleaned(KDevelop::ProjectBaseItem*))); connect(i, SIGNAL(installed(KDevelop::ProjectBaseItem*)), this, SIGNAL(installed(KDevelop::ProjectBaseItem*))); qCDebug(CMAKEBUILDER) << "Added builder " << i->metaObject()->className() << "for" << neededfile; } else qWarning() << "Couldn't add " << i->metaObject()->className() << i->extensions(); } } KJob* CMakeBuilder::build(KDevelop::ProjectBaseItem *dom) { KDevelop::IProject* p = dom->project(); IProjectBuilder* builder = builderForProject(p); if( builder ) { bool valid; KJob* configure = checkConfigureJob(dom->project(), valid); - KJob* build = 0; + KJob* build = nullptr; if(dom->file()) { IMakeBuilder* makeBuilder = dynamic_cast(builder); if (!makeBuilder) { return new ErrorJob(this, i18n("Couldn't find the make builder. Check your installation")); } KDevelop::ProjectFileItem* file = dom->file(); int lastDot = file->text().lastIndexOf('.'); QString target = file->text().mid(0, lastDot)+".o"; build = makeBuilder->executeMakeTarget(dom->parent(), target); qCDebug(CMAKEBUILDER) << "create build job for target" << build << dom << target; } qCDebug(CMAKEBUILDER) << "Building with" << builder; if (!build) { build = builder->build(dom); } if( configure ) { qCDebug(CMAKEBUILDER) << "creating composite job"; KDevelop::BuilderJob* builderJob = new KDevelop::BuilderJob; builderJob->addCustomJob( KDevelop::BuilderJob::Configure, configure, dom ); builderJob->addCustomJob( KDevelop::BuilderJob::Build, build, dom ); builderJob->updateJobName(); build = builderJob; } return build; } return new ErrorJob(this, i18n("Couldn't find a builder for %1", p->name())); } KJob* CMakeBuilder::clean(KDevelop::ProjectBaseItem *dom) { IProjectBuilder* builder = builderForProject(dom->project()); if( builder ) { bool valid; KJob* configure = checkConfigureJob(dom->project(), valid); KDevelop::ProjectBaseItem* item = dom; if(dom->file()) //It doesn't work to compile a file item=(KDevelop::ProjectBaseItem*) dom->parent(); qCDebug(CMAKEBUILDER) << "Cleaning with" << builder; KJob* clean = builder->clean(item); if( configure ) { KDevelop::BuilderJob* builderJob = new KDevelop::BuilderJob; builderJob->addCustomJob( KDevelop::BuilderJob::Configure, configure, item ); builderJob->addCustomJob( KDevelop::BuilderJob::Clean, clean, item ); builderJob->updateJobName(); clean = builderJob; } return clean; } return new ErrorJob(this, i18n("Couldn't find a builder for %1", dom->project()->name())); } KJob* CMakeBuilder::install(KDevelop::ProjectBaseItem *dom, const QUrl &installPrefix) { IProjectBuilder* builder = builderForProject(dom->project()); if( builder ) { bool valid; KJob* configure = checkConfigureJob(dom->project(), valid); KDevelop::ProjectBaseItem* item = dom; if(dom->file()) item=(KDevelop::ProjectBaseItem*) dom->parent(); qCDebug(CMAKEBUILDER) << "Installing with" << builder; KJob* install = builder->install(item, installPrefix); if( configure ) { KDevelop::BuilderJob* builderJob = new KDevelop::BuilderJob; builderJob->addCustomJob( KDevelop::BuilderJob::Configure, configure, item ); builderJob->addCustomJob( KDevelop::BuilderJob::Install, install, item ); builderJob->updateJobName(); install = builderJob; } return install; } return new ErrorJob(this, i18n("Couldn't find a builder for %1", dom->project()->name())); } KJob* CMakeBuilder::checkConfigureJob(KDevelop::IProject* project, bool& valid) { valid = false; KJob* configure = nullptr; if( CMake::checkForNeedingConfigure(project) ) { configure = this->configure(project); } else if( CMake::currentBuildDir(project).isEmpty() ) { return new ErrorJob(this, i18n("No Build Directory configured, cannot install")); } valid = true; return configure; } KJob* CMakeBuilder::configure( KDevelop::IProject* project ) { if( CMake::currentBuildDir( project ).isEmpty() ) { return new ErrorJob(this, i18n("No Build Directory configured, cannot configure")); } CMakeJob* job = new CMakeJob(this); job->setProject(project); connect(job, &KJob::result, this, [this, project] { emit configured(project); }); return job; } KJob* CMakeBuilder::prune( KDevelop::IProject* project ) { return new PruneJob(project); } KDevelop::IProjectBuilder* CMakeBuilder::builderForProject(KDevelop::IProject* p) const { QString builddir = CMake::currentBuildDir( p ).toLocalFile(); QMap::const_iterator it = m_builders.constBegin(), itEnd = m_builders.constEnd(); for(; it!=itEnd; ++it) { if(QFile::exists(builddir+'/'+it.key())) return it.value(); } //It means that it still has to be generated, so use the builder for //the generator we use return m_buildersForGenerator[defaultGenerator()]; } QList< KDevelop::IProjectBuilder* > CMakeBuilder::additionalBuilderPlugins( KDevelop::IProject* project ) const { IProjectBuilder* b = builderForProject( project ); QList< KDevelop::IProjectBuilder* > ret; if(b) ret << b; return ret; } int CMakeBuilder::configPages() const { return 1; } QStringList CMakeBuilder::supportedGenerators() { QStringList generatorNames; bool hasNinja = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IProjectBuilder", "KDevNinjaBuilder"); if (hasNinja) generatorNames << "Ninja"; #ifdef Q_OS_WIN // Visual Studio solution is the standard generator under windows, but we dont want to use // the VS IDE, so we need nmake makefiles generatorNames << "NMake Makefiles"; #endif generatorNames << "Unix Makefiles"; return generatorNames; } QString CMakeBuilder::defaultGenerator() { const QStringList generatorNames = supportedGenerators(); QString defGen = generatorNames.value(CMakeBuilderSettings::self()->generator()); if (defGen.isEmpty()) { qWarning() << "Couldn't find builder with index " << CMakeBuilderSettings::self()->generator() << ", defaulting to 0"; CMakeBuilderSettings::self()->setGenerator(0); defGen = generatorNames.at(0); } return defGen; } KDevelop::ConfigPage* CMakeBuilder::configPage(int number, QWidget* parent) { if (number == 0) { return new CMakeBuilderPreferences(this, parent); } return nullptr; } #include "cmakebuilder.moc" diff --git a/projectbuilders/cmakebuilder/cmakebuilder.h b/projectbuilders/cmakebuilder/cmakebuilder.h index cb35fa9a3f..46fb6d26a2 100644 --- a/projectbuilders/cmakebuilder/cmakebuilder.h +++ b/projectbuilders/cmakebuilder/cmakebuilder.h @@ -1,87 +1,87 @@ /* KDevelop CMake Support * * Copyright 2006-2007 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEBUILDER_H #define CMAKEBUILDER_H #include #include #include #include #include #include #include class QStringList; class QSignalMapper; class KDialog; namespace KDevelop{ class ProjectBaseItem; class CommandExecutor; class OutputModel; } /** * @author Aleix Pol */ class CMakeBuilder : public KDevelop::IPlugin, public KDevelop::IProjectBuilder { Q_OBJECT Q_INTERFACES( KDevelop::IProjectBuilder ) public: - explicit CMakeBuilder(QObject *parent = 0, const QVariantList &args = QVariantList()); + explicit CMakeBuilder(QObject *parent = nullptr, const QVariantList &args = QVariantList()); ~CMakeBuilder() override; KJob* build(KDevelop::ProjectBaseItem *dom) override; KJob* install(KDevelop::ProjectBaseItem *dom, const QUrl &installPrefix) override; KJob* clean(KDevelop::ProjectBaseItem *dom) override; KJob* configure(KDevelop::IProject*) override; KJob* prune(KDevelop::IProject*) override; QList< KDevelop::IProjectBuilder* > additionalBuilderPlugins( KDevelop::IProject* project ) const override; // bool updateConfig( KDevelop::IProject* project ); static QStringList supportedGenerators(); static QString defaultGenerator(); int configPages() const override; KDevelop::ConfigPage* configPage(int number, QWidget* parent) override; Q_SIGNALS: void built(KDevelop::ProjectBaseItem*); void failed(KDevelop::ProjectBaseItem*); void installed(KDevelop::ProjectBaseItem*); void cleaned(KDevelop::ProjectBaseItem*); void configured(KDevelop::IProject*); void pruned(KDevelop::IProject*); private: KJob* checkConfigureJob(KDevelop::IProject* project, bool& valid); void addBuilder(const QString& neededfile, const QStringList& generator, KDevelop::IPlugin* i); KDevelop::IProjectBuilder* builderForProject(KDevelop::IProject* p) const; QMap m_builders; QMap m_buildersForGenerator; }; #endif // CMAKEBUILDER_H diff --git a/projectbuilders/cmakebuilder/cmakejob.cpp b/projectbuilders/cmakebuilder/cmakejob.cpp index 7e40362a70..fe075b5bfb 100644 --- a/projectbuilders/cmakebuilder/cmakejob.cpp +++ b/projectbuilders/cmakebuilder/cmakejob.cpp @@ -1,140 +1,140 @@ /* KDevelop CMake Support * * Copyright 2006-2007 Andreas Pakulat * Copyright 2008 Hamish Rodda * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakejob.h" #include #include #include #include #include #include #include #include #include #include "cmakeutils.h" #include "debug.h" using namespace KDevelop; CMakeJob::CMakeJob(QObject* parent) : OutputExecuteJob(parent) - , m_project(0) + , m_project(nullptr) { setCapabilities( Killable ); setFilteringStrategy( OutputModel::CompilerFilter ); setProperties( NeedWorkingDirectory | PortableMessages | DisplayStderr | IsBuilderHint ); setToolTitle( i18n("CMake") ); setStandardToolView( KDevelop::IOutputView::BuildView ); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); } void CMakeJob::start() { qCDebug(CMAKEBUILDER) << "Configuring cmake" << workingDirectory(); if( !m_project ) { setError(NoProjectError); setErrorText("Internal error: no project specified to configure."); emitResult(); return; } QDir::temp().mkpath(workingDirectory().toLocalFile()); CMake::updateConfig( m_project, CMake::currentBuildDirIndex(m_project) ); OutputExecuteJob::start(); } QUrl CMakeJob::workingDirectory() const { KDevelop::Path path = CMake::currentBuildDir( m_project ); qCDebug(CMAKEBUILDER) << "builddir: " << path; Q_ASSERT(path.isValid()); //We cannot get the project folder as a build directory! return path.toUrl(); } QStringList CMakeJob::commandLine() const { QStringList args; args << CMake::currentCMakeBinary( m_project ).toLocalFile(); args << "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"; QString installDir = CMake::currentInstallDir( m_project ).toLocalFile(); if( !installDir.isEmpty() ) { args << QStringLiteral("-DCMAKE_INSTALL_PREFIX=%1").arg(installDir); } QString buildType = CMake::currentBuildType( m_project ); if( !buildType.isEmpty() ) { args << QStringLiteral("-DCMAKE_BUILD_TYPE=%1").arg(buildType); } QVariantMap cacheArgs = property("extraCMakeCacheValues").toMap(); for( auto it = cacheArgs.constBegin(), itEnd = cacheArgs.constEnd(); it!=itEnd; ++it) { args << QStringLiteral("-D%1=%2").arg(it.key()).arg(it.value().toString()); } //if we are creating a new build directory, we'll want to specify the generator QDir builddir(CMake::currentBuildDir( m_project ).toLocalFile()); if(!builddir.exists() || builddir.count()==2) { CMakeBuilderSettings::self()->load(); args << QString("-G") << CMakeBuilder::defaultGenerator(); } QString cmakeargs = CMake::currentExtraArguments( m_project ); if( !cmakeargs.isEmpty() ) { KShell::Errors err; QStringList tmp = KShell::splitArgs( cmakeargs, KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err == KShell::NoError ) { args += tmp; } else { qWarning() << "Ignoring cmake Extra arguments"; if( err == KShell::BadQuoting ) { qWarning() << "CMake arguments badly quoted:" << cmakeargs; } else { qWarning() << "CMake arguments had meta character:" << cmakeargs; } } } args << CMake::projectRoot( m_project ).toLocalFile(); return args; } QString CMakeJob::environmentProfile() const { return CMake::currentEnvironment( m_project ); } void CMakeJob::setProject(KDevelop::IProject* project) { m_project = project; if (m_project) setJobName( i18n("CMake: %1", m_project->name()) ); } diff --git a/projectbuilders/cmakebuilder/cmakejob.h b/projectbuilders/cmakebuilder/cmakejob.h index dad3723057..5e8646651f 100644 --- a/projectbuilders/cmakebuilder/cmakejob.h +++ b/projectbuilders/cmakebuilder/cmakejob.h @@ -1,70 +1,70 @@ /* KDevelop CMake Support * * Copyright 2006-2007 Andreas Pakulat * Copyright 2008 Hamish Rodda * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEJOB_H #define CMAKEJOB_H #include #include #include #include namespace KDevelop { class IProject; class ProjectBaseItem; class CommandExecutor; } class CMakeJob: public KDevelop::OutputExecuteJob { Q_OBJECT public: enum ErrorTypes { NoProjectError = UserDefinedError, FailedError }; - CMakeJob(QObject* parent = 0); + CMakeJob(QObject* parent = nullptr); void setProject(KDevelop::IProject* project); void start() override; // This returns the build directory for registered item. QUrl workingDirectory() const override; // This returns the "cmake" command line. QStringList commandLine() const override; // This returns the configured global environment profile. QString environmentProfile() const override; private: QStringList cmakeArguments( KDevelop::IProject* project ); KDevelop::Path buildDir( KDevelop::IProject* project ); KDevelop::IProject* m_project; }; #endif // CMAKEJOB_H diff --git a/projectbuilders/cmakebuilder/prunejob.cpp b/projectbuilders/cmakebuilder/prunejob.cpp index ce6ba3454f..bd658f9ff0 100644 --- a/projectbuilders/cmakebuilder/prunejob.cpp +++ b/projectbuilders/cmakebuilder/prunejob.cpp @@ -1,88 +1,88 @@ /* KDevelop CMake Support * * Copyright 2013 Aleix Pol Gonzalez * * 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 "prunejob.h" #include #include #include #include #include #include using namespace KDevelop; PruneJob::PruneJob(KDevelop::IProject* project) : OutputJob(project, Verbose) , m_project(project) - , m_job(0) + , m_job(nullptr) { setCapabilities( Killable ); setToolTitle( i18n("CMake") ); setStandardToolView( KDevelop::IOutputView::BuildView ); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); } void PruneJob::start() { OutputModel* output = new OutputModel(this); setModel(output); startOutput(); Path builddir = CMake::currentBuildDir( m_project ); if( builddir.isEmpty() ) { output->appendLine(i18n("No Build Directory configured, cannot clear builddir")); emitResult(); return; } else if (!builddir.isLocalFile() || QDir(builddir.toLocalFile()).exists("CMakeLists.txt")) { output->appendLine(i18n("Wrong build directory, cannot clear the build directory")); emitResult(); return; } QDir d( builddir.toLocalFile() ); QList urls; foreach( const QString& entry, d.entryList( QDir::NoDotAndDotDot | QDir::AllEntries ) ) { urls << Path(builddir, entry).toUrl(); } output->appendLine(i18n("%1> rm -rf %2", m_project->path().pathOrUrl(), builddir.toLocalFile())); m_job = KIO::del( urls ); m_job->start(); connect(m_job, &KJob::finished, this, &PruneJob::jobFinished); } bool PruneJob::doKill() { return m_job->kill(); } void PruneJob::jobFinished(KJob* job) { OutputModel* output = qobject_cast(model()); if(job->error()==0) output->appendLine(i18n("** Prune successful **")); else output->appendLine(i18n("** Prune failed: %1 **", job->errorString())); emitResult(); - m_job = 0; + m_job = nullptr; } diff --git a/projectbuilders/makebuilder/makebuilder.h b/projectbuilders/makebuilder/makebuilder.h index 65133f09a4..c3c8f102cc 100644 --- a/projectbuilders/makebuilder/makebuilder.h +++ b/projectbuilders/makebuilder/makebuilder.h @@ -1,96 +1,96 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2007 Dukju Ahn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MAKEBUILDER_H #define MAKEBUILDER_H #include #include #include #include #include "imakebuilder.h" #include "makejob.h" #include namespace KDevelop { class ProjectBaseItem; } /** @author Roberto Raggi */ class MakeBuilder: public KDevelop::IPlugin, public IMakeBuilder { Q_OBJECT Q_INTERFACES( IMakeBuilder ) Q_INTERFACES( KDevelop::IProjectBuilder ) public: - explicit MakeBuilder(QObject *parent = 0, const QVariantList &args = QVariantList()); + explicit MakeBuilder(QObject *parent = nullptr, const QVariantList &args = QVariantList()); ~MakeBuilder() override; /** * If argument is ProjectItem, invoke "make" in IBuildSystemManager::buildDirectory(), with * specified target in project setting. * * If argument is ProjectTargetItem, invoke "make" with targetname QStandardItem::text(). In this case, * it tries its best to fetch ProjectItem, which is the argument of IBuildSystemManager::buildDirectory() * If it fails to fetch ProjectItem, the top build directory is defaulted to project directory. * Based on top build directory, the actual build_dir is computed and handed to outputview * * If argument is ProjectBuildFolderItem, invoke "make" with specified target in project setting. * To determine the build directory, first calculates rel_dir between * top_project_dir(ProjectItem::url()) and ProjectBuildFolderItem::url(). * Then invokes make in top_build_dir/rel_dir. * If this fails to fetch top_build_dir, just invoke "make" in ProjectBuildFolderItem::url(). * * @TODO: Work on any project item, for fileitems you may find a target. */ KJob* build(KDevelop::ProjectBaseItem *dom) override; KJob* clean(KDevelop::ProjectBaseItem *dom) override; KJob* install(KDevelop::ProjectBaseItem *dom, const QUrl &installPath) override; KJob* executeMakeTarget(KDevelop::ProjectBaseItem* item, const QString& targetname ) override; KJob* executeMakeTargets(KDevelop::ProjectBaseItem* item, const QStringList& targetnames, const MakeVariables& variables = MakeVariables() ) override; KJob* runMake( KDevelop::ProjectBaseItem*, MakeJob::CommandType, const QStringList& = QStringList(), const MakeVariables& variables = MakeVariables() ); int perProjectConfigPages() const override; KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; Q_SIGNALS: void built( KDevelop::ProjectBaseItem* ); void failed( KDevelop::ProjectBaseItem* ); void installed(KDevelop::ProjectBaseItem*); void cleaned(KDevelop::ProjectBaseItem*); void makeTargetBuilt( KDevelop::ProjectBaseItem* item, const QString& targetname ); private Q_SLOTS: void jobFinished(KJob* job); private: KDevelop::ObjectList m_activeMakeJobs; }; #endif // KDEVMAKEBUILDER_H diff --git a/projectbuilders/makebuilder/makebuilderpreferences.h b/projectbuilders/makebuilder/makebuilderpreferences.h index b97adb8ae4..72b3b1028f 100644 --- a/projectbuilders/makebuilder/makebuilderpreferences.h +++ b/projectbuilders/makebuilder/makebuilderpreferences.h @@ -1,53 +1,53 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef MAKEBUILDERPREFERENCES_H #define MAKEBUILDERPREFERENCES_H #include #include "makebuilderconfig.h" class QWidget; class QStringList; namespace Ui { class MakeConfig; } class MakeBuilderPreferences : public ProjectConfigPage { Q_OBJECT public: - explicit MakeBuilderPreferences(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent = 0); + explicit MakeBuilderPreferences(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent = nullptr); ~MakeBuilderPreferences() override; void reset() override; void apply() override; void defaults() override; QString name() const override; QString fullName() const override; QIcon icon() const override; static QString standardMakeCommand(); private: Ui::MakeConfig* m_prefsUi; }; #endif diff --git a/projectbuilders/ninjabuilder/kdevninjabuilderplugin.cpp b/projectbuilders/ninjabuilder/kdevninjabuilderplugin.cpp index d903f7b8ff..00d16674cd 100644 --- a/projectbuilders/ninjabuilder/kdevninjabuilderplugin.cpp +++ b/projectbuilders/ninjabuilder/kdevninjabuilderplugin.cpp @@ -1,203 +1,203 @@ /* This file is part of KDevelop Copyright 2012 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdevninjabuilderplugin.h" #include "ninjajob.h" #include "ninjabuilderpreferences.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(NINJABUILDER) Q_LOGGING_CATEGORY(NINJABUILDER, "kdevelop.projectbuilders.ninjabuilder") K_PLUGIN_FACTORY_WITH_JSON(NinjaBuilderFactory, "kdevninja.json", registerPlugin(); ) KDevNinjaBuilderPlugin::KDevNinjaBuilderPlugin(QObject* parent, const QVariantList& ) : KDevelop::IPlugin("kdevninja", parent) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectBuilder ) if(NinjaJob::ninjaBinary().isEmpty()) { setErrorDescription(i18n("Unable to find ninja executable. Is it installed on the system?")); } } static QStringList targetsInFolder(KDevelop::ProjectFolderItem* item) { QStringList ret; foreach(KDevelop::ProjectTargetItem* target, item->targetList()) { ret += target->text(); } return ret; } /** * Returns the first non-empty list of targets in folder @p item * or any of its ancestors if possible */ static QStringList closestTargetsForFolder(KDevelop::ProjectFolderItem* item) { KDevelop::ProjectFolderItem* current = item; while (current) { const QStringList targets = targetsInFolder(current); if (!targets.isEmpty()) { return targets; } - current = (current->parent() ? current->parent()->folder() : 0); + current = (current->parent() ? current->parent()->folder() : nullptr); } return QStringList(); } static QStringList argumentsForItem(KDevelop::ProjectBaseItem* item) { if(!item->parent() && QFile::exists(item->project()->buildSystemManager()->buildDirectory(item->project()->projectItem()).toLocalFile())) return QStringList(); switch(item->type()) { case KDevelop::ProjectBaseItem::File: return QStringList(item->path().toLocalFile()+'^'); case KDevelop::ProjectBaseItem::Target: case KDevelop::ProjectBaseItem::ExecutableTarget: case KDevelop::ProjectBaseItem::LibraryTarget: return QStringList(item->target()->text()); case KDevelop::ProjectBaseItem::Folder: case KDevelop::ProjectBaseItem::BuildFolder: return closestTargetsForFolder(item->folder()); } return QStringList(); } NinjaJob* KDevNinjaBuilderPlugin::runNinja(KDevelop::ProjectBaseItem* item, const QStringList& args, const QByteArray& signal) { ///Running the same builder twice may result in serious problems, ///so kill jobs already running on the same project foreach (NinjaJob* ninjaJob, m_activeNinjaJobs.data()) { if(item && ninjaJob->item() && ninjaJob->item()->project() == item->project() ) { qCDebug(NINJABUILDER) << "killing running ninja job, due to new started build on same project:" << ninjaJob; ninjaJob->kill(KJob::EmitResult); } } // Build arguments using data from KCM QStringList jobArguments; KSharedConfigPtr config = item->project()->projectConfiguration(); KConfigGroup group = config->group( "NinjaBuilder" ); if( !group.readEntry( "Abort on First Error", true ) ) { jobArguments << "-k"; } if( group.readEntry( "Override Number Of Jobs", false ) ) { int jobCount = group.readEntry( "Number Of Jobs", 1 ); if( jobCount > 0 ) { jobArguments << QString( "-j%1" ).arg( jobCount ); } } int errorCount = group.readEntry( "Number Of Errors", 1 ); if( errorCount > 1 ) { jobArguments << QString( "-k%1" ).arg( errorCount ); } if( group.readEntry( "Display Only", false ) ) { jobArguments << "-n"; } QString extraOptions = group.readEntry( "Additional Options", QString() ); if( !extraOptions.isEmpty() ) { foreach(const QString& option, KShell::splitArgs( extraOptions ) ) { jobArguments << option; } } jobArguments << args; NinjaJob* job = new NinjaJob(item, jobArguments, signal, this); m_activeNinjaJobs.append(job); return job; } KJob* KDevNinjaBuilderPlugin::build(KDevelop::ProjectBaseItem* item) { return runNinja(item, argumentsForItem(item), "built"); } KJob* KDevNinjaBuilderPlugin::clean(KDevelop::ProjectBaseItem* item) { return runNinja(item, QStringList("-t") << "clean", "cleaned"); } KJob* KDevNinjaBuilderPlugin::install(KDevelop::ProjectBaseItem* item) { NinjaJob* installJob = runNinja( item, QStringList( "install" ), "installed" ); installJob->setIsInstalling( true ); KSharedConfigPtr configPtr = item->project()->projectConfiguration(); KConfigGroup builderGroup( configPtr, "NinjaBuilder" ); bool installAsRoot = builderGroup.readEntry("Install As Root", false); if(installAsRoot) { KDevelop::BuilderJob* job = new KDevelop::BuilderJob; job->addCustomJob( KDevelop::BuilderJob::Build, build( item ), item ); job->addCustomJob( KDevelop::BuilderJob::Install, installJob, item ); job->updateJobName(); return job; } else { return installJob; } } int KDevNinjaBuilderPlugin::perProjectConfigPages() const { return 1; } KDevelop::ConfigPage* KDevNinjaBuilderPlugin::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new NinjaBuilderPreferences(this, options, parent); } return nullptr; } class ErrorJob : public KJob { public: ErrorJob(QObject* parent, const QString& error) : KJob(parent) , m_error(error) {} void start() override { setError(!m_error.isEmpty()); setErrorText(m_error); emitResult(); } private: QString m_error; }; KJob* KDevNinjaBuilderPlugin::install(KDevelop::ProjectBaseItem *dom, const QUrl &installPath) { return installPath.isEmpty() ? install(dom) : new ErrorJob(nullptr, i18n("Cannot specify prefix in %1, on ninja", installPath.toDisplayString())); } #include "kdevninjabuilderplugin.moc" diff --git a/projectbuilders/ninjabuilder/kdevninjabuilderplugin.h b/projectbuilders/ninjabuilder/kdevninjabuilderplugin.h index 9fe886d543..99e1006c06 100644 --- a/projectbuilders/ninjabuilder/kdevninjabuilderplugin.h +++ b/projectbuilders/ninjabuilder/kdevninjabuilderplugin.h @@ -1,57 +1,57 @@ /* This file is part of KDevelop Copyright 2012 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVNINJABUILDERPLUGIN_H #define KDEVNINJABUILDERPLUGIN_H #include #include #include #include #include class NinjaJob; class KDevNinjaBuilderPlugin : public KDevelop::IPlugin, KDevelop::IProjectBuilder { Q_OBJECT Q_INTERFACES( KDevelop::IProjectBuilder ) public: - KDevNinjaBuilderPlugin(QObject* parent = 0, const QVariantList& args = QVariantList()); + KDevNinjaBuilderPlugin(QObject* parent = nullptr, const QVariantList& args = QVariantList()); KJob* build(KDevelop::ProjectBaseItem* item) override; KJob* clean(KDevelop::ProjectBaseItem* item) override; KJob* install(KDevelop::ProjectBaseItem *dom, const QUrl &installPath) override; KJob* install(KDevelop::ProjectBaseItem* item); NinjaJob* runNinja(KDevelop::ProjectBaseItem* item, const QStringList& args, const QByteArray& signal); int perProjectConfigPages() const override; KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; Q_SIGNALS: void built( KDevelop::ProjectBaseItem* item); void failed( KDevelop::ProjectBaseItem* item); void installed(KDevelop::ProjectBaseItem* item); void cleaned(KDevelop::ProjectBaseItem* item); private: KDevelop::ObjectList m_activeNinjaJobs; }; #endif // KDEVNINJABUILDERPLUGIN_H diff --git a/projectbuilders/ninjabuilder/ninjabuilderpreferences.h b/projectbuilders/ninjabuilder/ninjabuilderpreferences.h index b8fc5db6e4..4c0ad38a76 100644 --- a/projectbuilders/ninjabuilder/ninjabuilderpreferences.h +++ b/projectbuilders/ninjabuilder/ninjabuilderpreferences.h @@ -1,48 +1,48 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef NINJABUILDERPREFERENCES_H #define NINJABUILDERPREFERENCES_H #include #include "ninjabuilderconfig.h" class QWidget; class QStringList; namespace Ui { class NinjaConfig; } class NinjaBuilderPreferences : public ProjectConfigPage { Q_OBJECT public: - explicit NinjaBuilderPreferences(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent = 0); + explicit NinjaBuilderPreferences(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent = nullptr); ~NinjaBuilderPreferences() override; QString name() const override; QString fullName() const override; QIcon icon() const override; private: Ui::NinjaConfig* m_prefsUi; }; #endif diff --git a/projectmanagers/cmake/cmakebuilddirchooser.h b/projectmanagers/cmake/cmakebuilddirchooser.h index 1c316aa78f..186cc248ae 100644 --- a/projectmanagers/cmake/cmakebuilddirchooser.h +++ b/projectmanagers/cmake/cmakebuilddirchooser.h @@ -1,89 +1,89 @@ /* KDevelop CMake Support * * Copyright 2007 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEBUILDDIRCHOOSER_H #define CMAKEBUILDDIRCHOOSER_H #include #include #include #include "cmakecommonexport.h" class QDialogButtonBox; namespace Ui { class CMakeBuildDirChooser; } class KDEVCMAKECOMMON_EXPORT CMakeBuildDirChooser : public QDialog { Q_OBJECT public: enum StatusType { BuildDirCreated = 1, CorrectProject = 2, BuildFolderEmpty = 4, HaveCMake = 8, CorrectBuildDir = 16, DirAlreadyCreated = 32 //Error message in case it's already configured }; Q_DECLARE_FLAGS( StatusTypes, StatusType ) - explicit CMakeBuildDirChooser(QWidget* parent = 0); + explicit CMakeBuildDirChooser(QWidget* parent = nullptr); ~CMakeBuildDirChooser() override; KDevelop::Path cmakeBinary() const; KDevelop::Path installPrefix() const; KDevelop::Path buildFolder() const; QString buildType() const; QString extraArguments() const; void setCMakeBinary(const KDevelop::Path& path); void setInstallPrefix(const KDevelop::Path& path); void setBuildFolder(const KDevelop::Path& path); void setBuildType(const QString& buildType); void setSourceFolder( const KDevelop::Path& srcFolder ); void setAlreadyUsed(const QStringList& used); void setStatus(const QString& message, bool canApply); void setExtraArguments(const QString& args); private slots: void updated(); private: QStringList m_alreadyUsed; void buildDirSettings( const KDevelop::Path& buildDir, QString& srcDir, QString& installDir, QString& buildType); QStringList extraArgumentsHistory() const; Ui::CMakeBuildDirChooser* m_chooserUi; QDialogButtonBox* m_buttonBox; KDevelop::Path m_srcFolder; }; Q_DECLARE_OPERATORS_FOR_FLAGS( CMakeBuildDirChooser::StatusTypes ) #endif diff --git a/projectmanagers/cmake/cmakedocumentation.cpp b/projectmanagers/cmake/cmakedocumentation.cpp index af51c52dbd..ea7716ab5c 100644 --- a/projectmanagers/cmake/cmakedocumentation.cpp +++ b/projectmanagers/cmake/cmakedocumentation.cpp @@ -1,208 +1,208 @@ /* KDevelop CMake Support * * Copyright 2009 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakedocumentation.h" #include "cmakeutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmakemanager.h" #include "cmakeparserutils.h" #include "cmakeutils.h" #include "cmakehelpdocumentation.h" #include "cmakedoc.h" #include "debug.h" K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportDocFactory, "kdevcmakedocumentation.json", registerPlugin(); ) -CMakeDocumentation* CMakeDoc::s_provider=0; +CMakeDocumentation* CMakeDoc::s_provider=nullptr; KDevelop::IDocumentationProvider* CMakeDoc::provider() const { return s_provider; } CMakeDocumentation::CMakeDocumentation(QObject* parent, const QVariantList&) : KDevelop::IPlugin( "kdevcmakedocumentation", parent ) , mCMakeCmd(CMake::findExecutable()) , m_index(nullptr) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IDocumentationProvider ) KDEV_USE_EXTENSION_INTERFACE( ICMakeDocumentation ) if (mCMakeCmd.isEmpty()) { setErrorDescription(i18n("Unable to find cmake executable. Is it installed on the system?") ); return; } CMakeDoc::s_provider=this; m_index= new QStringListModel(this); initializeModel(); } -static const char* args[] = { "--help-command", "--help-variable", "--help-module", "--help-property", 0, 0 }; +static const char* args[] = { "--help-command", "--help-variable", "--help-module", "--help-property", nullptr, nullptr }; void CMakeDocumentation::delayedInitialization() { for(int i=0; i<=Property; i++) { collectIds(QString(args[i])+"-list", (Type) i); } m_index->setStringList(m_typeForName.keys()); } void CMakeDocumentation::collectIds(const QString& param, Type type) { QStringList ids=CMake::executeProcess(mCMakeCmd, QStringList(param)).split('\n'); ids.takeFirst(); foreach(const QString& name, ids) { m_typeForName[name]=type; } } QStringList CMakeDocumentation::names(CMakeDocumentation::Type t) const { return m_typeForName.keys(t); } QString CMakeDocumentation::descriptionForIdentifier(const QString& id, Type t) const { QString desc; if(args[t]) { desc = CMake::executeProcess(mCMakeCmd, { args[t], id.simplified() }); desc = desc.remove(":ref:"); const QString rst2html = QStandardPaths::findExecutable(QStringLiteral("rst2html")); if (rst2html.isEmpty()) { desc = ("
" + desc.toHtmlEscaped() + "
" + i18n("

For better cmake documentation rendering, install rst2html

") + ""); } else { QProcess p; p.start(rst2html, { "--no-toc-backlinks" }); p.write(desc.toUtf8()); p.closeWriteChannel(); p.waitForFinished(); desc = QString::fromUtf8(p.readAllStandardOutput()); } } return desc; } KDevelop::IDocumentation::Ptr CMakeDocumentation::description(const QString& identifier, const QUrl &file) const { initializeModel(); //make it not queued if (!file.isEmpty() && !QMimeDatabase().mimeTypeForUrl(file).inherits("text/x-cmake")) { return KDevelop::IDocumentation::Ptr(); } QString desc; if(m_typeForName.contains(identifier)) { desc=descriptionForIdentifier(identifier, m_typeForName[identifier]); } else if(m_typeForName.contains(identifier.toLower())) { desc=descriptionForIdentifier(identifier, m_typeForName[identifier.toLower()]); } else if(m_typeForName.contains(identifier.toUpper())) { desc=descriptionForIdentifier(identifier, m_typeForName[identifier.toUpper()]); } KDevelop::IProject* p=KDevelop::ICore::self()->projectController()->findProjectForUrl(file); - ICMakeManager* m=0; + ICMakeManager* m=nullptr; if(p) m=p->managerPlugin()->extension(); if(m) { QPair entry = m->cacheValue(p, identifier); if(!entry.first.isEmpty()) desc += i18n("
Cache Value: %1\n", entry.first); if(!entry.second.isEmpty()) desc += i18n("
Cache Documentation: %1\n", entry.second); } if(desc.isEmpty()) return KDevelop::IDocumentation::Ptr(); else return KDevelop::IDocumentation::Ptr(new CMakeDoc(identifier, desc)); } KDevelop::IDocumentation::Ptr CMakeDocumentation::documentationForDeclaration(KDevelop::Declaration* decl) const { return description(decl->identifier().toString(), decl->url().toUrl()); } KDevelop::IDocumentation::Ptr CMakeDocumentation::documentationForIndex(const QModelIndex& idx) const { return description(idx.data().toString(), QUrl()); } QAbstractListModel* CMakeDocumentation::indexModel() const { initializeModel(); return m_index; } QIcon CMakeDocumentation::icon() const { return QIcon::fromTheme("cmake"); } QString CMakeDocumentation::name() const { return "CMake"; } KDevelop::IDocumentation::Ptr CMakeDocumentation::homePage() const { if(m_typeForName.isEmpty()) const_cast(this)->delayedInitialization(); // initializeModel(); return KDevelop::IDocumentation::Ptr(new CMakeHomeDocumentation); } void CMakeDocumentation::initializeModel() const { if(!m_typeForName.isEmpty()) return; QMetaObject::invokeMethod(const_cast(this), "delayedInitialization", Qt::QueuedConnection); } //////////CMakeDoc QWidget* CMakeDoc::documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent) { KDevelop::StandardDocumentationView* view = new KDevelop::StandardDocumentationView(findWidget, parent); view->setHtml(mDesc); return view; } #include "cmakedocumentation.moc" diff --git a/projectmanagers/cmake/cmakedocumentation.h b/projectmanagers/cmake/cmakedocumentation.h index 7aee1621cc..ed9fcfbf3b 100644 --- a/projectmanagers/cmake/cmakedocumentation.h +++ b/projectmanagers/cmake/cmakedocumentation.h @@ -1,72 +1,72 @@ /* KDevelop CMake Support * * Copyright 2009 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEDOCUMENTATION_H #define CMAKEDOCUMENTATION_H #include #include #include #include #include #include "icmakedocumentation.h" class QStringListModel; namespace KDevelop { class Declaration; } class CMakeManager; class QUrl; class CMakeDocumentation : public KDevelop::IPlugin, public ICMakeDocumentation { Q_OBJECT Q_INTERFACES( ICMakeDocumentation ) Q_INTERFACES( KDevelop::IDocumentationProvider ) public: - explicit CMakeDocumentation( QObject* parent = 0, const QVariantList& args = QVariantList() ); + explicit CMakeDocumentation( QObject* parent = nullptr, const QVariantList& args = QVariantList() ); KDevelop::IDocumentation::Ptr description(const QString& identifier, const QUrl &file) const override; KDevelop::IDocumentation::Ptr documentationForDeclaration(KDevelop::Declaration* declaration) const override; QStringList names(Type t) const override; QAbstractListModel* indexModel() const override; KDevelop::IDocumentation::Ptr documentationForIndex(const QModelIndex& idx) const override; QIcon icon() const override; QString name() const override; KDevelop::IDocumentation::Ptr homePage() const override; QString descriptionForIdentifier(const QString& identifier, Type t) const; public slots: void delayedInitialization(); Q_SIGNALS: void addHistory(const KDevelop::IDocumentation::Ptr& doc) const override; private: void initializeModel() const; void collectIds(const QString& param, Type type); QMap m_typeForName; const QString mCMakeCmd; QStringListModel* m_index; }; #endif // CMAKEDOCUMENTATION_H diff --git a/projectmanagers/cmake/cmakehelpdocumentation.h b/projectmanagers/cmake/cmakehelpdocumentation.h index f430feb565..b3787838cf 100644 --- a/projectmanagers/cmake/cmakehelpdocumentation.h +++ b/projectmanagers/cmake/cmakehelpdocumentation.h @@ -1,53 +1,53 @@ /* KDevelop CMake Support * * Copyright 2009 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEHELPDOCUMENTATION_H #define CMAKEHELPDOCUMENTATION_H #include #include class CMakeContentsModel : public QAbstractItemModel { Q_OBJECT public: CMakeContentsModel(QObject* parent) ; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex&) const override { return 1; } QModelIndex parent(const QModelIndex& child) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role) const override; public slots: void showItem(const QModelIndex& idx); }; class CMakeHomeDocumentation : public KDevelop::IDocumentation { public: KDevelop::IDocumentationProvider* provider() const override; QString name() const override; QString description() const override { return name(); } - QWidget* documentationWidget ( KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = 0 ) override; + QWidget* documentationWidget ( KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = nullptr ) override; }; #endif diff --git a/projectmanagers/cmake/cmakemanager.cpp b/projectmanagers/cmake/cmakemanager.cpp index 2e41e01752..2c2ef4cb7b 100644 --- a/projectmanagers/cmake/cmakemanager.cpp +++ b/projectmanagers/cmake/cmakemanager.cpp @@ -1,887 +1,887 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2013 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakemanager.h" #include "cmakeedit.h" #include "cmakeutils.h" #include "cmakeprojectdata.h" #include "duchain/cmakeparsejob.h" #include "cmakeimportjsonjob.h" #include "debug.h" #include "settings/cmakepreferences.h" #include #include "cmakecodecompletionmodel.h" #include "cmakenavigationwidget.h" #include "icmakedocumentation.h" #include "cmakemodelitems.h" #include "testing/ctestutils.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 #include #include #include #include #include Q_DECLARE_METATYPE(KDevelop::IProject*); using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportFactory, "kdevcmakemanager.json", registerPlugin(); ) const QString DIALOG_CAPTION = i18n("KDevelop - CMake Support"); CMakeManager::CMakeManager( QObject* parent, const QVariantList& ) : KDevelop::AbstractFileManagerPlugin( "kdevcmakemanager", parent ) , m_filter( new ProjectFilterManager( this ) ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBuildSystemManager ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::ILanguageSupport ) KDEV_USE_EXTENSION_INTERFACE( ICMakeManager) if (CMake::findExecutable().isEmpty()) { setErrorDescription(i18n("Unable to find cmake executable. Is it installed on the system?")); m_highlight = nullptr; return; } m_highlight = new KDevelop::CodeHighlighting(this); new CodeCompletion(this, new CMakeCodeCompletionModel(this), name()); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CMakeManager::projectClosing); connect(this, &KDevelop::AbstractFileManagerPlugin::folderAdded, this, &CMakeManager::folderAdded); // m_fileSystemChangeTimer = new QTimer(this); // m_fileSystemChangeTimer->setSingleShot(true); // m_fileSystemChangeTimer->setInterval(100); // connect(m_fileSystemChangeTimer,SIGNAL(timeout()),SLOT(filesystemBuffererTimeout())); } CMakeManager::~CMakeManager() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); } bool CMakeManager::hasBuildInfo(ProjectBaseItem* item) const { return m_projects[item->project()].jsonData.files.contains(item->path()); } Path CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const { // CMakeFolderItem *fi=dynamic_cast(item); // Path ret; // ProjectBaseItem* parent = fi ? fi->formerParent() : item->parent(); // if (parent) // ret=buildDirectory(parent); // else // ret=Path(CMake::currentBuildDir(item->project())); // // if(fi) // ret.addPath(fi->buildDir()); // return ret; return Path(CMake::currentBuildDir(item->project())); } KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project ) { CMake::checkForNeedingConfigure(project); return AbstractFileManagerPlugin::import(project); } KJob* CMakeManager::createImportJob(ProjectFolderItem* item) { auto project = item->project(); QList jobs; // create the JSON file if it doesn't exist auto commandsFile = CMake::commandsFile(project); if (!QFileInfo::exists(commandsFile.toLocalFile())) { qCDebug(CMAKE) << "couldn't find commands file:" << commandsFile << "- now trying to reconfigure"; jobs << builder()->configure(project); } // parse the JSON file CMakeImportJob* job = new CMakeImportJob(project, this); connect(job, &CMakeImportJob::result, this, &CMakeManager::importFinished); jobs << job; // generate the file system listing jobs << KDevelop::AbstractFileManagerPlugin::createImportJob(item); Q_ASSERT(!jobs.contains(nullptr)); ExecuteCompositeJob* composite = new ExecuteCompositeJob(this, jobs); // even if the cmake call failed, we want to load the project so that the project can be worked on composite->setAbortOnError(false); return composite; } // QList CMakeManager::parse(ProjectFolderItem*) // { return QList< ProjectFolderItem* >(); } // // QList CMakeManager::targets() const { QList ret; foreach(IProject* p, m_projects.keys()) { ret+=p->projectItem()->targetList(); } return ret; } CMakeFile CMakeManager::fileInformation(KDevelop::ProjectBaseItem* item) const { const CMakeJsonData & data = m_projects[item->project()].jsonData; QHash::const_iterator it = data.files.constFind(item->path()); if (it == data.files.constEnd()) { // if the item path contains a symlink, then we will not find it in the lookup table // as that only only stores canonicalized paths. Thus, we fallback to // to the canonicalized path and see if that brings up any matches const auto canonicalized = Path(QFileInfo(item->path().toLocalFile()).canonicalFilePath()); it = data.files.constFind(canonicalized); } if (it != data.files.constEnd()) { return *it; } else { // otherwise look for siblings and use the include paths of any we find const Path folder = item->folder() ? item->path() : item->path().parent(); for( it = data.files.constBegin(); it != data.files.constEnd(); ++it) { if (folder.isDirectParentOf(it.key())) { return *it; } } } // last-resort fallback: bubble up the parent chain, and keep looking for include paths if (auto parent = item->parent()) { return fileInformation(parent); } return {}; } Path::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).includes; } Path::List CMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).frameworkDirectories; } QHash CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const { return fileInformation(item).defines; } KDevelop::IProjectBuilder * CMakeManager::builder() const { IPlugin* i = core()->pluginController()->pluginForExtension( "org.kdevelop.IProjectBuilder", "KDevCMakeBuilder"); Q_ASSERT(i); KDevelop::IProjectBuilder* _builder = i->extension(); Q_ASSERT(_builder ); return _builder ; } bool CMakeManager::reload(KDevelop::ProjectFolderItem* folder) { qCDebug(CMAKE) << "reloading" << folder->path(); IProject* project = folder->project(); if (!project->isReady()) return false; KJob *job = createImportJob(folder); project->setReloadJob(job); ICore::self()->runController()->registerJob( job ); return true; } static void populateTargets(ProjectFolderItem* folder, const QHash& targets) { QStringList dirTargets = targets[folder->path()]; foreach (ProjectTargetItem* item, folder->targetList()) { if(!dirTargets.contains(item->text())) { delete item; } else { dirTargets.removeAll(item->text()); } } static QSet standardTargets = { QStringLiteral("edit_cache"), QStringLiteral("rebuild_cache"), QStringLiteral("list_install_components"), QStringLiteral("test"), //not really standard, but applicable for make and ninja QStringLiteral("install") }; foreach (const QString& name, dirTargets) { if (!name.endsWith(QLatin1String("_automoc")) && !standardTargets.contains(name) && !name.startsWith(QLatin1String("install/")) ) new CMakeTargetItem(folder, name); } foreach (ProjectFolderItem* children, folder->folderList()) { populateTargets(children, targets); } } void CMakeManager::importFinished(KJob* j) { CMakeImportJob* job = qobject_cast(j); Q_ASSERT(job); auto project = job->project(); if (job->error() != 0) { qCDebug(CMAKE) << "Import failed for project" << project->name() << job->errorText(); m_projects.remove(project); } qCDebug(CMAKE) << "Successfully imported project" << project->name(); CMakeProjectData data; data.watcher->addPath(CMake::commandsFile(project).toLocalFile()); data.watcher->addPath(CMake::targetDirectoriesFile(project).toLocalFile()); data.jsonData = job->jsonData(); data.targets = job->targets(); connect(data.watcher.data(), &QFileSystemWatcher::fileChanged, this, &CMakeManager::dirtyFile); connect(data.watcher.data(), &QFileSystemWatcher::directoryChanged, this, &CMakeManager::dirtyFile); m_projects[job->project()] = data; populateTargets(job->project()->projectItem(), job->targets()); CTestUtils::createTestSuites(job->testSuites(), project); } // void CMakeManager::deletedWatchedDirectory(IProject* p, const QUrl &dir) // { // if(p->folder().equals(dir, QUrl::CompareWithoutTrailingSlash)) { // ICore::self()->projectController()->closeProject(p); // } else { // if(dir.fileName()=="CMakeLists.txt") { // QList folders = p->foldersForUrl(dir.upUrl()); // foreach(ProjectFolderItem* folder, folders) // reload(folder); // } else { // qDeleteAll(p->itemsForUrl(dir)); // } // } // } // void CMakeManager::directoryChanged(const QString& dir) // { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // void CMakeManager::filesystemBuffererTimeout() // { // Q_FOREACH(const QString& file, m_fileSystemChangedBuffer) { // realDirectoryChanged(file); // } // m_fileSystemChangedBuffer.clear(); // } // void CMakeManager::realDirectoryChanged(const QString& dir) // { // QUrl path(dir); // IProject* p=ICore::self()->projectController()->findProjectForUrl(dir); // if(!p || !p->isReady()) { // if(p) { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // return; // } // // if(!QFile::exists(dir)) { // path.adjustPath(QUrl::AddTrailingSlash); // deletedWatchedDirectory(p, path); // } else // dirtyFile(dir); // } QList< KDevelop::ProjectTargetItem * > CMakeManager::targets(KDevelop::ProjectFolderItem * folder) const { return folder->targetList(); } QString CMakeManager::name() const { return languageName().str(); } IndexedString CMakeManager::languageName() { static IndexedString name("CMake"); return name; } KDevelop::ParseJob * CMakeManager::createParseJob(const IndexedString &url) { return new CMakeParseJob(url, this); } KDevelop::ICodeHighlighting* CMakeManager::codeHighlighting() const { return m_highlight; } // ContextMenuExtension CMakeManager::contextMenuExtension( KDevelop::Context* context ) // { // if( context->type() != KDevelop::Context::ProjectItemContext ) // return IPlugin::contextMenuExtension( context ); // // KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); // QList items = ctx->items(); // // if( items.isEmpty() ) // return IPlugin::contextMenuExtension( context ); // // m_clickedItems = items; // ContextMenuExtension menuExt; // if(items.count()==1 && dynamic_cast(items.first())) // { // QAction * action = new QAction( i18n( "Jump to Target Definition" ), this ); // connect( action, SIGNAL(triggered()), this, SLOT(jumpToDeclaration()) ); // menuExt.addAction( ContextMenuExtension::ProjectGroup, action ); // } // // return menuExt; // } // // void CMakeManager::jumpToDeclaration() // { // DUChainAttatched* du=dynamic_cast(m_clickedItems.first()); // if(du) // { // KTextEditor::Cursor c; // QUrl url; // { // KDevelop::DUChainReadLocker lock; // Declaration* decl = du->declaration().data(); // if(!decl) // return; // c = decl->rangeInCurrentRevision().start(); // url = decl->url().toUrl(); // } // // ICore::self()->documentController()->openDocument(url, c); // } // } // // // TODO: Port to Path API // bool CMakeManager::moveFilesAndFolders(const QList< ProjectBaseItem* > &items, ProjectFolderItem* toFolder) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Move files and folders within CMakeLists as follows:")); // // bool cmakeSuccessful = true; // CMakeFolderItem *nearestCMakeFolderItem = nearestCMakeFolder(toFolder); // IProject* project=toFolder->project(); // // QList movedUrls; // QList oldUrls; // foreach(ProjectBaseItem *movedItem, items) // { // QList dirtyItems = cmakeListedItemsAffectedByUrlChange(project, movedItem->url()); // QUrl movedItemNewUrl = toFolder->url(); // movedItemNewUrl.addPath(movedItem->baseName()); // if (movedItem->folder()) // movedItemNewUrl.adjustPath(QUrl::AddTrailingSlash); // foreach(ProjectBaseItem* dirtyItem, dirtyItems) // { // QUrl dirtyItemNewUrl = afterMoveUrl(dirtyItem->url(), movedItem->url(), movedItemNewUrl); // if (CMakeFolderItem* folder = dynamic_cast(dirtyItem)) // { // cmakeSuccessful &= changesWidgetRemoveCMakeFolder(folder, &changesWidget); // cmakeSuccessful &= changesWidgetAddFolder(dirtyItemNewUrl, nearestCMakeFolderItem, &changesWidget); // } // else if (dirtyItem->parent()->target()) // { // cmakeSuccessful &= changesWidgetMoveTargetFile(dirtyItem, dirtyItemNewUrl, &changesWidget); // } // } // // oldUrls += movedItem->url(); // movedUrls += movedItemNewUrl; // } // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort move?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // QList::const_iterator it1=oldUrls.constBegin(), it1End=oldUrls.constEnd(); // QList::const_iterator it2=movedUrls.constBegin(); // Q_ASSERT(oldUrls.size()==movedUrls.size()); // for(; it1!=it1End; ++it1, ++it2) // { // if (!KDevelop::renameUrl(project, *it1, *it2)) // return false; // // QList renamedItems = project->itemsForUrl(*it2); // bool dir = QFileInfo(it2->toLocalFile()).isDir(); // foreach(ProjectBaseItem* item, renamedItems) { // if(dir) // emit folderRenamed(Path(*it1), item->folder()); // else // emit fileRenamed(Path(*it1), item->file()); // } // } // // return true; // } // // bool CMakeManager::copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* toFolder) // { // IProject* project = toFolder->project(); // foreach(const Path& path, items) { // if (!KDevelop::copyUrl(project, path.toUrl(), toFolder->url())) // return false; // } // // return true; // } // // bool CMakeManager::removeFilesAndFolders(const QList &items) // { // using namespace CMakeEdit; // // IProject* p = 0; // QList urls; // foreach(ProjectBaseItem* item, items) // { // Q_ASSERT(item->folder() || item->file()); // // urls += item->url(); // if(!p) // p = item->project(); // } // // //First do CMakeLists changes // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Remove files and folders from CMakeLists as follows:")); // // bool cmakeSuccessful = changesWidgetRemoveItems(cmakeListedItemsAffectedByItemsChanged(items).toSet(), &changesWidget); // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort deletion?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = true; // //Then delete the files/folders // foreach(const QUrl& file, urls) // { // ret &= KDevelop::removeUrl(p, file, QDir(file.toLocalFile()).exists()); // } // // return ret; // } bool CMakeManager::removeFilesFromTargets(const QList &/*files*/) { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify project targets as follows:")); // // if (!files.isEmpty() && // changesWidgetRemoveFilesFromTargets(files, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges()) { // return true; // } return false; } // ProjectFolderItem* CMakeManager::addFolder(const Path& folder, ProjectFolderItem* parent) // { // using namespace CMakeEdit; // // CMakeFolderItem *cmakeParent = nearestCMakeFolder(parent); // if(!cmakeParent) // return 0; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Create folder '%1':", folder.lastPathSegment())); // // ///FIXME: use path in changes widget // changesWidgetAddFolder(folder.toUrl(), cmakeParent, &changesWidget); // // if(changesWidget.exec() && changesWidget.applyAllChanges()) // { // if(KDevelop::createFolder(folder.toUrl())) { //If saved we create the folder then the CMakeLists.txt file // Path newCMakeLists(folder, "CMakeLists.txt"); // KDevelop::createFile( newCMakeLists.toUrl() ); // } else // KMessageBox::error(0, i18n("Could not save the change."), // DIALOG_CAPTION); // } // // return 0; // } // // KDevelop::ProjectFileItem* CMakeManager::addFile( const Path& file, KDevelop::ProjectFolderItem* parent) // { // KDevelop::ProjectFileItem* created = 0; // if ( KDevelop::createFile(file.toUrl()) ) { // QList< ProjectFileItem* > files = parent->project()->filesForPath(IndexedString(file.pathOrUrl())); // if(!files.isEmpty()) // created = files.first(); // else // created = new KDevelop::ProjectFileItem( parent->project(), file, parent ); // } // return created; // } bool CMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &/*_files*/, ProjectTargetItem* /*target*/) { return false; // using namespace CMakeEdit; // // const QSet headerExt = QSet() << ".h" << ".hpp" << ".hxx"; // QList< ProjectFileItem* > files = _files; // for (int i = files.count() - 1; i >= 0; --i) // { // QString fileName = files[i]->fileName(); // QString fileExt = fileName.mid(fileName.lastIndexOf('.')); // QList sameUrlItems = files[i]->project()->itemsForUrl(files[i]->url()); // if (headerExt.contains(fileExt)) // files.removeAt(i); // else foreach(ProjectBaseItem* item, sameUrlItems) // { // if (item->parent() == target) // { // files.removeAt(i); // break; // } // } // } // // if(files.isEmpty()) // return true; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify target '%1' as follows:", target->baseName())); // // bool success = changesWidgetAddFilesToTarget(files, target, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges(); // // if(!success) // KMessageBox::error(0, i18n("CMakeLists changes failed."), DIALOG_CAPTION); // // return success; } // bool CMakeManager::renameFileOrFolder(ProjectBaseItem *item, const Path &newPath) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Rename '%1' to '%2':", item->text(), // newPath.lastPathSegment())); // // bool cmakeSuccessful = true, changedCMakeLists=false; // IProject* project=item->project(); // const Path oldPath=item->path(); // QUrl oldUrl=oldPath.toUrl(); // if (item->file()) // { // QList targetFiles = cmakeListedItemsAffectedByUrlChange(project, oldUrl); // foreach(ProjectBaseItem* targetFile, targetFiles) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetMoveTargetFile(targetFile, newPath.toUrl(), &changesWidget); // } // else if (CMakeFolderItem *folder = dynamic_cast(item)) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetRenameFolder(folder, newPath.toUrl(), &changesWidget); // // item->setPath(newPath); // if (changesWidget.hasDocuments() && cmakeSuccessful) { // changedCMakeLists = changesWidget.exec() && changesWidget.applyAllChanges(); // cmakeSuccessful &= changedCMakeLists; // } // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort rename?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = KDevelop::renameUrl(project, oldUrl, newPath.toUrl()); // if(!ret) { // item->setPath(oldPath); // } // return ret; // } // // bool CMakeManager::renameFile(ProjectFileItem *item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } // // bool CMakeManager::renameFolder(ProjectFolderItem* item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } QWidget* CMakeManager::specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) { KDevelop::TopDUContextPointer top= TopDUContextPointer(KDevelop::DUChain::self()->chainForDocument(url)); - Declaration *decl=0; + Declaration *decl=nullptr; if(top) { int useAt=top->findUseAt(top->transformToLocalRevision(position)); if(useAt>=0) { Use u=top->uses()[useAt]; decl=u.usedDeclaration(top->topContext()); } } - CMakeNavigationWidget* doc=0; + CMakeNavigationWidget* doc=nullptr; if(decl) { doc=new CMakeNavigationWidget(top, decl); } else { const IDocument* d=ICore::self()->documentController()->documentForUrl(url); const KTextEditor::Document* e=d->textDocument(); KTextEditor::Cursor start=position, end=position, step(0,1); for(QChar i=e->characterAt(start); i.isLetter() || i=='_'; i=e->characterAt(start-=step)) {} start+=step; for(QChar i=e->characterAt(end); i.isLetter() || i=='_'; i=e->characterAt(end+=step)) {} QString id=e->text(KTextEditor::Range(start, end)); ICMakeDocumentation* docu=CMake::cmakeDocumentation(); if( docu ) { IDocumentation::Ptr desc=docu->description(id, url); if(desc) { doc=new CMakeNavigationWidget(top, desc); } } } return doc; } QPair CMakeManager::cacheValue(KDevelop::IProject* /*project*/, const QString& /*id*/) const { return QPair(); } // { // QPair ret; // if(project==0 && !m_projectsData.isEmpty()) // { // project=m_projectsData.keys().first(); // } // // // qCDebug(CMAKE) << "cache value " << id << project << (m_projectsData.contains(project) && m_projectsData[project].cache.contains(id)); // CMakeProjectData* data = m_projectsData[project]; // if(data && data->cache.contains(id)) // { // const CacheEntry& e=data->cache.value(id); // ret.first=e.value; // ret.second=e.doc; // } // return ret; // }Add // void CMakeManager::projectClosing(IProject* p) { m_projects.remove(p); // delete m_projectsData.take(p); // delete m_watchers.take(p); // // m_filter->remove(p); // // qCDebug(CMAKE) << "Project closed" << p; } // // QStringList CMakeManager::processGeneratorExpression(const QStringList& expr, IProject* project, ProjectTargetItem* target) const // { // QStringList ret; // const CMakeProjectData* data = m_projectsData[project]; // GenerationExpressionSolver exec(data->properties, data->targetAlias); // if(target) // exec.setTargetName(target->text()); // // exec.defineVariable("INSTALL_PREFIX", data->vm.value("CMAKE_INSTALL_PREFIX").join(QString())); // for(QStringList::const_iterator it = expr.constBegin(), itEnd = expr.constEnd(); it!=itEnd; ++it) { // QStringList val = exec.run(*it).split(';'); // ret += val; // } // return ret; // } /* void CMakeManager::addPending(const Path& path, CMakeFolderItem* folder) { m_pending.insert(path, folder); } CMakeFolderItem* CMakeManager::takePending(const Path& path) { return m_pending.take(path); } void CMakeManager::addWatcher(IProject* p, const QString& path) { if (QFileSystemWatcher* watcher = m_watchers.value(p)) { watcher->addPath(path); } else { qWarning() << "Could not find a watcher for project" << p << p->name() << ", path " << path; Q_ASSERT(false); } }*/ // CMakeProjectData CMakeManager::projectData(IProject* project) // { // Q_ASSERT(QThread::currentThread() == project->thread()); // CMakeProjectData* data = m_projectsData[project]; // if(!data) { // data = new CMakeProjectData; // m_projectsData[project] = data; // } // return *data; // } ProjectFilterManager* CMakeManager::filterManager() const { return m_filter; } void CMakeManager::dirtyFile(const QString& path) { qCDebug(CMAKE) << "dirty!" << path; //we initialize again hte project that sent the signal for(QHash::const_iterator it = m_projects.constBegin(), itEnd = m_projects.constEnd(); it!=itEnd; ++it) { if(it->watcher == sender()) { reload(it.key()->projectItem()); break; } } } void CMakeManager::folderAdded(KDevelop::ProjectFolderItem* folder) { populateTargets(folder, m_projects[folder->project()].targets); } ProjectFolderItem* CMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO: when we have data about targets, use folders with targets or similar if (QFile::exists(path.toLocalFile()+"/CMakeLists.txt")) return new KDevelop::ProjectBuildFolderItem( project, path, parent ); else return KDevelop::AbstractFileManagerPlugin::createFolderItem(project, path, parent); } int CMakeManager::perProjectConfigPages() const { return 1; } ConfigPage* CMakeManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CMakePreferences(this, options, parent); } return nullptr; } #include "cmakemanager.moc" diff --git a/projectmanagers/cmake/cmakemanager.h b/projectmanagers/cmake/cmakemanager.h index 9a1b2be7fc..3f68cd2995 100644 --- a/projectmanagers/cmake/cmakemanager.h +++ b/projectmanagers/cmake/cmakemanager.h @@ -1,168 +1,168 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2009 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEMANAGER_H #define CMAKEMANAGER_H #include #include #include #include #include #include #include #include #include #include #include "cmakeprojectdata.h" #include "icmakemanager.h" #include "cmakeprojectvisitor.h" class WaitAllJobs; class CMakeCommitChangesJob; struct CMakeProjectData; class QStandardItem; class QDir; class QObject; class CMakeHighlighting; class CMakeDocumentation; namespace KDevelop { class IProject; class IProjectBuilder; class ICodeHighlighting; class ProjectFolderItem; class ProjectBaseItem; class ProjectFileItem; class ProjectTargetItem; class ProjectFilterManager; class IProjectFilter; class ParseJob; class ContextMenuExtension; class Context; } class CMakeFolderItem; class CMakeManager : public KDevelop::AbstractFileManagerPlugin , public KDevelop::IBuildSystemManager , public KDevelop::ILanguageSupport , public ICMakeManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) Q_INTERFACES( KDevelop::IProjectFileManager ) Q_INTERFACES( KDevelop::ILanguageSupport ) Q_INTERFACES( ICMakeManager ) public: - explicit CMakeManager( QObject* parent = 0, const QVariantList& args = QVariantList() ); + explicit CMakeManager( QObject* parent = nullptr, const QVariantList& args = QVariantList() ); ~CMakeManager() override; Features features() const override { return Features(Folders | Targets | Files ); } KDevelop::IProjectBuilder* builder() const override; bool hasBuildInfo(KDevelop::ProjectBaseItem*) const override; KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem *) const override; KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem *item) const override; QHash defines(KDevelop::ProjectBaseItem *) const override; - KDevelop::ProjectTargetItem* createTarget( const QString&, KDevelop::ProjectFolderItem* ) override { return 0; } + KDevelop::ProjectTargetItem* createTarget( const QString&, KDevelop::ProjectFolderItem* ) override { return nullptr; } virtual QList targets() const; QList targets(KDevelop::ProjectFolderItem* folder) const override; // virtual KDevelop::ProjectFolderItem* addFolder( const KDevelop::Path& folder, KDevelop::ProjectFolderItem* parent ); // virtual KDevelop::ProjectFileItem* addFile( const KDevelop::Path&, KDevelop::ProjectFolderItem* ); bool addFilesToTarget( const QList &files, KDevelop::ProjectTargetItem* target) override; bool removeTarget( KDevelop::ProjectTargetItem* ) override { return false; } bool removeFilesFromTargets( const QList &files ) override; // virtual bool removeFilesAndFolders( const QList &items); // // virtual bool renameFile(KDevelop::ProjectFileItem*, const KDevelop::Path&); // virtual bool renameFolder(KDevelop::ProjectFolderItem*, const KDevelop::Path&); // virtual bool moveFilesAndFolders(const QList< KDevelop::ProjectBaseItem* > &items, KDevelop::ProjectFolderItem *newParent); // virtual bool copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* newParent); // // virtual QList parse( KDevelop::ProjectFolderItem* dom ); KDevelop::ProjectFolderItem* import( KDevelop::IProject *project ) override; KJob* createImportJob(KDevelop::ProjectFolderItem* item) override; // bool reload(KDevelop::ProjectFolderItem*) override; // // virtual KDevelop::ContextMenuExtension contextMenuExtension( KDevelop::Context* context ); - KDevelop::ProjectFolderItem* createFolderItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = 0) override; + KDevelop::ProjectFolderItem* createFolderItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = nullptr) override; QPair cacheValue(KDevelop::IProject* project, const QString& id) const override; //LanguageSupport QString name() const override; KDevelop::ParseJob *createParseJob(const KDevelop::IndexedString &url) override; KDevelop::ICodeHighlighting* codeHighlighting() const override; QWidget* specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) override; // void addPending(const KDevelop::Path& path, CMakeFolderItem* folder); // CMakeFolderItem* takePending(const KDevelop::Path& path); // void addWatcher(KDevelop::IProject* p, const QString& path); // CMakeProjectData projectData(KDevelop::IProject* project); KDevelop::ProjectFilterManager* filterManager() const; static KDevelop::IndexedString languageName(); int perProjectConfigPages() const override; KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; signals: void folderRenamed(const KDevelop::Path& oldFolder, KDevelop::ProjectFolderItem* newFolder); void fileRenamed(const KDevelop::Path& oldFile, KDevelop::ProjectFileItem* newFile); private slots: // void dirtyFile(const QString& file); // // void jumpToDeclaration(); void projectClosing(KDevelop::IProject*); void dirtyFile(const QString& file); // // void directoryChanged(const QString& dir); // void filesystemBuffererTimeout(); void importFinished(KJob* job); private: CMakeFile fileInformation(KDevelop::ProjectBaseItem* item) const; void folderAdded(KDevelop::ProjectFolderItem* folder); QHash m_projects; KDevelop::ProjectFilterManager* m_filter; KDevelop::ICodeHighlighting* m_highlight; }; #endif diff --git a/projectmanagers/cmake/cmakenavigationwidget.cpp b/projectmanagers/cmake/cmakenavigationwidget.cpp index 0c0ad745d6..2ce7ece021 100644 --- a/projectmanagers/cmake/cmakenavigationwidget.cpp +++ b/projectmanagers/cmake/cmakenavigationwidget.cpp @@ -1,61 +1,61 @@ /* KDevelop CMake Support * * Copyright 2009 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakenavigationwidget.h" #include #include #include using namespace KDevelop; class CMakeNavigationContext: public AbstractNavigationContext { public: CMakeNavigationContext(TopDUContextPointer top, const QString& name, const QString& html) - : AbstractNavigationContext(top, 0), mName(name), mDescription(html) {} + : AbstractNavigationContext(top, nullptr), mName(name), mDescription(html) {} QString name() const override { return mName; } QString html(bool shorten = false) override { Q_UNUSED(shorten); return mDescription; } private: QString mName; QString mDescription; }; class CMakeDeclarationNavigationContext: public AbstractDeclarationNavigationContext { public: CMakeDeclarationNavigationContext(DeclarationPointer decl, TopDUContextPointer top) : AbstractDeclarationNavigationContext(decl, top) {} }; CMakeNavigationWidget::CMakeNavigationWidget(TopDUContextPointer top, const IDocumentation::Ptr doc) { setContext( NavigationContextPointer(new CMakeNavigationContext(top, doc->name(), doc->description())) ); } CMakeNavigationWidget::CMakeNavigationWidget(KDevelop::DUChainPointer< KDevelop::TopDUContext > top, KDevelop::Declaration* decl) { setContext( NavigationContextPointer(new CMakeDeclarationNavigationContext(DeclarationPointer(decl), top)) ); } diff --git a/projectmanagers/cmake/duchain/contextbuilder.cpp b/projectmanagers/cmake/duchain/contextbuilder.cpp index 4774db2774..776b3c58eb 100644 --- a/projectmanagers/cmake/duchain/contextbuilder.cpp +++ b/projectmanagers/cmake/duchain/contextbuilder.cpp @@ -1,55 +1,55 @@ /* KDevelop CMake Support * * Copyright 2014 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "contextbuilder.h" #include using namespace KDevelop; KDevelop::DUContext* ContextBuilder::contextFromNode(CMakeContentIterator* /*node*/) { - return 0; + return nullptr; } KDevelop::RangeInRevision ContextBuilder::editorFindRange(CMakeContentIterator* /*fromNode*/, CMakeContentIterator* /*toNode*/) { return RangeInRevision(); } KDevelop::QualifiedIdentifier ContextBuilder::identifierForNode(CMakeFunctionDesc* node) { return QualifiedIdentifier(node->name); } void ContextBuilder::setContextOnNode(CMakeContentIterator* /*node*/, KDevelop::DUContext* /*context*/) { } TopDUContext* ContextBuilder::newTopContext(const RangeInRevision& range, ParsingEnvironmentFile* file) { if (!file) { file = new ParsingEnvironmentFile(document()); file->setLanguage(CMakeManager::languageName()); } return KDevelop::AbstractContextBuilder< CMakeContentIterator, CMakeFunctionDesc >::newTopContext(range, file); } diff --git a/projectmanagers/cmake/parser/cmakeduchaintypes.cpp b/projectmanagers/cmake/parser/cmakeduchaintypes.cpp index 5bc92f2d09..69830ad13d 100644 --- a/projectmanagers/cmake/parser/cmakeduchaintypes.cpp +++ b/projectmanagers/cmake/parser/cmakeduchaintypes.cpp @@ -1,51 +1,51 @@ /* KDevelop CMake Support * * Copyright 2007-2012 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakeduchaintypes.h" #include namespace KDevelop { REGISTER_TYPE(TargetType); } TargetType::TargetType() : AbstractType(createData()) {} TargetType::TargetType(TargetType& rhs) : AbstractType(copyData(*rhs.d_func())) {} TargetType::TargetType(KDevelop::AbstractTypeData& dd): AbstractType(dd) {} KDevelop::AbstractType* TargetType::clone() const { return new TargetType; } void TargetType::accept0(KDevelop::TypeVisitor* ) const {} bool TargetType::equals(const KDevelop::AbstractType* rhs) const { - return dynamic_cast(rhs)!=0; + return dynamic_cast(rhs)!=nullptr; } diff --git a/projectmanagers/cmake/settings/cmakecachedelegate.cpp b/projectmanagers/cmake/settings/cmakecachedelegate.cpp index 038a7e0f55..5be957f4aa 100644 --- a/projectmanagers/cmake/settings/cmakecachedelegate.cpp +++ b/projectmanagers/cmake/settings/cmakecachedelegate.cpp @@ -1,191 +1,191 @@ /* KDevelop CMake Support * * Copyright 2008 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakecachedelegate.h" #include "../debug.h" #include #include #include #include #include CMakeCacheDelegate::CMakeCacheDelegate(QObject * parent) : QItemDelegate(parent) { m_sample=new KUrlRequester(); } CMakeCacheDelegate::~CMakeCacheDelegate() { delete m_sample; } QWidget * CMakeCacheDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const { - QWidget *ret=0; + QWidget *ret=nullptr; if(index.column()==2) { QModelIndex typeIdx=index.sibling(index.row(), 1); QString type=typeIdx.model()->data(typeIdx, Qt::DisplayRole).toString(); if(type=="BOOL") { QCheckBox* box=new QCheckBox(parent); connect(box, &QCheckBox::toggled, this, &CMakeCacheDelegate::checkboxToggled); ret = box; } else if(type=="PATH" || type=="FILEPATH") { KUrlRequester *r=new KUrlRequester(parent); if(type=="FILEPATH") r->setMode(KFile::File); else r->setMode(KFile::Directory | KFile::ExistingOnly); emit const_cast(this)->sizeHintChanged ( index ); qCDebug(CMAKE) << "EMITINT!" << index; ret=r; } else { ret=QItemDelegate::createEditor(parent, option, index); } if(!ret) qCDebug(CMAKE) << "Did not recognize type " << type; } return ret; } void CMakeCacheDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const { if(index.column()==2) { QModelIndex typeIdx=index.sibling(index.row(), 1); QString type=index.model()->data(typeIdx, Qt::DisplayRole).toString(); QString value=index.model()->data(index, Qt::DisplayRole).toString(); if(type=="BOOL") { QCheckBox *boolean=qobject_cast(editor); boolean->setCheckState(value=="ON" ? Qt::Checked : Qt::Unchecked); } else if(type=="PATH" || type=="FILEPATH") { KUrlRequester *url=qobject_cast(editor); url->setUrl(QUrl(value)); } else { QItemDelegate::setEditorData(editor, index); } } else qCDebug(CMAKE) << "Error. trying to edit a read-only field"; } void CMakeCacheDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const { if(index.column()==2) { QModelIndex typeIdx=index.sibling(index.row(), 1); QString type=model->data(typeIdx, Qt::DisplayRole).toString(); QString value; if(type=="BOOL") { QCheckBox *boolean=qobject_cast(editor); value = boolean->isChecked() ? "ON" : "OFF"; } else if(type=="PATH" || type=="FILEPATH") { KUrlRequester *urlreq=qobject_cast(editor); value = urlreq->url().toDisplayString(QUrl::StripTrailingSlash | QUrl::PreferLocalFile); //CMake usually don't put it } else { QItemDelegate::setModelData(editor, model, index); return; } model->setData(index, value, Qt::DisplayRole); } else qCDebug(CMAKE) << "Error. trying to edit a read-only field"; } void CMakeCacheDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if(index.column()==2) { QModelIndex typeIdx=index.sibling(index.row(), 1); QString type=index.model()->data(typeIdx, Qt::DisplayRole).toString(); if(type=="BOOL") return; } QItemDelegate::paint(painter, option, index); } QSize CMakeCacheDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index ) const { // qCDebug(CMAKE) << "calculant" << index << bool(option.state & QStyle::State_Editing); QSize ret=QItemDelegate::sizeHint(option, index); if(index.column()==2 && option.state & QStyle::State_Editing) { QModelIndex typeIdx=index.sibling(index.row(), 1); QString type=index.model()->data(typeIdx, Qt::DisplayRole).toString(); if(type=="PATH") { ret.setHeight(m_sample->sizeHint().height()); } } return ret; } void CMakeCacheDelegate::checkboxToggled() { // whenever the check box gets toggled, we directly want to set the // model data which is done by closing the editor which in turn // calls setModelData. otherwise, the behavior is quite confusing, see e.g. // https://bugs.kde.org/show_bug.cgi?id=304352 QCheckBox* editor = qobject_cast(sender()); Q_ASSERT(editor); closeEditor(editor); } void CMakeCacheDelegate::closingEditor(QWidget * editor, QAbstractItemDelegate::EndEditHint hint) { Q_UNUSED(editor); Q_UNUSED(hint); qCDebug(CMAKE) << "closing..."; } // void CMakeCacheDelegate::updateEditorGeometry ( QWidget * editor, const QStyleOptionViewItem & option, // const QModelIndex & index ) const // { // if(index.column()==2) // { // QModelIndex typeIdx=index.sibling(index.row(), 1); // QString type=index.model()->data(typeIdx, Qt::DisplayRole).toString(); // if(type=="PATH") // { // KUrlRequester* urlreq=qobject_cast(editor); // urlreq->setGeometry(QRect(option.rect.topLeft(), urlreq->sizeHint())); // return; // } // } // QItemDelegate::updateEditorGeometry( editor, option, index ); // } diff --git a/projectmanagers/cmake/settings/cmakecachemodel.cpp b/projectmanagers/cmake/settings/cmakecachemodel.cpp index ff87bc0597..daaa67a698 100644 --- a/projectmanagers/cmake/settings/cmakecachemodel.cpp +++ b/projectmanagers/cmake/settings/cmakecachemodel.cpp @@ -1,217 +1,217 @@ /* KDevelop CMake Support * * Copyright 2007 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakecachemodel.h" #include #include #include "cmakecachereader.h" #include "../debug.h" //4 columns: name, type, value, comment //name:type=value - comment CMakeCacheModel::CMakeCacheModel(QObject *parent, const KDevelop::Path &path) : QStandardItemModel(parent), m_filePath(path) { read(); } void CMakeCacheModel::reset() { emit beginResetModel(); clear(); m_internal.clear(); m_modifiedRows.clear(); read(); emit endResetModel(); } void CMakeCacheModel::read() { // Set headers QStringList labels; labels.append(i18n("Name")); labels.append(i18n("Type")); labels.append(i18n("Value")); labels.append(i18n("Comment")); labels.append(i18n("Advanced")); setHorizontalHeaderLabels(labels); QFile file(m_filePath.toLocalFile()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qCDebug(CMAKE) << "error. Could not find the file"; return; } int currentIdx=0; QStringList currentComment; QTextStream in(&file); QHash variablePos; while (!in.atEnd()) { QString line = in.readLine().trimmed(); if(line.startsWith("//")) currentComment += line.right(line.count()-2); else if(!line.isEmpty() && !line.startsWith('#')) //it is a variable { CacheLine c; c.readLine(line); if(c.isCorrect()) { QString name=c.name(), flag=c.flag(); QString type=c.type(); QString value=c.value(); QList lineItems; lineItems.append(new QStandardItem(name)); lineItems.append(new QStandardItem(type)); lineItems.append(new QStandardItem(value)); lineItems.append(new QStandardItem(currentComment.join("\n"))); if(flag=="INTERNAL") { m_internal.insert(name); } else if(flag=="ADVANCED") { if(variablePos.contains(name)) { int pos=variablePos[name]; QStandardItem *p = item(pos, 4); if(!p) { p=new QStandardItem(value); setItem(pos, 4, p); } else { p->setText(value); } } else { qCDebug(CMAKE) << "Flag for an unknown variable"; } } if(!flag.isEmpty()) { lineItems[0]->setText(lineItems[0]->text()+'-'+flag); } insertRow(currentIdx, lineItems); variablePos[name]=currentIdx; currentIdx++; currentComment.clear(); } } else if(line.startsWith('#') && line.contains("INTERNAL")) { m_internalBegin=currentIdx; // qCDebug(CMAKE) << "Comment: " << line << " -.- " << currentIdx; } else if(!line.startsWith('#') && !line.isEmpty()) { qCDebug(CMAKE) << "unrecognized cache line: " << line; } } } bool CMakeCacheModel::setData(const QModelIndex& index, const QVariant& value, int role) { bool ret = QStandardItemModel::setData(index, value, role); if (ret) { m_modifiedRows.insert(index.row()); } return ret; } QVariantMap CMakeCacheModel::changedValues() const { QVariantMap ret; for(int i=0; itext()+':'+type->text(), valu->text()); } return ret; } QString CMakeCacheModel::value(const QString & varName) const { for(int i=0; itext()==varName) { QStandardItem* valu = item(i, 2); return valu->text(); } } return QString(); } bool CMakeCacheModel::isAdvanced(int i) const { QStandardItem *p=item(i, 4); - bool isAdv= (p!=0) || i>m_internalBegin; + bool isAdv= (p!=nullptr) || i>m_internalBegin; if(!isAdv) { p=item(i, 1); isAdv = p->text()=="INTERNAL" || p->text()=="STATIC"; } return isAdv || m_internal.contains(item(i,0)->text()); } bool CMakeCacheModel::isInternal(int i) const { bool isInt= i>m_internalBegin; return isInt; } QList< QModelIndex > CMakeCacheModel::persistentIndices() const { QList< QModelIndex > ret; for(int i=0; itext()=="BOOL") { QStandardItem* valu = item(i, 2); ret.append(valu->index()); } } return ret; } KDevelop::Path CMakeCacheModel::filePath() const { return m_filePath; } diff --git a/projectmanagers/cmake/settings/cmakepreferences.cpp b/projectmanagers/cmake/settings/cmakepreferences.cpp index 76aa87c770..e7cfbbbf10 100644 --- a/projectmanagers/cmake/settings/cmakepreferences.cpp +++ b/projectmanagers/cmake/settings/cmakepreferences.cpp @@ -1,368 +1,368 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2008 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakepreferences.h" #include #include #include #include #include #include #include #include "ui_cmakebuildsettings.h" #include "cmakecachedelegate.h" #include "cmakebuilddirchooser.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; CMakePreferences::CMakePreferences(IPlugin* plugin, const ProjectConfigOptions& options, QWidget* parent) - : ConfigPage(plugin, nullptr, parent), m_project(options.project), m_currentModel(0) + : ConfigPage(plugin, nullptr, parent), m_project(options.project), m_currentModel(nullptr) { QVBoxLayout* l = new QVBoxLayout( this ); QWidget* w = new QWidget; m_prefsUi = new Ui::CMakeBuildSettings; m_prefsUi->setupUi( w ); l->addWidget( w ); m_prefsUi->addBuildDir->setIcon(QIcon::fromTheme( "list-add" )); m_prefsUi->removeBuildDir->setIcon(QIcon::fromTheme( "list-remove" )); m_prefsUi->addBuildDir->setText(QString()); m_prefsUi->removeBuildDir->setText(QString()); m_prefsUi->cacheList->setItemDelegate(new CMakeCacheDelegate(m_prefsUi->cacheList)); m_prefsUi->cacheList->setSelectionMode(QAbstractItemView::SingleSelection); m_prefsUi->cacheList->horizontalHeader()->setStretchLastSection(true); m_prefsUi->cacheList->verticalHeader()->hide(); connect(m_prefsUi->buildDirs, static_cast(&KComboBox::currentIndexChanged), this, &CMakePreferences::buildDirChanged); connect(m_prefsUi->showInternal, &QCheckBox::stateChanged, this, &CMakePreferences::showInternal); connect(m_prefsUi->addBuildDir, &QPushButton::pressed, this, &CMakePreferences::createBuildDir); connect(m_prefsUi->removeBuildDir, &QPushButton::pressed, this, &CMakePreferences::removeBuildDir); connect(m_prefsUi->showAdvanced, &QPushButton::toggled, this, &CMakePreferences::showAdvanced); connect(m_prefsUi->environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &CMakePreferences::changed); connect(m_prefsUi->configureEnvironment, &EnvironmentConfigureButton::environmentConfigured, this, &CMakePreferences::changed); showInternal(m_prefsUi->showInternal->checkState()); m_subprojFolder = Path(options.projectTempFile).parent(); qCDebug(CMAKE) << "Source folder: " << m_srcFolder << options.projectTempFile; // foreach(const QVariant &v, args) // { // qCDebug(CMAKE) << "arg: " << v.toString(); // } m_prefsUi->configureEnvironment->setSelectionWidget(m_prefsUi->environment); m_prefsUi->showAdvanced->setChecked(false); showAdvanced(false); reset(); // load the initial values } CMakePreferences::~CMakePreferences() { CMake::removeOverrideBuildDirIndex(m_project); delete m_prefsUi; } void CMakePreferences::reset() { qCDebug(CMAKE) << "********loading"; m_prefsUi->buildDirs->clear(); m_prefsUi->buildDirs->addItems( CMake::allBuildDirs(m_project) ); CMake::removeOverrideBuildDirIndex(m_project); // addItems() triggers buildDirChanged(), compensate for it m_prefsUi->buildDirs->setCurrentIndex( CMake::currentBuildDirIndex(m_project) ); m_prefsUi->environment->setCurrentProfile( CMake::currentEnvironment(m_project) ); m_srcFolder = m_project->path(); m_prefsUi->removeBuildDir->setEnabled(m_prefsUi->buildDirs->count()!=0); // QString cmDir=group.readEntry("CMakeDirectory"); // m_prefsUi->kcfg_cmakeDir->setUrl(QUrl(cmDir)); // qCDebug(CMAKE) << "cmakedir" << cmDir; } void CMakePreferences::apply() { qCDebug(CMAKE) << "*******saving"; // the build directory list is incrementally maintained through createBuildDir() and removeBuildDir(). // We won't rewrite it here based on the data from m_prefsUi->buildDirs. CMake::removeOverrideBuildDirIndex( m_project, true ); // save current selection int savedBuildDir = CMake::currentBuildDirIndex(m_project); if (savedBuildDir < 0) { // no build directory exists: skip any writing to config file as well as configuring return; } CMake::setCurrentEnvironment( m_project, m_prefsUi->environment->currentProfile() ); qCDebug(CMAKE) << "writing to cmake config: using builddir " << CMake::currentBuildDirIndex(m_project); qCDebug(CMAKE) << "writing to cmake config: builddir path " << CMake::currentBuildDir(m_project); qCDebug(CMAKE) << "writing to cmake config: installdir " << CMake::currentInstallDir(m_project); qCDebug(CMAKE) << "writing to cmake config: build type " << CMake::currentBuildType(m_project); qCDebug(CMAKE) << "writing to cmake config: cmake binary " << CMake::currentCMakeBinary(m_project); qCDebug(CMAKE) << "writing to cmake config: environment " << CMake::currentEnvironment(m_project); //We run cmake on the builddir to generate it configure(); } void CMakePreferences::defaults() { // do nothing } void CMakePreferences::configureCacheView() { // Sets up the cache view after model re-creation/reset. // Emits changed(false) because model re-creation probably means // mass programmatical invocation of itemChanged(), which invokes changed(true) - which is not what we want. m_prefsUi->cacheList->setModel(m_currentModel); m_prefsUi->cacheList->hideColumn(1); m_prefsUi->cacheList->hideColumn(3); m_prefsUi->cacheList->hideColumn(4); m_prefsUi->cacheList->horizontalHeader()->resizeSection(0, 200); if( m_currentModel ) { m_prefsUi->cacheList->setEnabled( true ); foreach(const QModelIndex & idx, m_currentModel->persistentIndices()) { m_prefsUi->cacheList->openPersistentEditor(idx); } } else { m_prefsUi->cacheList->setEnabled( false ); } showInternal(m_prefsUi->showInternal->checkState()); } void CMakePreferences::updateCache(const Path &newBuildDir) { const Path file = newBuildDir.isValid() ? Path(newBuildDir, "CMakeCache.txt") : Path(); if(QFile::exists(file.toLocalFile())) { if (m_currentModel) { m_currentModel->deleteLater(); } m_currentModel = new CMakeCacheModel(this, file); configureCacheView(); connect(m_currentModel, &CMakeCacheModel::itemChanged, this, &CMakePreferences::cacheEdited); connect(m_currentModel, &CMakeCacheModel::modelReset, this, &CMakePreferences::configureCacheView); connect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, &CMakePreferences::listSelectionChanged); } else { - disconnect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, 0); + disconnect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, nullptr); if (m_currentModel) { m_currentModel->deleteLater(); - m_currentModel = 0; + m_currentModel = nullptr; } configureCacheView(); } if( !m_currentModel ) emit changed(); } void CMakePreferences::listSelectionChanged(const QModelIndex & index, const QModelIndex& ) { qCDebug(CMAKE) << "item " << index << " selected"; QModelIndex idx = index.sibling(index.row(), 3); QModelIndex idxType = index.sibling(index.row(), 1); QString comment=QString("%1. %2") .arg(m_currentModel->itemFromIndex(idxType)->text()) .arg(m_currentModel->itemFromIndex(idx)->text()); m_prefsUi->commentText->setText(comment); } void CMakePreferences::showInternal(int state) { if(!m_currentModel) return; bool showAdv=(state == Qt::Checked); for(int i=0; irowCount(); i++) { bool hidden=m_currentModel->isInternal(i) || (!showAdv && m_currentModel->isAdvanced(i)); m_prefsUi->cacheList->setRowHidden(i, hidden); } } void CMakePreferences::buildDirChanged(int index) { CMake::setOverrideBuildDirIndex( m_project, index ); const Path buildDir = CMake::currentBuildDir(m_project); m_prefsUi->environment->setCurrentProfile( CMake::currentEnvironment( m_project ) ); updateCache(buildDir); qCDebug(CMAKE) << "builddir Changed" << buildDir; emit changed(); } void CMakePreferences::cacheUpdated() { const Path buildDir = CMake::currentBuildDir(m_project); updateCache(buildDir); qCDebug(CMAKE) << "cache updated for" << buildDir; } void CMakePreferences::createBuildDir() { CMakeBuildDirChooser bdCreator; bdCreator.setSourceFolder( m_srcFolder ); // NOTE: (on removing the trailing slashes) // Generally, we have no clue about how shall a trailing slash look in the current system. // Moreover, the slash may be a part of the filename. // It may be '/' or '\', so maybe should we rely on CMake::allBuildDirs() for returning well-formed pathes? QStringList used = CMake::allBuildDirs( m_project ); bdCreator.setAlreadyUsed(used); bdCreator.setCMakeBinary(Path(CMake::findExecutable())); if(bdCreator.exec()) { QString newbuilddir = bdCreator.buildFolder().toLocalFile(); m_prefsUi->buildDirs->addItem(newbuilddir); int buildDirCount = m_prefsUi->buildDirs->count(); int addedBuildDirIndex = buildDirCount - 1; m_prefsUi->buildDirs->setCurrentIndex(addedBuildDirIndex); m_prefsUi->removeBuildDir->setEnabled(true); // Initialize the kconfig items with the values from the dialog, this ensures the settings // end up in the config file once the changes are saved qCDebug(CMAKE) << "adding to cmake config: new builddir index" << addedBuildDirIndex; qCDebug(CMAKE) << "adding to cmake config: builddir path " << bdCreator.buildFolder(); qCDebug(CMAKE) << "adding to cmake config: installdir " << bdCreator.installPrefix(); qCDebug(CMAKE) << "adding to cmake config: extra args" << bdCreator.extraArguments(); qCDebug(CMAKE) << "adding to cmake config: build type " << bdCreator.buildType(); qCDebug(CMAKE) << "adding to cmake config: cmake binary " << bdCreator.cmakeBinary(); qCDebug(CMAKE) << "adding to cmake config: environment empty"; CMake::setBuildDirCount( m_project, buildDirCount ); CMake::setCurrentBuildDir( m_project, bdCreator.buildFolder() ); CMake::setCurrentInstallDir( m_project, bdCreator.installPrefix() ); CMake::setCurrentExtraArguments( m_project, bdCreator.extraArguments() ); CMake::setCurrentBuildType( m_project, bdCreator.buildType() ); CMake::setCurrentCMakeBinary( m_project, bdCreator.cmakeBinary() ); CMake::setCurrentEnvironment( m_project, QString() ); qCDebug(CMAKE) << "Emitting changed signal for cmake kcm"; emit changed(); } //TODO: Save it for next runs } void CMakePreferences::removeBuildDir() { int curr=m_prefsUi->buildDirs->currentIndex(); if(curr < 0) return; Path removedPath = CMake::currentBuildDir( m_project ); QString removed = removedPath.toLocalFile(); if(QDir(removed).exists()) { KMessageBox::ButtonCode ret = KMessageBox::warningYesNo(this, i18n("The %1 directory is about to be removed in KDevelop's list.\n" "Do you want KDevelop to remove it in the file system as well?", removed)); if(ret == KMessageBox::Yes) { bool correct = KIO::del(removedPath.toUrl())->exec(); if(!correct) KMessageBox::error(this, i18n("Could not remove: %1\n", removed)); } } qCDebug(CMAKE) << "removing from cmake config: using builddir " << curr; qCDebug(CMAKE) << "removing from cmake config: builddir path " << removedPath; qCDebug(CMAKE) << "removing from cmake config: installdir " << CMake::currentInstallDir( m_project ); qCDebug(CMAKE) << "removing from cmake config: extra args" << CMake::currentExtraArguments( m_project ); qCDebug(CMAKE) << "removing from cmake config: buildtype " << CMake::currentBuildType( m_project ); qCDebug(CMAKE) << "removing from cmake config: cmake binary " << CMake::currentCMakeBinary( m_project ); qCDebug(CMAKE) << "removing from cmake config: environment " << CMake::currentEnvironment( m_project ); CMake::removeBuildDirConfig(m_project); m_prefsUi->buildDirs->removeItem( curr ); // this triggers buildDirChanged() if(m_prefsUi->buildDirs->count()==0) m_prefsUi->removeBuildDir->setEnabled(false); emit changed(); } void CMakePreferences::configure() { IProjectBuilder *b=m_project->buildSystemManager()->builder(); KJob* job=b->configure(m_project); if( m_currentModel ) { QVariantMap map = m_currentModel->changedValues(); job->setProperty("extraCMakeCacheValues", map); connect(job, &KJob::finished, m_currentModel, &CMakeCacheModel::reset); } else { connect(job, &KJob::finished, this, &CMakePreferences::cacheUpdated); } connect(job, &KJob::finished, m_project, &IProject::reloadModel); ICore::self()->runController()->registerJob(job); } void CMakePreferences::showAdvanced(bool v) { qCDebug(CMAKE) << "toggle pressed: " << v; m_prefsUi->advancedBox->setHidden(!v); } QString CMakePreferences::name() const { return i18n("CMake"); } QString CMakePreferences::fullName() const { return i18n("Configure CMake settings"); } QIcon CMakePreferences::icon() const { return QIcon::fromTheme("cmake"); } diff --git a/projectmanagers/cmake/settings/cmakepreferences.h b/projectmanagers/cmake/settings/cmakepreferences.h index 8781827a0f..d653927293 100644 --- a/projectmanagers/cmake/settings/cmakepreferences.h +++ b/projectmanagers/cmake/settings/cmakepreferences.h @@ -1,75 +1,75 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEPREFERENCES_H #define CMAKEPREFERENCES_H #include "cmakecachemodel.h" #include #include class QItemSelection; class CMakeSettings; namespace Ui { class CMakeBuildSettings; } /** * @author Matt Rogers * @author Aleix Pol */ class CMakePreferences : public KDevelop::ConfigPage { Q_OBJECT public: - explicit CMakePreferences(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent = 0); + explicit CMakePreferences(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent = nullptr); ~CMakePreferences() override; QString name() const override; QString fullName() const override; QIcon icon() const override; void apply() override; void reset() override; void defaults() override; private slots: void listSelectionChanged ( const QModelIndex& current, const QModelIndex& ); void showInternal(int state); void cacheEdited(QStandardItem * ) { emit changed(); } void buildDirChanged(int index); void cacheUpdated(); void createBuildDir(); void removeBuildDir(); void showAdvanced(bool v); void configureCacheView(); private: void configure(); KDevelop::IProject* m_project; KDevelop::Path m_srcFolder; KDevelop::Path m_subprojFolder; void updateCache( const KDevelop::Path & ); Ui::CMakeBuildSettings* m_prefsUi; CMakeCacheModel* m_currentModel; }; #endif diff --git a/projectmanagers/cmake/testing/ctestfindjob.h b/projectmanagers/cmake/testing/ctestfindjob.h index 9d129142b2..258eea9e69 100644 --- a/projectmanagers/cmake/testing/ctestfindjob.h +++ b/projectmanagers/cmake/testing/ctestfindjob.h @@ -1,52 +1,52 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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. */ #ifndef CTESTFINDJOB_H #define CTESTFINDJOB_H #include #include namespace KDevelop { class IndexedString; class ReferencedTopDUContext; } class CTestSuite; class CTestFindJob : public KJob { Q_OBJECT public: - explicit CTestFindJob(CTestSuite* suite, QObject* parent = 0); + explicit CTestFindJob(CTestSuite* suite, QObject* parent = nullptr); void start() override; private slots: void findTestCases(); void updateReady(const KDevelop::IndexedString& document, const KDevelop::ReferencedTopDUContext& context); protected: bool doKill() override; private: CTestSuite* m_suite; QList m_pendingFiles; }; #endif // CTESTFINDJOB_H diff --git a/projectmanagers/cmake/testing/ctestrunjob.cpp b/projectmanagers/cmake/testing/ctestrunjob.cpp index df0c47aae9..a38eb6ef50 100644 --- a/projectmanagers/cmake/testing/ctestrunjob.cpp +++ b/projectmanagers/cmake/testing/ctestrunjob.cpp @@ -1,222 +1,222 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 "ctestrunjob.h" #include "ctestsuite.h" #include "qttestdelegate.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; CTestRunJob::CTestRunJob(CTestSuite* suite, const QStringList& cases, OutputJob::OutputJobVerbosity verbosity, bool expectFail, QObject* parent) : KJob(parent) , m_suite(suite) , m_cases(cases) -, m_job(0) -, m_outputJob(0) +, m_job(nullptr) +, m_outputJob(nullptr) , m_verbosity(verbosity) , m_expectFail(expectFail) { foreach (const QString& testCase, cases) { m_caseResults[testCase] = TestResult::NotRun; } setCapabilities(Killable); } KJob* createTestJob(QString launchModeId, QStringList arguments ) { LaunchConfigurationType* type = ICore::self()->runController()->launchConfigurationTypeForId( "Native Application" ); ILaunchMode* mode = ICore::self()->runController()->launchModeForId( launchModeId ); qCDebug(CMAKE) << "got mode and type:" << type << type->id() << mode << mode->id(); Q_ASSERT(type && mode); - ILauncher* launcher = 0; + ILauncher* launcher = nullptr; foreach (ILauncher *l, type->launchers()) { //qCDebug(CMAKE) << "avaliable launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } Q_ASSERT(launcher); - ILaunchConfiguration* ilaunch = 0; + ILaunchConfiguration* ilaunch = nullptr; QList launchConfigurations = ICore::self()->runController()->launchConfigurations(); foreach (ILaunchConfiguration *l, launchConfigurations) { if (l->type() == type && l->config().readEntry("ConfiguredByCTest", false)) { ilaunch = l; break; } } if (!ilaunch) { ilaunch = ICore::self()->runController()->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), - 0, //TODO add project + nullptr, //TODO add project i18n("CTest") ); ilaunch->config().writeEntry("ConfiguredByCTest", true); //qCDebug(CMAKE) << "created config, launching"; } else { //qCDebug(CMAKE) << "reusing generated config, launching"; } type->configureLaunchFromCmdLineArguments( ilaunch->config(), arguments ); return ICore::self()->runController()->execute(launchModeId, ilaunch); } void CTestRunJob::start() { // if (!m_suite->cases().isEmpty()) // { // TODO: Find a better way of determining whether QTestLib is used by this test // qCDebug(CMAKE) << "Setting a QtTestDelegate"; // setDelegate(new QtTestDelegate); // } // setStandardToolView(IOutputView::RunView); QStringList arguments = m_cases; if (m_cases.isEmpty() && !m_suite->arguments().isEmpty()) { arguments = m_suite->arguments(); } QStringList cases_selected = arguments; arguments.prepend(m_suite->executable().toLocalFile()); m_job = createTestJob("execute", arguments); if (ExecuteCompositeJob* cjob = qobject_cast(m_job)) { m_outputJob = cjob->findChild(); Q_ASSERT(m_outputJob); m_outputJob->setVerbosity(m_verbosity); QString testName = arguments.value(0).split('/').last(); QString title; if (cases_selected.count() == 1) title = i18nc("running test %1, %2 test case", "CTest %1: %2", testName, cases_selected.value(0)); else title = i18ncp("running test %1, %2 number of test cases", "CTest %2 (%1)", "CTest %2 (%1)", cases_selected.count(), testName); m_outputJob->setTitle(title); connect(m_outputJob->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); } connect(m_job, SIGNAL(finished(KJob*)), SLOT(processFinished(KJob*))); ICore::self()->testController()->notifyTestRunStarted(m_suite, cases_selected); } bool CTestRunJob::doKill() { if (m_job) { m_job->kill(); } return true; } void CTestRunJob::processFinished(KJob* job) { TestResult result; result.testCaseResults = m_caseResults; if (job->error() == OutputJob::FailedShownError) { result.suiteResult = TestResult::Failed; } else if (job->error() == KJob::NoError) { result.suiteResult = TestResult::Passed; } else { result.suiteResult = TestResult::Error; } // in case the job was killed, mark this job as killed as well if (job->error() == KJob::KilledJobError) { setError(KJob::KilledJobError); setErrorText("Child job was killed."); } qCDebug(CMAKE) << result.suiteResult << result.testCaseResults; ICore::self()->testController()->notifyTestRunFinished(m_suite, result); emitResult(); } void CTestRunJob::rowsInserted(const QModelIndex &parent, int startRow, int endRow) { // This regular expresion matches the name of the testcase (whatever between "::" and "(", indeed ) // For example, from: // PASS : ExpTest::testExp(sum) // matches "testExp" static QRegExp caseRx("::(.*)\\(", Qt::CaseSensitive, QRegExp::RegExp2); for (int row = startRow; row <= endRow; ++row) { QString line = m_outputJob->model()->data(m_outputJob->model()->index(row, 0, parent), Qt::DisplayRole).toString(); QString testCase; if (caseRx.indexIn(line) >= 0) { testCase = caseRx.cap(1); } TestResult::TestCaseResult prevResult = m_caseResults.value(testCase, TestResult::NotRun); if (prevResult == TestResult::Passed || prevResult == TestResult::NotRun) { TestResult::TestCaseResult result = TestResult::NotRun; if (line.startsWith("PASS :")) { result = m_expectFail ? TestResult::UnexpectedPass : TestResult::Passed; } else if (line.startsWith("FAIL! :")) { result = m_expectFail ? TestResult::ExpectedFail : TestResult::Failed; } else if (line.startsWith("XFAIL :")) { result = TestResult::ExpectedFail; } else if (line.startsWith("XPASS :")) { result = TestResult::UnexpectedPass; } else if (line.startsWith("SKIP :")) { result = TestResult::Skipped; } if (result != TestResult::NotRun) { m_caseResults[testCase] = result; } } } } diff --git a/projectmanagers/cmake/testing/ctestrunjob.h b/projectmanagers/cmake/testing/ctestrunjob.h index d1fd7d1c51..5d36bbd375 100644 --- a/projectmanagers/cmake/testing/ctestrunjob.h +++ b/projectmanagers/cmake/testing/ctestrunjob.h @@ -1,59 +1,59 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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. */ #ifndef CTESTRUNJOB_H #define CTESTRUNJOB_H #include #include #include class CTestSuite; namespace KDevelop { class ILaunchConfiguration; class ProcessLineMaker; } class CTestRunJob : public KJob { Q_OBJECT public: - CTestRunJob(CTestSuite* suite, const QStringList& cases, KDevelop::OutputJob::OutputJobVerbosity verbosity, bool expectFail, QObject* parent = 0); + CTestRunJob(CTestSuite* suite, const QStringList& cases, KDevelop::OutputJob::OutputJobVerbosity verbosity, bool expectFail, QObject* parent = nullptr); void start() override; protected: bool doKill() override; private slots: void processFinished(KJob* job); void rowsInserted(const QModelIndex &parent, int startRow, int endRow); private: CTestSuite* m_suite; QStringList m_cases; QHash m_caseResults; KJob* m_job; KDevelop::OutputJob* m_outputJob; KDevelop::OutputJob::OutputJobVerbosity m_verbosity; bool m_expectFail; }; #endif // CTESTRUNJOB_H diff --git a/projectmanagers/cmake/testing/ctestsuite.cpp b/projectmanagers/cmake/testing/ctestsuite.cpp index fccd55408c..fb0a6b5559 100644 --- a/projectmanagers/cmake/testing/ctestsuite.cpp +++ b/projectmanagers/cmake/testing/ctestsuite.cpp @@ -1,198 +1,198 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 "ctestsuite.h" #include "ctestrunjob.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; CTestSuite::CTestSuite(const QString& name, const KDevelop::Path &executable, const QList& files, IProject* project, const QStringList& args, bool expectFail): m_executable(executable), m_name(name), m_args(args), m_files(files), m_project(project), m_expectFail(expectFail) { Q_ASSERT(project); qCDebug(CMAKE) << m_name << m_executable << m_project->name(); } CTestSuite::~CTestSuite() { } void CTestSuite::loadDeclarations(const IndexedString& document, const KDevelop::ReferencedTopDUContext& ref) { DUChainReadLocker locker(DUChain::lock()); TopDUContext* topContext = DUChainUtils::contentContextFromProxyContext(ref.data()); if (!topContext) { qCDebug(CMAKE) << "No top context in" << document.str(); return; } - Declaration* testClass = 0; + Declaration* testClass = nullptr; Identifier testCaseIdentifier("tc"); foreach (Declaration* declaration, topContext->findLocalDeclarations(Identifier("main"))) { if (declaration->isDefinition()) { qCDebug(CMAKE) << "Found a definition for a function 'main()' "; FunctionDefinition* def = dynamic_cast(declaration); DUContext* main = def->internalContext(); foreach (Declaration* mainDeclaration, main->localDeclarations(topContext)) { if (mainDeclaration->identifier() == testCaseIdentifier) { qCDebug(CMAKE) << "Found tc declaration in main:" << mainDeclaration->identifier().toString(); qCDebug(CMAKE) << "Its type is" << mainDeclaration->abstractType()->toString(); if (StructureType::Ptr type = mainDeclaration->abstractType().cast()) { testClass = type->declaration(topContext); } } } } } if (!testClass || !testClass->internalContext()) { qCDebug(CMAKE) << "No test class found or internal context missing in " << document.str(); return; } if (!m_suiteDeclaration.data()) { m_suiteDeclaration = IndexedDeclaration(testClass); } foreach (Declaration* decl, testClass->internalContext()->localDeclarations(topContext)) { qCDebug(CMAKE) << "Found declaration" << decl->toString() << decl->identifier().identifier().byteArray(); if (ClassFunctionDeclaration* function = dynamic_cast(decl)) { if (function->accessPolicy() == Declaration::Private && function->isSlot()) { QString name = function->qualifiedIdentifier().last().toString(); qCDebug(CMAKE) << "Found private slot in test" << name; if (name.endsWith("_data")) { continue; } if (name != "initTestCase" && name != "cleanupTestCase" && name != "init" && name != "cleanup") { m_cases << name; } qCDebug(CMAKE) << "Found test case function declaration" << function->identifier().toString(); FunctionDefinition* def = FunctionDefinition::definition(decl); m_declarations[name] = def ? IndexedDeclaration(def) : IndexedDeclaration(function); } } } } KJob* CTestSuite::launchCase(const QString& testCase, TestJobVerbosity verbosity) { return launchCases(QStringList() << testCase, verbosity); } KJob* CTestSuite::launchCases(const QStringList& testCases, ITestSuite::TestJobVerbosity verbosity) { qCDebug(CMAKE) << "Launching test run" << m_name << "with cases" << testCases; OutputJob::OutputJobVerbosity outputVerbosity = (verbosity == Verbose) ? OutputJob::Verbose : OutputJob::Silent; return new CTestRunJob(this, testCases, outputVerbosity, m_expectFail); } KJob* CTestSuite::launchAllCases(TestJobVerbosity verbosity) { return launchCases(cases(), verbosity); } KDevelop::Path CTestSuite::executable() const { return m_executable; } QStringList CTestSuite::cases() const { return m_cases; } QString CTestSuite::name() const { return m_name; } KDevelop::IProject* CTestSuite::project() const { return m_project; } QStringList CTestSuite::arguments() const { return m_args; } IndexedDeclaration CTestSuite::declaration() const { return m_suiteDeclaration; } IndexedDeclaration CTestSuite::caseDeclaration(const QString& testCase) const { - return m_declarations.value(testCase, IndexedDeclaration(0)); + return m_declarations.value(testCase, IndexedDeclaration(nullptr)); } void CTestSuite::setTestCases(const QStringList& cases) { m_cases = cases; } QList CTestSuite::sourceFiles() const { return m_files; } diff --git a/projectmanagers/cmake/testing/qttestdelegate.h b/projectmanagers/cmake/testing/qttestdelegate.h index 44edef8f63..408d19ec32 100644 --- a/projectmanagers/cmake/testing/qttestdelegate.h +++ b/projectmanagers/cmake/testing/qttestdelegate.h @@ -1,46 +1,46 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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. */ #ifndef QTTESTDELEGATE_H #define QTTESTDELEGATE_H #include #include class QtTestDelegate : public QItemDelegate { Q_OBJECT public: - explicit QtTestDelegate(QObject* parent = 0); + explicit QtTestDelegate(QObject* parent = nullptr); ~QtTestDelegate() override; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; private: void highlight(QStyleOptionViewItem& option, const KStatefulBrush& brush, bool bold = true) const; KStatefulBrush passBrush; KStatefulBrush failBrush; KStatefulBrush xFailBrush; KStatefulBrush xPassBrush; KStatefulBrush debugBrush; }; #endif // QTTESTDELEGATE_H diff --git a/projectmanagers/cmake/tests/kdevprojectopen.h b/projectmanagers/cmake/tests/kdevprojectopen.h index 719466b4cf..687b1edc46 100644 --- a/projectmanagers/cmake/tests/kdevprojectopen.h +++ b/projectmanagers/cmake/tests/kdevprojectopen.h @@ -1,44 +1,44 @@ /* KDevelop CMake Support * * Copyright 2013 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef KDEVPROJECTOPEN_H #define KDEVPROJECTOPEN_H #include #include namespace KDevelop { class IProject; } class KDevProjectOpen : public QObject { Q_OBJECT public: - explicit KDevProjectOpen(QObject* parent = 0); + explicit KDevProjectOpen(QObject* parent = nullptr); void openProject(const QUrl& path); void cleanup(); private slots: void projectDone(KDevelop::IProject*); private: int m_toOpen; }; #endif diff --git a/projectmanagers/custom-buildsystem/configwidget.h b/projectmanagers/custom-buildsystem/configwidget.h index ec81b70c27..3b390230f2 100644 --- a/projectmanagers/custom-buildsystem/configwidget.h +++ b/projectmanagers/custom-buildsystem/configwidget.h @@ -1,63 +1,63 @@ /************************************************************************ * 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 . * ************************************************************************/ #ifndef CONFIGWIDGET_H #define CONFIGWIDGET_H #include #include "custombuildsystemconfig.h" namespace Ui { class ConfigWidget; } namespace KDevelop { class IProject; } class ConfigWidget : public QWidget { Q_OBJECT public: - ConfigWidget( QWidget* parent = 0 ); + ConfigWidget( QWidget* parent = nullptr ); void loadConfig( CustomBuildSystemConfig cfg ); CustomBuildSystemConfig config() const; void clear(); signals: void changed(); private slots: void changeAction( int ); void toggleActionEnablement( bool ); void actionArgumentsEdited( const QString& ); void actionEnvironmentChanged(const QString&); void actionExecutableChanged( const QUrl& ); void actionExecutableChanged( const QString& ); private: template void applyChange(F toolChanger); Ui::ConfigWidget* ui; QVector m_tools; void setTool( const CustomBuildSystemTool& tool ); }; #endif diff --git a/projectmanagers/custom-buildsystem/custombuildsystemplugin.cpp b/projectmanagers/custom-buildsystem/custombuildsystemplugin.cpp index a445ce8024..538600dc84 100644 --- a/projectmanagers/custom-buildsystem/custombuildsystemplugin.cpp +++ b/projectmanagers/custom-buildsystem/custombuildsystemplugin.cpp @@ -1,201 +1,201 @@ /************************************************************************ * 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 "custombuildsystemplugin.h" #include #include #include #include #include #include #include #include #include "configconstants.h" #include "kcm_custombuildsystem.h" #include "config.h" #include "custombuildjob.h" using KDevelop::ProjectTargetItem; using KDevelop::ProjectFolderItem; using KDevelop::ProjectBuildFolderItem; using KDevelop::ProjectBaseItem; using KDevelop::ProjectFileItem; using KDevelop::IPlugin; using KDevelop::ICore; using KDevelop::IOutputView; using KDevelop::IProjectFileManager; using KDevelop::IProjectBuilder; using KDevelop::IProject; using KDevelop::Path; K_PLUGIN_FACTORY_WITH_JSON(CustomBuildSystemFactory, "kdevcustombuildsystem.json", registerPlugin(); ) CustomBuildSystem::CustomBuildSystem( QObject *parent, const QVariantList & ) : AbstractFileManagerPlugin( "kdevcustombuildsystem", parent ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectBuilder ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBuildSystemManager ) } CustomBuildSystem::~CustomBuildSystem() { } bool CustomBuildSystem::addFilesToTarget( const QList&, ProjectTargetItem* ) { return false; } bool CustomBuildSystem::hasBuildInfo( ProjectBaseItem* ) const { return false; } KJob* CustomBuildSystem::build( ProjectBaseItem* dom ) { return new CustomBuildJob( this, dom, CustomBuildSystemTool::Build ); } Path CustomBuildSystem::buildDirectory( ProjectBaseItem* item ) const { Path p; if( item->folder() ) { p = item->path(); } else { ProjectBaseItem* parent = item; while( !parent->folder() ) { parent = parent->parent(); } p = parent->path(); } const QString relative = item->project()->path().relativePath(p); KConfigGroup grp = configuration( item->project() ); if(!grp.isValid()) { return Path(); } Path builddir(grp.readEntry( ConfigConstants::buildDirKey, QUrl() )); if(!builddir.isValid() ) // set builddir to default if project contains a buildDirKey that does not have a value { builddir = item->project()->path(); } builddir.addPath( relative ); return builddir; } IProjectBuilder* CustomBuildSystem::builder() const { return const_cast(dynamic_cast(this)); } KJob* CustomBuildSystem::clean( ProjectBaseItem* dom ) { return new CustomBuildJob( this, dom, CustomBuildSystemTool::Clean ); } KJob* CustomBuildSystem::configure( IProject* project ) { return new CustomBuildJob( this, project->projectItem(), CustomBuildSystemTool::Configure ); } ProjectTargetItem* CustomBuildSystem::createTarget( const QString&, ProjectFolderItem* ) { - return 0; + return nullptr; } QHash CustomBuildSystem::defines( ProjectBaseItem* ) const { return {}; } IProjectFileManager::Features CustomBuildSystem::features() const { return IProjectFileManager::Files | IProjectFileManager::Folders; } ProjectFolderItem* CustomBuildSystem::createFolderItem( IProject* project, const Path& path, ProjectBaseItem* parent ) { return new ProjectBuildFolderItem( project, path, parent ); } Path::List CustomBuildSystem::includeDirectories( ProjectBaseItem* ) const { return {}; } Path::List CustomBuildSystem::frameworkDirectories( ProjectBaseItem* ) const { return {}; } KJob* CustomBuildSystem::install( KDevelop::ProjectBaseItem* item, const QUrl &installPrefix ) { auto job = new CustomBuildJob( this, item, CustomBuildSystemTool::Install ); job->setInstallPrefix(installPrefix); return job; } KJob* CustomBuildSystem::prune( IProject* project ) { return new CustomBuildJob( this, project->projectItem(), CustomBuildSystemTool::Prune ); } bool CustomBuildSystem::removeFilesFromTargets( const QList& ) { return false; } bool CustomBuildSystem::removeTarget( ProjectTargetItem* ) { return false; } QList CustomBuildSystem::targets( ProjectFolderItem* ) const { return QList(); } KConfigGroup CustomBuildSystem::configuration( IProject* project ) const { KConfigGroup grp = project->projectConfiguration()->group( ConfigConstants::customBuildSystemGroup ); if(grp.isValid() && grp.hasKey(ConfigConstants::currentConfigKey)) return grp.group( grp.readEntry( ConfigConstants::currentConfigKey ) ); else return KConfigGroup(); } int CustomBuildSystem::perProjectConfigPages() const { return 1; } KDevelop::ConfigPage* CustomBuildSystem::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CustomBuildSystemKCModule(this, options, parent); } return nullptr; } #include "custombuildsystemplugin.moc" diff --git a/projectmanagers/custom-buildsystem/tests/test_custombuildsystemplugin.cpp b/projectmanagers/custom-buildsystem/tests/test_custombuildsystemplugin.cpp index 4988bee815..711a1d6ebf 100644 --- a/projectmanagers/custom-buildsystem/tests/test_custombuildsystemplugin.cpp +++ b/projectmanagers/custom-buildsystem/tests/test_custombuildsystemplugin.cpp @@ -1,113 +1,113 @@ /************************************************************************ * 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 "test_custombuildsystemplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "testconfig.h" using KDevelop::Core; using KDevelop::ICore; using KDevelop::IProject; using KDevelop::TestCore; using KDevelop::AutoTestShell; using KDevelop::KDevSignalSpy; using KDevelop::Path; QTEST_MAIN(TestCustomBuildSystemPlugin) void TestCustomBuildSystemPlugin::cleanupTestCase() { TestCore::shutdown(); } void TestCustomBuildSystemPlugin::initTestCase() { AutoTestShell::init({"kdevcustombuildsystem"}); TestCore::initialize(); } void TestCustomBuildSystemPlugin::loadSimpleProject() { QUrl projecturl = QUrl::fromLocalFile( PROJECTS_SOURCE_DIR"/simpleproject/simpleproject.kdev4" ); KDevSignalSpy* projectSpy = new KDevSignalSpy( ICore::self()->projectController(), SIGNAL( projectOpened( KDevelop::IProject* ) ) ); ICore::self()->projectController()->openProject( projecturl ); // Wait for the project to be opened QVERIFY(projectSpy->wait(10000)); IProject* project = ICore::self()->projectController()->findProjectByName( "SimpleProject" ); QVERIFY( project ); QCOMPARE( project->buildSystemManager()->buildDirectory( project->projectItem() ), Path( "file:///home/andreas/projects/testcustom/build/" ) ); } void TestCustomBuildSystemPlugin::buildDirProject() { QUrl projecturl = QUrl::fromLocalFile( PROJECTS_SOURCE_DIR"/builddirproject/builddirproject.kdev4" ); KDevSignalSpy* projectSpy = new KDevSignalSpy( ICore::self()->projectController(), SIGNAL( projectOpened( KDevelop::IProject* ) ) ); ICore::self()->projectController()->openProject( projecturl ); // Wait for the project to be opened QVERIFY(projectSpy->wait(10000)); IProject* project = ICore::self()->projectController()->findProjectByName( "BuilddirProject" ); QVERIFY( project ); Path currentBuilddir = project->buildSystemManager()->buildDirectory( project->projectItem() ); QCOMPARE( currentBuilddir, Path( projecturl ).parent() ); } void TestCustomBuildSystemPlugin::loadMultiPathProject() { QUrl projecturl = QUrl::fromLocalFile( PROJECTS_SOURCE_DIR"/multipathproject/multipathproject.kdev4" ); KDevSignalSpy* projectSpy = new KDevSignalSpy( ICore::self()->projectController(), SIGNAL( projectOpened( KDevelop::IProject* ) ) ); ICore::self()->projectController()->openProject( projecturl ); // Wait for the project to be opened QVERIFY(projectSpy->wait(10000)); IProject* project = ICore::self()->projectController()->findProjectByName( "MultiPathProject" ); QVERIFY( project ); - KDevelop::ProjectBaseItem* mainfile = 0; + KDevelop::ProjectBaseItem* mainfile = nullptr; for (const auto& file: project->fileSet() ) { for (auto i: project->filesForPath(file)) { if( i->text() == "main.cpp" ) { mainfile = i; break; } } } QVERIFY(mainfile); QCOMPARE( project->buildSystemManager()->buildDirectory( mainfile ), Path( "file:///home/andreas/projects/testcustom/build2/src" ) ); } diff --git a/projectmanagers/custommake/custommakemanager.cpp b/projectmanagers/custommake/custommakemanager.cpp index 682099c64f..8e49004e15 100644 --- a/projectmanagers/custommake/custommakemanager.cpp +++ b/projectmanagers/custommake/custommakemanager.cpp @@ -1,334 +1,334 @@ /* KDevelop Custom Makefile Support * * Copyright 2007 Dukju Ahn * Copyright 2011 Milian Wolff * * 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 "custommakemanager.h" #include "custommakemodelitems.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(CUSTOMMAKE) Q_LOGGING_CATEGORY(CUSTOMMAKE, "kdevelop.projectmanagers.custommake") #include #include #include #include "../../languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h" #include "makefileresolver/makefileresolver.h" using namespace KDevelop; class CustomMakeProvider : public IDefinesAndIncludesManager::BackgroundProvider { public: CustomMakeProvider(CustomMakeManager* manager) : m_customMakeManager(manager) , m_resolver(new MakeFileResolver()) {} // NOTE: Fixes build failures for GCC versions <4.8. // cf. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53613 ~CustomMakeProvider() Q_DECL_NOEXCEPT override; QHash< QString, QString > definesInBackground(const QString&) const override { return {}; } Path::List resolvePathInBackground(const QString& path, const bool isFrameworks) const { { QReadLocker lock(&m_lock); bool inProject = std::any_of(m_customMakeManager->m_projectPaths.constBegin(), m_customMakeManager->m_projectPaths.constEnd(), [&path](const QString& projectPath) { return path.startsWith(projectPath); } ); if (!inProject) { return {}; } } if (isFrameworks) { return m_resolver->resolveIncludePath(path).frameworkDirectories; } else { return m_resolver->resolveIncludePath(path).paths; } } Path::List includesInBackground(const QString& path) const override { return resolvePathInBackground(path, false); } Path::List frameworkDirectoriesInBackground(const QString& path) const override { return resolvePathInBackground(path, true); } IDefinesAndIncludesManager::Type type() const override { return IDefinesAndIncludesManager::ProjectSpecific; } CustomMakeManager* m_customMakeManager; QScopedPointer m_resolver; mutable QReadWriteLock m_lock; }; // NOTE: Fixes build failures for GCC versions <4.8. // See above. CustomMakeProvider::~CustomMakeProvider() Q_DECL_NOEXCEPT = default; K_PLUGIN_FACTORY_WITH_JSON(CustomMakeSupportFactory, "kdevcustommakemanager.json", registerPlugin(); ) CustomMakeManager::CustomMakeManager( QObject *parent, const QVariantList& args ) : KDevelop::AbstractFileManagerPlugin( "kdevcustommakemanager", parent ) , m_builder( nullptr ) , m_provider(new CustomMakeProvider(this)) { Q_UNUSED(args) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBuildSystemManager ) setXMLFile( "kdevcustommakemanager.rc" ); // TODO use CustomMakeBuilder IPlugin* i = core()->pluginController()->pluginForExtension( "org.kdevelop.IMakeBuilder" ); Q_ASSERT(i); m_builder = i->extension(); Q_ASSERT(m_builder); connect(this, &CustomMakeManager::reloadedFileItem, this, &CustomMakeManager::reloadMakefile); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CustomMakeManager::projectClosing); IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data()); } CustomMakeManager::~CustomMakeManager() { } IProjectBuilder* CustomMakeManager::builder() const { Q_ASSERT(m_builder); return m_builder; } Path::List CustomMakeManager::includeDirectories(KDevelop::ProjectBaseItem*) const { return Path::List(); } Path::List CustomMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem*) const { return Path::List(); } QHash CustomMakeManager::defines(KDevelop::ProjectBaseItem*) const { return QHash(); } ProjectTargetItem* CustomMakeManager::createTarget(const QString& target, KDevelop::ProjectFolderItem *parent) { Q_UNUSED(target) Q_UNUSED(parent) - return NULL; + return nullptr; } bool CustomMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &files, ProjectTargetItem* parent) { Q_UNUSED( files ) Q_UNUSED( parent ) return false; } bool CustomMakeManager::removeTarget(KDevelop::ProjectTargetItem *target) { Q_UNUSED( target ) return false; } bool CustomMakeManager::removeFilesFromTargets(const QList< ProjectFileItem* > &targetFiles) { Q_UNUSED( targetFiles ) return false; } bool CustomMakeManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const { Q_UNUSED(item); return false; } Path CustomMakeManager::buildDirectory(KDevelop::ProjectBaseItem* item) const { ProjectFolderItem *fi=dynamic_cast(item); for(; !fi && item; ) { item=item->parent(); fi=dynamic_cast(item); } if(!fi) { return item->project()->path(); } return fi->path(); } QList CustomMakeManager::targets(KDevelop::ProjectFolderItem*) const { QList ret; return ret; } static bool isMakefile(const QString& fileName) { return ( fileName == QLatin1String("Makefile") || fileName == QLatin1String("makefile") || fileName == QLatin1String("GNUmakefile") || fileName == QLatin1String("BSDmakefile") ); } void CustomMakeManager::createTargetItems(IProject* project, const Path& path, ProjectBaseItem* parent) { Q_ASSERT(isMakefile(path.lastPathSegment())); foreach(const QString& target, parseCustomMakeFile( path )) { if (!isValid(Path(parent->path(), target), false, project)) { continue; } new CustomMakeTargetItem( project, target, parent ); } } ProjectFileItem* CustomMakeManager::createFileItem(IProject* project, const Path& path, ProjectBaseItem* parent) { ProjectFileItem* item = new ProjectFileItem(project, path, parent); if (isMakefile(path.lastPathSegment())){ createTargetItems(project, path, parent); } return item; } void CustomMakeManager::reloadMakefile(ProjectFileItem* file) { if( !isMakefile(file->path().lastPathSegment())){ return; } ProjectBaseItem* parent = file->parent(); // remove the items that are Makefile targets foreach(ProjectBaseItem* item, parent->children()){ if (item->target()){ delete item; } } // Recreate the targets. createTargetItems(parent->project(), file->path(), parent); } ProjectFolderItem* CustomMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO more faster algorithm. should determine whether this directory // contains makefile or not. return new KDevelop::ProjectBuildFolderItem( project, path, parent ); } KDevelop::ProjectFolderItem* CustomMakeManager::import(KDevelop::IProject *project) { if( project->path().isRemote() ) { //FIXME turn this into a real warning qCWarning(CUSTOMMAKE) << project->path() << "not a local file. Custom make support doesn't handle remote projects"; - return 0; + return nullptr; } { QWriteLocker lock(&m_provider->m_lock); m_projectPaths.insert(project->path().path()); } return AbstractFileManagerPlugin::import( project ); } ///////////////////////////////////////////////////////////////////////////// // private slots ///TODO: move to background thread, probably best would be to use a proper ParseJob QStringList CustomMakeManager::parseCustomMakeFile( const Path &makefile ) { if( !makefile.isValid() ) return QStringList(); QStringList ret; // the list of targets QFile f( makefile.toLocalFile() ); if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) ) { qCDebug(CUSTOMMAKE) << "could not open" << makefile; return ret; } QRegExp targetRe( "^ *([^\\t$.#]\\S+) *:?:(?!=).*$" ); targetRe.setMinimal( true ); QString str; QTextStream stream( &f ); while ( !stream.atEnd() ) { str = stream.readLine(); if ( targetRe.indexIn( str ) != -1 ) { QString tmpTarget = targetRe.cap( 1 ).simplified(); if ( ! ret.contains( tmpTarget ) ) ret.append( tmpTarget ); } } f.close(); return ret; } void CustomMakeManager::projectClosing(IProject* project) { QWriteLocker lock(&m_provider->m_lock); m_projectPaths.remove(project->path().path()); } void CustomMakeManager::unload() { IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data()); } #include "custommakemanager.moc" diff --git a/projectmanagers/custommake/custommakemanager.h b/projectmanagers/custommake/custommakemanager.h index 1648b0111f..19eeedf26c 100644 --- a/projectmanagers/custommake/custommakemanager.h +++ b/projectmanagers/custommake/custommakemanager.h @@ -1,144 +1,144 @@ /* KDevelop Custom Makefile Support * * Copyright 2007 Dukju Ahn * Copyright 2011 Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. */ #ifndef CUSTOMMAKEMANAGER_H #define CUSTOMMAKEMANAGER_H #include #include #include #include class IMakeBuilder; class CustomMakeProvider; class CustomMakeManager : public KDevelop::AbstractFileManagerPlugin, public KDevelop::IBuildSystemManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) public: - explicit CustomMakeManager( QObject *parent = NULL, const QVariantList& args = QVariantList() ); + explicit CustomMakeManager( QObject *parent = nullptr, const QVariantList& args = QVariantList() ); ~CustomMakeManager() override; Features features() const override { return Features(Folders | Targets | Files); } KDevelop::ProjectFolderItem* import(KDevelop::IProject* project) override; /** * Provide access to the builder */ KDevelop::IProjectBuilder* builder() const override; /** * Provide a list of include directories. */ KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem*) const override; /** * Provide a list of framework directories. */ KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem*) const override; /** * Provide a list of files that contain the preprocessor defines for the * project */ QHash defines(KDevelop::ProjectBaseItem*) const override; /** * Create a new target * * Creates the target specified by @p target to the folder @p parent and * modifies the underlying build system if needed */ KDevelop::ProjectTargetItem* createTarget(const QString& target, KDevelop::ProjectFolderItem *parent) override; /** * Add a file to a target * * Adds the file specified by @p file to the target @p parent and modifies * the underlying build system if needed. */ bool addFilesToTarget(const QList &files, KDevelop::ProjectTargetItem *parent) override; /** * Remove a target * * Removes the target specified by @p target and * modifies the underlying build system if needed. */ bool removeTarget(KDevelop::ProjectTargetItem *target) override; /** * Remove a file from a target * * Removes the file specified by @p file from the folder @p parent and * modifies the underlying build system if needed. The file is not removed * from the folder it is in */ bool removeFilesFromTargets(const QList&) override; /** * Test if @p item has any includes or defines from this BSM */ bool hasBuildInfo(KDevelop::ProjectBaseItem* item) const override; /** * Get the toplevel build directory for the project */ KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; /** * Get a list of all the targets in this project * * The list returned by this function should be checked to verify it is not * empty before using it * * @return The list of targets for this project * @todo implement */ QList targets(KDevelop::ProjectFolderItem*) const override; protected: KDevelop::ProjectFileItem* createFileItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent) override; KDevelop::ProjectFolderItem* createFolderItem(KDevelop::IProject* project, const KDevelop::Path& path, - KDevelop::ProjectBaseItem* parent = 0) override; + KDevelop::ProjectBaseItem* parent = nullptr) override; void unload() override; private slots: void reloadMakefile(KDevelop::ProjectFileItem *item); void projectClosing(KDevelop::IProject*); private: /** * Initialize targets by reading Makefile in @arg dir * @return Target lists in Makefile at @arg dir. */ QStringList parseCustomMakeFile( const KDevelop::Path &makefile ); void createTargetItems(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent); private: IMakeBuilder *m_builder; QScopedPointer m_provider; QSet m_projectPaths; friend class CustomMakeProvider; }; #endif diff --git a/providers/kdeprovider/kdeprojectsmodel.h b/providers/kdeprovider/kdeprojectsmodel.h index 35a9766909..75b9e20d56 100644 --- a/providers/kdeprovider/kdeprojectsmodel.h +++ b/providers/kdeprovider/kdeprojectsmodel.h @@ -1,68 +1,68 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEPROJECTSMODEL_H #define KDEPROJECTSMODEL_H #include namespace KDevelop { class VcsLocation; } struct Source { enum SourceType { Project, Module }; Source() {} Source(const SourceType & aType, const QString& anIcon, const QString& aName, const QPair< QString, QString >& aUrl); SourceType type = Project; QString name; QString icon; QString identifier; /** urls for protocol */ QVariantMap m_urls; }; class SourceItem : public QStandardItem { public: SourceItem(const Source& s); QVariant data(int role = Qt::UserRole + 1) const override; private: Source m_s; }; class KDEProjectsModel : public QStandardItemModel { Q_OBJECT public: enum Role { VcsLocationRole = Qt::UserRole+1, IdentifierRole }; - explicit KDEProjectsModel(QObject* parent = 0); + explicit KDEProjectsModel(QObject* parent = nullptr); }; #endif // KDEPROJECTSMODEL_H diff --git a/providers/kdeprovider/kdeproviderwidget.cpp b/providers/kdeprovider/kdeproviderwidget.cpp index 6bdecd5a95..48d65fa4f2 100644 --- a/providers/kdeprovider/kdeproviderwidget.cpp +++ b/providers/kdeprovider/kdeproviderwidget.cpp @@ -1,135 +1,135 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * 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 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 "kdeproviderwidget.h" #include #include #include #include #include #include #include #include "ui_kdeconfig.h" #include "kdeconfig.h" #include #include "kdeprojectsmodel.h" #include "kdeprojectsreader.h" #include #include #include #include using namespace KDevelop; KDEProviderWidget::KDEProviderWidget(QWidget* parent) : IProjectProviderWidget(parent) { setLayout(new QVBoxLayout()); m_projects = new QListView(this); QHBoxLayout* topLayout = new QHBoxLayout(this); KFilterProxySearchLine* filterLine = new KFilterProxySearchLine(this); KDEProjectsModel* model = new KDEProjectsModel(this); KDEProjectsReader* reader = new KDEProjectsReader(model, model); connect(reader, &KDEProjectsReader::downloadDone, reader, &KDEProjectsReader::deleteLater); connect(m_projects, &QListView::clicked, this, &KDEProviderWidget::projectIndexChanged); topLayout->addWidget(filterLine); QPushButton* settings=new QPushButton(QIcon::fromTheme("configure"), i18n("Settings"), this); settings->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); connect(settings, &QPushButton::clicked, this, &KDEProviderWidget::showSettings); topLayout->addWidget(settings); layout()->addItem(topLayout); layout()->addWidget(m_projects); QSortFilterProxyModel* proxyModel = new QSortFilterProxyModel(this); proxyModel->setSourceModel(model); proxyModel->setDynamicSortFilter(true); proxyModel->sort(0); proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); m_projects->setModel(proxyModel); m_projects->setEditTriggers(QAbstractItemView::NoEditTriggers); filterLine->setProxy(proxyModel); } VcsLocation extractLocation(const QModelIndex& pos) { QString gitUrl=KDEProviderSettings::self()->gitProtocol(); if(gitUrl=="kde:") { return VcsLocation(QUrl("kde:"+pos.data(KDEProjectsModel::IdentifierRole).toString())); } else { QMap urls = pos.data(KDEProjectsModel::VcsLocationRole).toMap(); return VcsLocation(urls[gitUrl].toUrl()); } } VcsJob* KDEProviderWidget::createWorkingCopy(const QUrl &destinationDirectory) { QModelIndex pos = m_projects->currentIndex(); if(!pos.isValid()) - return 0; + return nullptr; IPlugin* plugin = ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IBasicVersionControl", "kdevgit"); if (!plugin) { - KMessageBox::error(0, i18n("The Git plugin could not be loaded which is required to download a KDE project."), i18n("KDE Provider Error")); - return 0; + KMessageBox::error(nullptr, i18n("The Git plugin could not be loaded which is required to download a KDE project."), i18n("KDE Provider Error")); + return nullptr; } IBasicVersionControl* vcIface = plugin->extension(); VcsJob* ret = vcIface->createWorkingCopy(extractLocation(pos), destinationDirectory); return ret; } bool KDEProviderWidget::isCorrect() const { return m_projects->currentIndex().isValid(); } void KDEProviderWidget::showSettings() { KConfigDialog* dialog = new KConfigDialog(this, "settings", KDEProviderSettings::self()); dialog->setFaceType(KPageDialog::Auto); QWidget* page = new QWidget(dialog); Ui::KDEConfig configUi; configUi.setupUi(page); configUi.kcfg_gitProtocol->setProperty("kcfg_property", QByteArray("currentText")); int idx = configUi.kcfg_gitProtocol->findText(KDEProviderSettings::self()->gitProtocol()); if(idx>=0) { configUi.kcfg_gitProtocol->setCurrentIndex(idx); } // TODO port to KF5 // dialog->button(KDialog::Default)->setVisible(false); dialog->addPage(page, i18n("General") ); dialog->show(); } void KDEProviderWidget::projectIndexChanged(const QModelIndex& currentIndex) { if (currentIndex.isValid()) { QString currentProjectName = currentIndex.data(Qt::DisplayRole).toString(); emit changed(currentProjectName); } } diff --git a/providers/kdeprovider/kdeproviderwidget.h b/providers/kdeprovider/kdeproviderwidget.h index 631232b040..00eb2c21e7 100644 --- a/providers/kdeprovider/kdeproviderwidget.h +++ b/providers/kdeprovider/kdeproviderwidget.h @@ -1,47 +1,47 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * 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 General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEPROVIDERWIDGET_H #define KDEPROVIDERWIDGET_H #include class QModelIndex; class KConfigDialog; class KFilterProxySearchLine; class QListView; class KDEProviderWidget : public KDevelop::IProjectProviderWidget { Q_OBJECT public: - KDEProviderWidget(QWidget* parent = 0); + KDEProviderWidget(QWidget* parent = nullptr); KDevelop::VcsJob* createWorkingCopy(const QUrl &destinationDirectory) override; bool isCorrect() const override; private slots: void showSettings(); void projectIndexChanged(const QModelIndex& currentIndex); private: QListView* m_projects; }; #endif // KDEPROVIDERWIDGET_H diff --git a/providers/kdeprovider/tests/test_kdeprojectsreader.cpp b/providers/kdeprovider/tests/test_kdeprojectsreader.cpp index e7b3727e8e..5325f9703a 100644 --- a/providers/kdeprovider/tests/test_kdeprojectsreader.cpp +++ b/providers/kdeprovider/tests/test_kdeprojectsreader.cpp @@ -1,60 +1,60 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_kdeprojectsreader.h" #include #include #include #include "../kdeprojectsreader.h" QTEST_MAIN( TestKDEProjectsReader ) void TestKDEProjectsReader::testsProperParse() { KDEProjectsModel m; - KDEProjectsReader reader(&m, 0); + KDEProjectsReader reader(&m, nullptr); if(reader.hasErrors()) qDebug() << "errors:" << reader.errors(); QVERIFY(!reader.hasErrors()); /// FIXME: a unit test should never try to download anything from the website /// this must be mocked properly QSignalSpy downloadDoneSpy(&reader, SIGNAL(downloadDone())); QVERIFY(downloadDoneSpy.wait(30000)); for(int i=0; itext() << item->icon() << item->data(KDEProjectsModel::VcsLocationRole); QVERIFY(item); QVERIFY(!item->text().isEmpty()); QVariant urls = item->data(KDEProjectsModel::VcsLocationRole); QVERIFY(urls.isValid()); QVERIFY(urls.canConvert(QVariant::Map)); QVariantMap mapurls=urls.toMap(); for(QVariantMap::const_iterator it=mapurls.constBegin(), itEnd=mapurls.constEnd(); it!=itEnd; ++it) { QVERIFY(!it.key().isEmpty()); QVERIFY(!it.value().toString().isEmpty()); } QVERIFY(!item->data(KDEProjectsModel::VcsLocationRole).toMap().isEmpty()); } } diff --git a/utils/okteta/kastentoolviewwidget.cpp b/utils/okteta/kastentoolviewwidget.cpp index 62674a9c7f..5d6b245520 100644 --- a/utils/okteta/kastentoolviewwidget.cpp +++ b/utils/okteta/kastentoolviewwidget.cpp @@ -1,82 +1,82 @@ /* This file is part of the KDevelop Okteta module, part of the KDE project. Copyright 2010 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kastentoolviewwidget.h" // plugin #include "oktetaview.h" // Okteta Kasten #include "kasten/okteta/bytearrayview.h" // Kasten #include #include // KDev #include #include #include #include // Qt #include namespace KDevelop { // TODO: find if hiddden, than unset target model KastenToolViewWidget::KastenToolViewWidget( Kasten::AbstractToolView* toolView, QWidget* parent ) : QWidget( parent ), mToolView( toolView ) { Sublime::Controller* controller = ICore::self()->uiController()->controller(); connect( controller, &Sublime::Controller::mainWindowAdded, this, &KastenToolViewWidget::onMainWindowAdded ); const QList& mainWindows = controller->mainWindows(); foreach( Sublime::MainWindow* mainWindow, mainWindows ) onMainWindowAdded( mainWindow ); QVBoxLayout* layout = new QVBoxLayout( this ); layout->setMargin( 0 ); layout->addWidget( mToolView->widget() ); } void KastenToolViewWidget::onMainWindowAdded( Sublime::MainWindow* mainWindow ) { connect( mainWindow, &Sublime::MainWindow::activeViewChanged, this, &KastenToolViewWidget::onActiveViewChanged ); onActiveViewChanged( mainWindow->activeView() ); } void KastenToolViewWidget::onActiveViewChanged( Sublime::View* view ) { // TODO: check if own mainWindow OktetaView* oktetaView = qobject_cast( view ); - Kasten::ByteArrayView* byteArrayView = oktetaView ? oktetaView->byteArrayView() : 0; + Kasten::ByteArrayView* byteArrayView = oktetaView ? oktetaView->byteArrayView() : nullptr; mToolView->tool()->setTargetModel( byteArrayView ); } KastenToolViewWidget::~KastenToolViewWidget() { Kasten::AbstractTool* tool = mToolView->tool(); delete mToolView; delete tool; } } diff --git a/utils/okteta/oktetadocument.cpp b/utils/okteta/oktetadocument.cpp index fa256f7c25..195a670e83 100644 --- a/utils/okteta/oktetadocument.cpp +++ b/utils/okteta/oktetadocument.cpp @@ -1,250 +1,250 @@ /* This file is part of the KDevelop Okteta module, part of the KDE project. Copyright 2010-2011 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "oktetadocument.h" // plugin #include "oktetaplugin.h" #include "oktetaview.h" // Okteta #include #include #include #include // Kasten #include #include #include #include #include // KDevelop #include #include #include #include #include // Sublime #include #include #include #include // KDE #include #include // Qt #include #include namespace KDevelop { OktetaDocument::OktetaDocument( const QUrl &url , ICore* core ) : Sublime::UrlDocument( core->uiController()->controller(), url ), IDocument( core ), - mByteArrayDocument( 0 ) + mByteArrayDocument( nullptr ) { } QUrl OktetaDocument::url() const { return Sublime::UrlDocument::url(); } // TODO: use fromContentAndUrl(ByteArrayIODevice) if document loaded QMimeType OktetaDocument::mimeType() const { return QMimeDatabase().mimeTypeForUrl( url() ); } -KParts::Part* OktetaDocument::partForView( QWidget* ) const { return 0; } -KTextEditor::Document* OktetaDocument::textDocument() const { return 0; } +KParts::Part* OktetaDocument::partForView( QWidget* ) const { return nullptr; } +KTextEditor::Document* OktetaDocument::textDocument() const { return nullptr; } KTextEditor::Cursor OktetaDocument::cursorPosition() const { return KTextEditor::Cursor(); } IDocument::DocumentState OktetaDocument::state() const { return mByteArrayDocument ? ( mByteArrayDocument->synchronizer()->localSyncState() == Kasten::LocalHasChanges ? IDocument::Modified : IDocument::Clean ) : IDocument::Clean; } bool OktetaDocument::save( IDocument::DocumentSaveMode mode ) { if( mode & Discard ) return true; if( state() == IDocument::Clean ) return false; Kasten::AbstractModelSynchronizer* synchronizer = mByteArrayDocument->synchronizer(); Kasten::AbstractSyncToRemoteJob* syncJob = synchronizer->startSyncToRemote(); const bool syncSucceeded = Kasten::JobManager::executeJob( syncJob ); if( syncSucceeded ) { notifySaved(); notifyStateChanged(); } return syncSucceeded; } void OktetaDocument::reload() { Kasten::AbstractModelSynchronizer* synchronizer = mByteArrayDocument->synchronizer(); Kasten::AbstractSyncFromRemoteJob* syncJob = synchronizer->startSyncFromRemote(); const bool syncSucceeded = Kasten::JobManager::executeJob( syncJob ); if( syncSucceeded ) notifyStateChanged(); } bool OktetaDocument::close( IDocument::DocumentSaveMode mode ) { bool isCanceled = false; if( !(mode & Discard) ) { if (mode & Silent) { if (!save(mode)) isCanceled = true; } else { if( state() == IDocument::Modified ) { // TODO: use Kasten::*Manager int code = KMessageBox::warningYesNoCancel( qApp->activeWindow(), i18n("The document \"%1\" has unsaved changes. Would you like to save them?", url().toLocalFile()), i18n("Close Document")); if (code == KMessageBox::Yes) { if (!save(mode)) isCanceled = true; } else if (code == KMessageBox::Cancel) isCanceled = true; } else if( state() == IDocument::DirtyAndModified ) { if( !save(mode) ) isCanceled = true; } } } if( isCanceled ) return false; //close all views and then delete ourself ///@todo test this const QList& allAreas = ICore::self()->uiController()->controller()->allAreas(); foreach( Sublime::Area *area, allAreas ) { const QList areaViews = area->views(); foreach( Sublime::View* view, areaViews ) { if (views().contains(view)) { area->removeView(view); delete view; } } } // The document is deleted automatically when there are no views left return true; } bool OktetaDocument::isActive() const { return Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()->document() == this; } void OktetaDocument::setCursorPosition( const KTextEditor::Cursor& ) {} void OktetaDocument::setTextSelection( const KTextEditor::Range& ) {} void OktetaDocument::activate( Sublime::View* view, KParts::MainWindow* mainWindow ) { notifyActivated(); } void OktetaDocument::setPlugin( OktetaPlugin* plugin ) { mPlugin = plugin; } Sublime::View* OktetaDocument::newView( Sublime::Document* document ) { - if( mByteArrayDocument == 0 ) + if( mByteArrayDocument == nullptr ) { Kasten::ByteArrayRawFileSynchronizerFactory* synchronizerFactory = new Kasten::ByteArrayRawFileSynchronizerFactory(); Kasten::AbstractModelSynchronizer* synchronizer = synchronizerFactory->createSynchronizer(); Kasten::AbstractLoadJob* loadJob = synchronizer->startLoad( url() ); connect( loadJob, &Kasten::AbstractLoadJob::documentLoaded, this, &OktetaDocument::onByteArrayDocumentLoaded ); const bool syncSucceeded = Kasten::JobManager::executeJob( loadJob ); delete synchronizerFactory; } Kasten::ByteArrayViewProfileManager* const viewProfileManager = mPlugin->viewProfileManager(); Kasten::ByteArrayViewProfileSynchronizer* viewProfileSynchronizer = new Kasten::ByteArrayViewProfileSynchronizer( viewProfileManager ); viewProfileSynchronizer->setViewProfileId( viewProfileManager->defaultViewProfileId() ); return new OktetaView( this, viewProfileSynchronizer ); } bool OktetaDocument::closeDocument(bool silent) { return close(silent ? Silent : Default); } void OktetaDocument::onByteArrayDocumentLoaded( Kasten::AbstractDocument* document ) { if( document ) { mByteArrayDocument = static_cast( document ); connect( mByteArrayDocument->synchronizer(), &Kasten::AbstractModelSynchronizer::localSyncStateChanged, this, &OktetaDocument::onByteArrayDocumentChanged ); } } void OktetaDocument::onByteArrayDocumentChanged() { notifyStateChanged(); } OktetaDocument::~OktetaDocument() { delete mByteArrayDocument; } } diff --git a/utils/okteta/oktetaview.h b/utils/okteta/oktetaview.h index a12e2cc506..4737e46ba7 100644 --- a/utils/okteta/oktetaview.h +++ b/utils/okteta/oktetaview.h @@ -1,64 +1,64 @@ /* This file is part of the KDevelop Okteta module, part of the KDE project. Copyright 2010 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef OKTETAVIEW_H #define OKTETAVIEW_H // KDevPlatform #include namespace Kasten { class ByteArrayViewProfileSynchronizer; class ByteArrayView; } namespace KDevelop { class OktetaDocument; class OktetaView : public Sublime::View { Q_OBJECT public: OktetaView( OktetaDocument* document, Kasten::ByteArrayViewProfileSynchronizer* viewProfileSynchronizer ); ~OktetaView() override; public: Kasten::ByteArrayView* byteArrayView() const; protected: // Sublime::View API - QWidget* createWidget( QWidget* parent = 0 ) override; + QWidget* createWidget( QWidget* parent = nullptr ) override; protected: Kasten::ByteArrayView* mByteArrayView; }; inline Kasten::ByteArrayView* OktetaView::byteArrayView() const { return mByteArrayView; } } #endif