diff --git a/app/main.cpp b/app/main.cpp index df75fcd30f..3964e09e7f 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,827 +1,827 @@ /*************************************************************************** * Copyright 2003-2009 Alexander Dymo * * Copyright 2007 Ralf Habacker * * Copyright 2006-2007 Matt Rogers * * Copyright 2006-2007 Hamish Rodda * * Copyright 2005-2007 Adam Treat * * Copyright 2003-2007 Jens Dagerbo * * Copyright 2001-2002 Bernd Gehrmann * * Copyright 2001-2002 Matthias Hoelzer-Kluepfel * * Copyright 2003 Roberto Raggi * * Copyright 2010 Niko Sams * * Copyright 2015 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "config-kdevelop.h" #include "kdevelop_version.h" #include "urlinfo.h" #include #include -#include -#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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) { for (const UrlInfo& info : infos) { if (!ICore::self()->documentController()->openDocument(info.url, info.cursor)) { qWarning(APP) << i18n("Could not open %1", info.url.toDisplayString(QUrl::PreferLocalFile)); } } } } class KDevelopApplication: #if KDEVELOP_SINGLE_APP public SharedTools::QtSingleApplication #else public QApplication #endif { public: explicit KDevelopApplication(int &argc, char **argv, bool GUIenabled = true) #if KDEVELOP_SINGLE_APP : SharedTools::QtSingleApplication(QStringLiteral("KDevelop"), argc, argv) #else : QApplication(argc, argv, GUIenabled) #endif { Q_UNUSED(GUIenabled); connect(this, &QGuiApplication::saveStateRequest, this, &KDevelopApplication::saveState); } #if KDEVELOP_SINGLE_APP public Q_SLOTS: void remoteArguments(const QString &message, QObject *socket) { Q_UNUSED(socket); QByteArray ba = QByteArray::fromHex(message.toLatin1()); QDataStream stream(ba); QByteArray command; stream >> command; qCDebug(APP) << "Received remote command: " << command; if (command == "open") { QVector infos; stream >> infos; QVector files, directories; for (const auto& info : infos) if (info.isDirectory()) directories << info; else files << info; openFiles(files); for(const auto &urlinfo : directories) ICore::self()->projectController()->openProjectForUrl(urlinfo.url); } else { qCWarning(APP) << "Unknown remote command: " << command; } } void fileOpenRequested(const QString &file) { openFiles({UrlInfo(file)}); } #endif private Q_SLOTS: void saveState( QSessionManager& sm ) { if (KDevelop::Core::self() && KDevelop::Core::self()->sessionController()) { const auto activeSession = KDevelop::Core::self()->sessionController()->activeSession(); if (!activeSession) { qWarning(APP) << "No active session, can't save state"; return; } const QString x11SessionId = sm.sessionId() + QLatin1Char('_') + sm.sessionKey(); QString kdevelopSessionId = activeSession->id().toString(); sm.setRestartCommand({QCoreApplication::applicationFilePath(), "-session", x11SessionId, "-s", kdevelopSessionId}); } } }; /// Tries to find a session identified by @p data in @p sessions. /// The @p data may be either a session's name or a string-representation of its UUID. /// @return pointer to the session or NULL if nothing appropriate has been found static const KDevelop::SessionInfo* findSessionInList( const SessionInfos& sessions, const QString& data ) { // We won't search a session without input data, since that could lead to false-positives // with unnamed sessions if( data.isEmpty() ) return nullptr; for( auto it = sessions.constBegin(); it != sessions.constEnd(); ++it ) { if ( ( it->name == data ) || ( it->uuid.toString() == data ) ) { const KDevelop::SessionInfo& sessionRef = *it; return &sessionRef; } } return nullptr; } /// Tries to find sessions containing project @p projectUrl in @p sessions. static const KDevelop::SessionInfos findSessionsWithProject(const SessionInfos& sessions, const QUrl& projectUrl) { if (!projectUrl.isValid()) return {}; KDevelop::SessionInfos infos; for (auto it = sessions.constBegin(); it != sessions.constEnd(); ++it) { if (it->projects.contains(projectUrl)) { infos << *it; } } return infos; } /// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid /// Returns the exit status static int openFilesInRunningInstance(const QVector& files, qint64 pid) { const QString service = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid); QDBusInterface iface(service, QStringLiteral("/org/kdevelop/DocumentController"), QStringLiteral("org.kdevelop.DocumentController")); QStringList urls; bool errors_occured = false; for (const UrlInfo& file : files) { QDBusReply result = iface.call(QStringLiteral("openDocumentSimple"), file.url.toString(), file.cursor.line(), file.cursor.column()); if ( ! result.value() ) { QTextStream err(stderr); err << i18n("Could not open file '%1'.", file.url.toDisplayString(QUrl::PreferLocalFile)) << "\n"; errors_occured = true; } } // make the window visible QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"), QStringLiteral("ensureVisible") ); QDBusConnection::sessionBus().asyncCall( makeVisible ); return errors_occured; } /// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid /// Returns the exit status static int openProjectInRunningInstance(const QVector& paths, qint64 pid) { const QString service = QStringLiteral("org.kdevelop.kdevelop-%1").arg(pid); QDBusInterface iface(service, QStringLiteral("/org/kdevelop/ProjectController"), QStringLiteral("org.kdevelop.ProjectController")); int errors = 0; for (const UrlInfo& path : paths) { QDBusReply result = iface.call(QStringLiteral("openProjectForUrl"), path.url.toString()); if ( !result.isValid() ) { QTextStream err(stderr); err << i18n("Could not open project '%1': %2", path.url.toDisplayString(QUrl::PreferLocalFile), result.error().message()) << "\n"; ++errors; } } // make the window visible QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"), QStringLiteral("ensureVisible") ); QDBusConnection::sessionBus().asyncCall( makeVisible ); return errors; } /// Gets the PID of a running KDevelop instance, eventually asking the user if there is more than one. /// Returns -1 in case there are no running sessions. static qint64 getRunningSessionPid() { SessionInfos candidates; foreach( const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos() ) { if( KDevelop::SessionController::isSessionRunning(si.uuid.toString()) ) { candidates << si; } } if ( candidates.isEmpty() ) { return -1; } QString sessionUuid; if ( candidates.size() == 1 ) { sessionUuid = candidates.first().uuid.toString(); } else { const QString title = i18n("Select the session to open the document in"); sessionUuid = KDevelop::SessionController::showSessionChooserDialog(title, true); } return KDevelop::SessionController::sessionRunInfo(sessionUuid).holderPid; } static QString findSessionId(const SessionInfos& availableSessionInfos, const QString& session) { //If there is a session and a project with the same name, always open the session //regardless of the order encountered QString projectAsSession; for (const KDevelop::SessionInfo& si : availableSessionInfos) { if ( session == si.name || session == si.uuid.toString() ) { return si.uuid.toString(); } else if (projectAsSession.isEmpty()) { foreach(const QUrl& k, si.projects) { QString fn(k.fileName()); fn = fn.left(fn.indexOf('.')); if ( session == fn ) { projectAsSession = si.uuid.toString(); } } } } if (projectAsSession.isEmpty()) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot open unknown session %1. See `--list-sessions` switch for available sessions or use `-n` to create a new one.", session) << endl; } return projectAsSession; } static qint64 findSessionPid(const QString &sessionId) { KDevelop::SessionRunInfo sessionInfo = KDevelop::SessionController::sessionRunInfo( sessionId ); return sessionInfo.holderPid; } int main( int argc, char *argv[] ) { QElapsedTimer timer; timer.start(); #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) // If possible, use the Software backend for QQuickWidget (currently used in the // welcome page plugin). This means we don't need OpenGL at all, avoiding issues // like https://bugs.kde.org/show_bug.cgi?id=386527. QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); #endif // TODO: Maybe generalize, add KDEVELOP_STANDALONE build option #if defined(Q_OS_WIN) || defined(Q_OS_MAC) qputenv("KDE_FORK_SLAVES", "1"); // KIO slaves will be forked off instead of being started via DBus #endif // Useful for valgrind runs, just `export KDEV_DISABLE_JIT=1` if (qEnvironmentVariableIsSet("KDEV_DISABLE_JIT")) { qputenv("KDEV_DISABLE_WELCOMEPAGE", "1"); qputenv("QT_ENABLE_REGEXP_JIT", "0"); } QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); #ifdef Q_OS_MAC CFBundleRef mainBundle = CFBundleGetMainBundle(); if (mainBundle) { // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist, // for regular executables it is obtained in another way. CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle); if (infoDict) { // Try to prevent App Nap on OS X. This can be tricky in practice, at least in 10.9 . CFDictionarySetValue(infoDict, CFSTR("NSAppSleepDisabled"), kCFBooleanTrue); CFDictionarySetValue(infoDict, CFSTR("NSSupportsAutomaticTermination"), kCFBooleanFalse); } } #endif //we can't use KCmdLineArgs as it doesn't allow arguments for the debugee //so lookup the --debug switch and eat everything behind by decrementing argc //debugArgs is filled with args after --debug QStringList debugArgs; QString debugeeName; { bool debugFound = false; int c = argc; for (int i=0; i < c; ++i) { if (debugFound) { debugArgs << argv[i]; } else if ((qstrcmp(argv[i], "--debug") == 0) || (qstrcmp(argv[i], "-d") == 0)) { if (argc > (i + 1)) { i++; } argc = i + 1; debugFound = true; } else if (QString(argv[i]).startsWith(QLatin1String("--debug="))) { argc = i + 1; debugFound = true; } } } KDevelopApplication app(argc, argv); KLocalizedString::setApplicationDomain("kdevelop"); KAboutData aboutData( QStringLiteral("kdevelop"), i18n( "KDevelop" ), QByteArray(KDEVELOP_VERSION_STRING), i18n("The KDevelop Integrated Development Environment"), KAboutLicense::GPL, i18n("Copyright 1999-2018, The KDevelop developers"), QString(), QStringLiteral("https://www.kdevelop.org/")); aboutData.setDesktopFileName(QStringLiteral("org.kde.kdevelop")); aboutData.addAuthor( i18n("Kevin Funk"), i18n( "Co-maintainer, C++/Clang, QA, Windows Support" ), QStringLiteral("kfunk@kde.org") ); aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Co-maintainer, AppImage, Python Support, User Interface improvements" ), QStringLiteral("svenbrauch@gmail.com") ); aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "CMake Support, Run Support, Kross Support" ), QStringLiteral("aleixpol@gmail.com") ); aboutData.addAuthor( i18n("Milian Wolff"), i18n( "C++/Clang, Generic manager, Webdevelopment Plugins, Snippets, Performance" ), QStringLiteral("mail@milianw.de") ); aboutData.addAuthor( i18n("Olivier JG"), i18n( "C++/Clang, DUChain, Bug Fixes" ), QStringLiteral("olivier.jg@gmail.com") ); aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), QStringLiteral("apaku@gmx.de") ); aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), QStringLiteral("adymo@kdevelop.org") ); aboutData.addAuthor( i18n("David Nolden"), i18n( "Definition-Use Chain, C++ Support, Code Navigation, Code Completion, Coding Assistance, Refactoring" ), QStringLiteral("david.nolden.kdevelop@art-master.de") ); aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), QStringLiteral("ghost@cs.msu.su") ); aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), QStringLiteral("rodda@kde.org") ); aboutData.addAuthor( i18n("Amilcar do Carmo Lucas"), i18n( "Website admin, API documentation, Doxygen and autoproject patches" ), QStringLiteral("amilcar@kdevelop.org") ); aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), QStringLiteral("niko.sams@gmail.com") ); aboutData.addAuthor( i18n("Friedrich W. H. Kossebau"), QString(), QStringLiteral("kossebau@kde.org") ); aboutData.addCredit( i18n("Matt Rogers"), QString(), QStringLiteral("mattr@kde.org")); aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), QStringLiteral("cedric.pasteur@free.fr") ); aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), QStringLiteral("powerfox@kde.ru") ); // QTest integration is separate in playground currently. //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integration"), "mbr.nxi@gmail.com" ); aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), QStringLiteral("rgruber@users.sourceforge.net") ); aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), QStringLiteral("dukjuahn@gmail.com") ); aboutData.addCredit( i18n("Harald Fernengel"), i18n( "Ported to Qt 3, patches, valgrind, diff and perforce support" ), QStringLiteral("harry@kdevelop.org") ); aboutData.addCredit( i18n("Roberto Raggi"), i18n( "C++ parser" ), QStringLiteral("roberto@kdevelop.org") ); aboutData.addCredit( i18n("The KWrite authors"), i18n( "Kate editor component" ), QStringLiteral("kwrite-devel@kde.org") ); aboutData.addCredit( i18n("Nokia Corporation/Qt Software"), i18n( "Designer code" ), QStringLiteral("qt-info@nokia.com") ); aboutData.addCredit( i18n("Contributors to older versions:"), QString(), QString() ); aboutData.addCredit( i18n("Bernd Gehrmann"), i18n( "Initial idea, basic architecture, much initial source code" ), QStringLiteral("bernd@kdevelop.org") ); aboutData.addCredit( i18n("Caleb Tennis"), i18n( "KTabBar, bugfixes" ), QStringLiteral("caleb@aei-tech.com") ); aboutData.addCredit( i18n("Richard Dale"), i18n( "Java & Objective C support" ), QStringLiteral("Richard_Dale@tipitina.demon.co.uk") ); aboutData.addCredit( i18n("John Birch"), i18n( "Debugger frontend" ), QStringLiteral("jbb@kdevelop.org") ); aboutData.addCredit( i18n("Sandy Meier"), i18n( "PHP support, context menu stuff" ), QStringLiteral("smeier@kdevelop.org") ); aboutData.addCredit( i18n("Kurt Granroth"), i18n( "KDE application templates" ), QStringLiteral("kurth@granroth.org") ); aboutData.addCredit( i18n("Ian Reinhart Geiser"), i18n( "Dist part, bash support, application templates" ), QStringLiteral("geiseri@yahoo.com") ); aboutData.addCredit( i18n("Matthias Hoelzer-Kluepfel"), i18n( "Several components, htdig indexing" ), QStringLiteral("hoelzer@kde.org") ); aboutData.addCredit( i18n("Victor Roeder"), i18n( "Help with Automake manager and persistent class store" ), QStringLiteral("victor_roeder@gmx.de") ); aboutData.addCredit( i18n("Simon Hausmann"), i18n( "Help with KParts infrastructure" ), QStringLiteral("hausmann@kde.org") ); aboutData.addCredit( i18n("Oliver Kellogg"), i18n( "Ada support" ), QStringLiteral("okellogg@users.sourceforge.net") ); aboutData.addCredit( i18n("Jakob Simon-Gaarde"), i18n( "QMake projectmanager" ), QStringLiteral("jsgaarde@tdcspace.dk") ); aboutData.addCredit( i18n("Falk Brettschneider"), i18n( "MDI modes, QEditor, bugfixes" ), QStringLiteral("falkbr@kdevelop.org") ); aboutData.addCredit( i18n("Mario Scalas"), i18n( "PartExplorer, redesign of CvsPart, patches, bugs(fixes)" ), QStringLiteral("mario.scalas@libero.it") ); aboutData.addCredit( i18n("Jens Dagerbo"), i18n( "Replace, Bookmarks, FileList and CTags2 plugins. Overall improvements and patches" ), QStringLiteral("jens.dagerbo@swipnet.se") ); aboutData.addCredit( i18n("Julian Rockey"), i18n( "Filecreate part and other bits and patches" ), QStringLiteral("linux@jrockey.com") ); aboutData.addCredit( i18n("Ajay Guleria"), i18n( "ClearCase support" ), QStringLiteral("ajay_guleria@yahoo.com") ); aboutData.addCredit( i18n("Marek Janukowicz"), i18n( "Ruby support" ), QStringLiteral("child@t17.ds.pwr.wroc.pl") ); aboutData.addCredit( i18n("Robert Moniot"), i18n( "Fortran documentation" ), QStringLiteral("moniot@fordham.edu") ); aboutData.addCredit( i18n("Ka-Ping Yee"), i18n( "Python documentation utility" ), QStringLiteral("ping@lfw.org") ); aboutData.addCredit( i18n("Dimitri van Heesch"), i18n( "Doxygen wizard" ), QStringLiteral("dimitri@stack.nl") ); aboutData.addCredit( i18n("Hugo Varotto"), i18n( "Fileselector component" ), QStringLiteral("hugo@varotto-usa.com") ); aboutData.addCredit( i18n("Matt Newell"), i18n( "Fileselector component" ), QStringLiteral("newellm@proaxis.com") ); aboutData.addCredit( i18n("Daniel Engelschalt"), i18n( "C++ code completion, persistent class store" ), QStringLiteral("daniel.engelschalt@gmx.net") ); aboutData.addCredit( i18n("Stephane Ancelot"), i18n( "Patches" ), QStringLiteral("sancelot@free.fr") ); aboutData.addCredit( i18n("Jens Zurheide"), i18n( "Patches" ), QStringLiteral("jens.zurheide@gmx.de") ); aboutData.addCredit( i18n("Luc Willems"), i18n( "Help with Perl support" ), QStringLiteral("Willems.luc@pandora.be") ); aboutData.addCredit( i18n("Marcel Turino"), i18n( "Documentation index view" ), QStringLiteral("M.Turino@gmx.de") ); aboutData.addCredit( i18n("Yann Hodique"), i18n( "Patches" ), QStringLiteral("Yann.Hodique@lifl.fr") ); aboutData.addCredit( i18n("Tobias Gl\303\244\303\237er") , i18n( "Documentation Finder, qmake projectmanager patches, usability improvements, bugfixes ... " ), QStringLiteral("tobi.web@gmx.de") ); aboutData.addCredit( i18n("Andreas Koepfle") , i18n( "QMake project manager patches" ), QStringLiteral("koepfle@ti.uni-mannheim.de") ); aboutData.addCredit( i18n("Sascha Cunz") , i18n( "Cleanup and bugfixes for qEditor, AutoMake and much other stuff" ), QStringLiteral("mail@sacu.de") ); aboutData.addCredit( i18n("Zoran Karavla"), i18n( "Artwork for the ruby language" ), QStringLiteral("webmaster@the-error.net"), QStringLiteral("http://the-error.net") ); KAboutData::setApplicationData(aboutData); // set icon for shells which do not use desktop file metadata // but without setting replacing an existing icon with an empty one! QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("kdevelop"), QApplication::windowIcon())); KCrash::initialize(); Kdelibs4ConfigMigrator migrator(QStringLiteral("kdevelop")); migrator.setConfigFiles({QStringLiteral("kdeveloprc")}); migrator.setUiFiles({QStringLiteral("kdevelopui.rc")}); migrator.migrate(); // High DPI support app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); qCDebug(APP) << "Startup"; QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addOption(QCommandLineOption{QStringList{"n", "new-session"}, i18n("Open KDevelop with a new session using the given name."), QStringLiteral("name")}); parser.addOption(QCommandLineOption{QStringList{"s", "open-session"}, i18n("Open KDevelop with the given session.\n" "You can pass either hash or the name of the session." ), QStringLiteral("session")}); parser.addOption(QCommandLineOption{QStringList{"rm", "remove-session"}, i18n("Delete the given session.\n" "You can pass either hash or the name of the session." ), QStringLiteral("session")}); parser.addOption(QCommandLineOption{QStringList{"ps", "pick-session"}, i18n("Shows all available sessions and lets you select one to open.")}); parser.addOption(QCommandLineOption{QStringList{"pss", "pick-session-shell"}, i18n("List all available sessions on shell and lets you select one to open.")}); parser.addOption(QCommandLineOption{QStringList{"l", "list-sessions"}, i18n("List available sessions and quit.")}); parser.addOption(QCommandLineOption{QStringList{"f", "fetch"}, i18n("Open KDevelop and fetch the project from the given ."), QStringLiteral("repo url")}); parser.addOption(QCommandLineOption{QStringList{"p", "project"}, i18n("Open KDevelop and load the given project. can be either a .kdev4 file or a directory path."), QStringLiteral("project")}); parser.addOption(QCommandLineOption{QStringList{"d", "debug"}, i18n("Start debugging an application in KDevelop with the given debugger.\n" "The executable that should be debugged must follow - including arguments.\n" "Example: kdevelop --debug gdb myapp --foo bar"), QStringLiteral("debugger")}); // this is used by the 'kdevelop!' script to retrieve the pid of a KDEVELOP // instance. When this is called, then we should just print the PID on the // standard-output. If a session is specified through open-session, then // we should return the PID of that session. Otherwise, if only a single // session is running, then we should just return the PID of that session. // Otherwise, we should print a command-line session-chooser dialog ("--pss"), // which only shows the running sessions, and the user can pick one. parser.addOption(QCommandLineOption{QStringList{"pid"}}); parser.addPositionalArgument(QStringLiteral("files"), i18n( "Files to load, or directories to load as projects" ), QStringLiteral("[FILE[:line[:column]] | DIRECTORY]...")); // The session-controller needs to arguments to eventually pass them to newly opened sessions KDevelop::SessionController::setArguments(argc, argv); parser.process(app); aboutData.processCommandLine(&parser); if(parser.isSet(QStringLiteral("list-sessions"))) { QTextStream qout(stdout); qout << endl << ki18n("Available sessions (use '-s HASH' or '-s NAME' to open a specific one):").toString() << endl << endl; qout << QStringLiteral("%1").arg(ki18n("Hash").toString(), -38) << '\t' << ki18n("Name: Opened Projects").toString() << endl; foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } qout << si.uuid.toString() << '\t' << si.description; if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) qout << " " << i18n("[running]"); qout << endl; } return 0; } // Handle extra arguments, which stand for files to open QVector initialFiles; QVector initialDirectories; foreach (const QString &file, parser.positionalArguments()) { const UrlInfo info(file); if (info.isDirectory()) { initialDirectories.append(info); } else { initialFiles.append(info); } } const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos(); if ((!initialFiles.isEmpty() || !initialDirectories.isEmpty()) && !parser.isSet(QStringLiteral("new-session"))) { #if KDEVELOP_SINGLE_APP if (app.isRunning()) { bool success = app.sendMessage(serializeOpenFilesMessage(initialFiles << initialDirectories)); if (success) { return 0; } } #else qint64 pid = -1; if (parser.isSet(QStringLiteral("open-session"))) { const QString session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("open-session"))); if (session.isEmpty()) { return 1; } else if (KDevelop::SessionController::isSessionRunning(session)) { pid = findSessionPid(session); } } else { pid = getRunningSessionPid(); } if ( pid > 0 ) { return openFilesInRunningInstance(initialFiles, pid) + openProjectInRunningInstance(initialDirectories, pid); } // else there are no running sessions, and the generated list of files will be opened below. #endif } // if empty, restart kdevelop with last active session, see SessionController::defaultSessionId QString session; uint nRunningSessions = 0; for (const KDevelop::SessionInfo& si : availableSessionInfos) { if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) ++nRunningSessions; } // also show the picker dialog when a pid shall be retrieved and multiple // sessions are running. if(parser.isSet(QStringLiteral("pss")) || (parser.isSet(QStringLiteral("pid")) && !parser.isSet(QStringLiteral("open-session")) && !parser.isSet(QStringLiteral("ps")) && nRunningSessions > 1)) { QTextStream qerr(stderr); SessionInfos candidates; for (const KDevelop::SessionInfo& si : availableSessionInfos) { if( (!si.name.isEmpty() || !si.projects.isEmpty() || parser.isSet(QStringLiteral("pid"))) && (!parser.isSet(QStringLiteral("pid")) || KDevelop::SessionController::isSessionRunning(si.uuid.toString()))) candidates << si; } if(candidates.size() == 0) { qerr << "no session available" << endl; return 1; } if(candidates.size() == 1 && parser.isSet(QStringLiteral("pid"))) { session = candidates[0].uuid.toString(); }else{ for(int i = 0; i < candidates.size(); ++i) qerr << "[" << i << "]: " << candidates[i].description << endl; int chosen; std::cin >> chosen; if(std::cin.good() && (chosen >= 0 && chosen < candidates.size())) { session = candidates[chosen].uuid.toString(); }else{ qerr << "invalid selection" << endl; return 1; } } } if(parser.isSet(QStringLiteral("ps"))) { bool onlyRunning = parser.isSet(QStringLiteral("pid")); session = KDevelop::SessionController::showSessionChooserDialog(i18n("Select the session you would like to use"), onlyRunning); if(session.isEmpty()) return 1; } if ( parser.isSet(QStringLiteral("debug")) ) { if ( debugArgs.isEmpty() ) { QTextStream qerr(stderr); qerr << endl << i18nc("@info:shell", "Specify the executable you want to debug.") << endl; return 1; } QFileInfo executableFileInfo(debugArgs.first()); if (!executableFileInfo.exists()) { executableFileInfo = QStandardPaths::findExecutable(debugArgs.first()); if (!executableFileInfo.exists()) { QTextStream qerr(stderr); qerr << endl << i18nc("@info:shell", "Specified executable does not exist.") << endl; return 1; } } debugArgs.first() = executableFileInfo.absoluteFilePath(); debugeeName = i18n("Debug %1", executableFileInfo.fileName()); session = debugeeName; } else if ( parser.isSet(QStringLiteral("new-session")) ) { session = parser.value(QStringLiteral("new-session")); for (const KDevelop::SessionInfo& si : availableSessionInfos) { if ( session == si.name ) { QTextStream qerr(stderr); qerr << endl << i18n("A session with the name %1 exists already. Use the -s switch to open it.", session) << endl; return 1; } } // session doesn't exist, we can create it } else if ( parser.isSet(QStringLiteral("open-session")) ) { session = findSessionId(availableSessionInfos, parser.value(QStringLiteral("open-session"))); if (session.isEmpty()) { return 1; } } else if ( parser.isSet(QStringLiteral("remove-session")) ) { session = parser.value(QStringLiteral("remove-session")); auto si = findSessionInList(availableSessionInfos, session); if (!si) { QTextStream qerr(stderr); qerr << endl << i18n("No session with the name %1 exists.", session) << endl; return 1; } auto sessionLock = KDevelop::SessionController::tryLockSession(si->uuid.toString()); if (!sessionLock.lock) { QTextStream qerr(stderr); qerr << endl << i18n("Could not lock session %1 for deletion.", session) << endl; return 1; } KDevelop::SessionController::deleteSessionFromDisk(sessionLock.lock); QTextStream qout(stdout); qout << endl << i18n("Session with name %1 was successfully removed.", session) << endl; return 0; } if(parser.isSet(QStringLiteral("pid"))) { if (session.isEmpty()) { // just pick the first running session for (const KDevelop::SessionInfo& si : availableSessionInfos) { if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) session = si.uuid.toString(); } } const KDevelop::SessionInfo* sessionData = findSessionInList(availableSessionInfos, session); if( !sessionData ) { qCritical(APP) << "session not given or does not exist"; return 5; } const auto pid = findSessionPid(sessionData->uuid.toString()); if (pid > 0) { // Print the PID and we're ready std::cout << pid << std::endl; return 0; } else { qCritical(APP) << sessionData->uuid.toString() << sessionData->name << "is not running"; return 5; } } if (parser.isSet(QStringLiteral("project"))) { const auto project = parser.value(QStringLiteral("project")); QFileInfo info(project); QUrl projectUrl; if (info.suffix() == QLatin1String("kdev4")) { projectUrl = QUrl::fromLocalFile(info.absoluteFilePath()); } else if (info.isDir()) { QDir dir(info.absoluteFilePath()); const auto potentialProjectFiles = dir.entryList({QStringLiteral("*.kdev4")}, QDir::Files, QDir::Name); qDebug(APP) << "Found these potential project files:" << potentialProjectFiles; if (!potentialProjectFiles.isEmpty()) { projectUrl = QUrl::fromLocalFile(dir.absoluteFilePath(potentialProjectFiles.value(0))); } } else { QTextStream qerr(stderr); qerr << "Invalid project: " << project << " - should be either a path to a .kdev4 file or a directory containing a .kdev4 file"; return 1; } qDebug(APP) << "Attempting to find a suitable session for project" << projectUrl; const auto sessionInfos = findSessionsWithProject(availableSessionInfos, projectUrl); qDebug(APP) << "Found matching sessions:" << sessionInfos.size(); if (!sessionInfos.isEmpty()) { // TODO: If there's more than one match: Allow the user to select which session to open? qDebug(APP) << "Attempting to open session:" << sessionInfos.at(0).name; session = sessionInfos.at(0).uuid.toString(); } } KDevIDEExtension::init(); qDebug(APP) << "Attempting to initialize session:" << session; if(!Core::initialize(Core::Default, session)) return 5; // register a DBUS service for this process, so that we can open files in it from other invocations QDBusConnection::sessionBus().registerService(QStringLiteral("org.kdevelop.kdevelop-%1").arg(app.applicationPid())); Core* core = Core::self(); if (!QProcessEnvironment::systemEnvironment().contains(QStringLiteral("KDEV_DISABLE_WELCOMEPAGE"))) { core->pluginController()->loadPlugin(QStringLiteral("KDevWelcomePage")); } const auto fetchUrlStrings = parser.values(QStringLiteral("fetch")); for (const auto& fetchUrlString : fetchUrlStrings) { core->projectControllerInternal()->fetchProjectFromUrl(QUrl::fromUserInput(fetchUrlString)); } const QString debugStr = QStringLiteral("debug"); if ( parser.isSet(debugStr) ) { Q_ASSERT( !debugeeName.isEmpty() ); QString launchName = debugeeName; KDevelop::LaunchConfiguration* launch = nullptr; qCDebug(APP) << launchName; foreach (KDevelop::LaunchConfiguration *l, core->runControllerInternal()->launchConfigurationsInternal()) { qCDebug(APP) << l->name(); if (l->name() == launchName) { launch = l; } } KDevelop::LaunchConfigurationType *type = nullptr; foreach (KDevelop::LaunchConfigurationType *t, core->runController()->launchConfigurationTypes()) { qCDebug(APP) << t->id(); if (t->id() == QLatin1String("Native Application")) { type = t; break; } } if (!type) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot find native launch configuration type") << endl; return 1; } if (launch && launch->type()->id() != QLatin1String("Native Application")) launch = nullptr; if (launch && launch->launcherForMode(debugStr) != parser.value(debugStr)) launch = nullptr; if (!launch) { qCDebug(APP) << launchName << "not found, creating a new one"; QPair launcher; launcher.first = debugStr; foreach (KDevelop::ILauncher *l, type->launchers()) { if (l->id() == parser.value(debugStr)) { if (l->supportedModes().contains(debugStr)) { launcher.second = l->id(); } } } if (launcher.second.isEmpty()) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot find launcher %1", parser.value(debugStr)) << endl; return 1; } KDevelop::ILaunchConfiguration* ilaunch = core->runController()->createLaunchConfiguration(type, launcher, nullptr, launchName); launch = static_cast(ilaunch); } type->configureLaunchFromCmdLineArguments(launch->config(), debugArgs); launch->config().writeEntry("Break on Start", true); core->runControllerInternal()->setDefaultLaunch(launch); core->runControllerInternal()->execute(debugStr, launch); } else { openFiles(initialFiles); for(const auto& urlinfo: qAsConst(initialDirectories)) core->projectController()->openProjectForUrl(urlinfo.url); } #if KDEVELOP_SINGLE_APP // Set up remote arguments. QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived, &app, &KDevelopApplication::remoteArguments); QObject::connect(&app, &SharedTools::QtSingleApplication::fileOpenRequest, &app, &KDevelopApplication::fileOpenRequested); #endif qCDebug(APP) << "Done startup" << "- took:" << timer.elapsed() << "ms"; timer.invalidate(); return app.exec(); } diff --git a/kdevplatform/debugger/breakpoint/breakpointmodel.cpp b/kdevplatform/debugger/breakpoint/breakpointmodel.cpp index ab4efd40c0..40a175dbb4 100644 --- a/kdevplatform/debugger/breakpoint/breakpointmodel.cpp +++ b/kdevplatform/debugger/breakpoint/breakpointmodel.cpp @@ -1,666 +1,666 @@ /* 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, see . */ #include "breakpointmodel.h" #include #include #include #include #include -#include +#include #include "../interfaces/icore.h" #include "../interfaces/idebugcontroller.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/idocument.h" #include "../interfaces/ipartcontroller.h" #include #include #include #include #include "breakpoint.h" #include #include #include #include #define IF_DEBUG(x) using namespace KDevelop; using namespace KTextEditor; namespace { IBreakpointController* breakpointController() { KDevelop::ICore* core = KDevelop::ICore::self(); if (!core) { return nullptr; } IDebugController* controller = core->debugController(); if (!controller) { return nullptr; } IDebugSession* session = controller->currentSession(); return session ? session->breakpointController() : nullptr; } } // anonymous namespace class KDevelop::BreakpointModelPrivate { public: bool dirty = false; bool dontUpdateMarks = false; QList breakpoints; }; BreakpointModel::BreakpointModel(QObject* parent) : QAbstractTableModel(parent), d(new BreakpointModelPrivate) { connect(this, &BreakpointModel::dataChanged, this, &BreakpointModel::updateMarks); if (KDevelop::ICore::self()->partController()) { //TODO remove if foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts()) slotPartAdded(p); connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &BreakpointModel::slotPartAdded); } connect (KDevelop::ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &BreakpointModel::textDocumentCreated); connect (KDevelop::ICore::self()->documentController(), &IDocumentController::documentSaved, this, &BreakpointModel::documentSaved); } BreakpointModel::~BreakpointModel() { qDeleteAll(d->breakpoints); } void BreakpointModel::slotPartAdded(KParts::Part* part) { if (auto doc = qobject_cast(part)) { MarkInterface *iface = dynamic_cast(doc); if( !iface ) return; iface->setMarkDescription((MarkInterface::MarkTypes)BreakpointMark, i18n("Breakpoint")); iface->setMarkPixmap((MarkInterface::MarkTypes)BreakpointMark, *breakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)PendingBreakpointMark, *pendingBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)ReachedBreakpointMark, *reachedBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)DisabledBreakpointMark, *disabledBreakpointPixmap()); iface->setEditableMarks( MarkInterface::Bookmark | BreakpointMark ); updateMarks(); } } void BreakpointModel::textDocumentCreated(KDevelop::IDocument* doc) { KTextEditor::MarkInterface *iface = qobject_cast(doc->textDocument()); if (iface) { // can't use new signal slot syntax here, MarkInterface is not a QObject connect(doc->textDocument(), SIGNAL(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)), this, SLOT(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction))); connect(doc->textDocument(), SIGNAL(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), SLOT(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&))); } } void BreakpointModel::markContextMenuRequested(Document* document, Mark mark, const QPoint &pos, bool& handled) { int type = mark.type; qCDebug(DEBUGGER) << type; Breakpoint *b = nullptr; if ((type & AllBreakpointMarks)) { b = breakpoint(document->url(), mark.line); if (!b) { QMessageBox::critical(nullptr, i18n("Breakpoint not found"), i18n("Couldn't find breakpoint at %1:%2", document->url().toString(), mark.line)); } } else if (!(type & MarkInterface::Bookmark)) // neither breakpoint nor bookmark return; QMenu menu; // TODO: needs qwidget QAction* breakpointAction = menu.addAction(QIcon::fromTheme(QStringLiteral("breakpoint")), i18n("&Breakpoint")); breakpointAction->setCheckable(true); breakpointAction->setChecked(b); QAction* enableAction = nullptr; if (b) { enableAction = b->enabled() ? menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Disable Breakpoint")) : menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("&Enable Breakpoint")); } menu.addSeparator(); QAction* bookmarkAction = menu.addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("&Bookmark")); bookmarkAction->setCheckable(true); bookmarkAction->setChecked((type & MarkInterface::Bookmark)); QAction* triggeredAction = menu.exec(pos); if (triggeredAction) { if (triggeredAction == bookmarkAction) { KTextEditor::MarkInterface *iface = qobject_cast(document); if ((type & MarkInterface::Bookmark)) iface->removeMark(mark.line, MarkInterface::Bookmark); else iface->addMark(mark.line, MarkInterface::Bookmark); } else if (triggeredAction == breakpointAction) { if (b) { b->setDeleted(); } else { Breakpoint *breakpoint = addCodeBreakpoint(document->url(), mark.line); MovingInterface *moving = qobject_cast(document); if (moving) { MovingCursor* cursor = moving->newMovingCursor(Cursor(mark.line, 0)); // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(document, SIGNAL(aboutToDeleteMovingInterfaceContent(Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(Document*)), Qt::UniqueConnection); breakpoint->setMovingCursor(cursor); } } } else if (triggeredAction == enableAction) { b->setData(Breakpoint::EnableColumn, b->enabled() ? Qt::Unchecked : Qt::Checked); } } handled = true; } QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) return QVariant(); if (role == Qt::DecorationRole ) { if (section == 0) return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); else if (section == 1) return QIcon::fromTheme(QStringLiteral("system-switch-user")); } if (role == Qt::DisplayRole) { if (section == 0 || section == 1) return QString(); if (section == 2) return i18n("Type"); if (section == 3) return i18n("Location"); if (section == 4) return i18n("Condition"); } if (role == Qt::ToolTipRole) { if (section == 0) return i18n("Active status"); if (section == 1) return i18n("State"); return headerData(section, orientation, Qt::DisplayRole); } return QVariant(); } Qt::ItemFlags BreakpointModel::flags(const QModelIndex &index) const { /* FIXME: all this logic must be in item */ if (!index.isValid()) return Qt::NoItemFlags; if (index.column() == 0) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); if (index.column() == Breakpoint::ConditionColumn) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); return static_cast(Qt::ItemIsEnabled | Qt::ItemIsSelectable); } QModelIndex BreakpointModel::breakpointIndex(KDevelop::Breakpoint* b, int column) { int row = d->breakpoints.indexOf(b); if (row == -1) return QModelIndex(); return index(row, column); } bool KDevelop::BreakpointModel::removeRows(int row, int count, const QModelIndex& parent) { if (count < 1 || (row < 0) || (row + count) > rowCount(parent)) return false; IBreakpointController* controller = breakpointController(); beginRemoveRows(parent, row, row+count-1); for (int i=0; i < count; ++i) { Breakpoint* b = d->breakpoints.at(row); b->m_deleted = true; if (controller) controller->breakpointAboutToBeDeleted(row); d->breakpoints.removeAt(row); b->m_model = nullptr; // To be changed: the controller is currently still responsible for deleting the breakpoint // object } endRemoveRows(); updateMarks(); scheduleSave(); return true; } int KDevelop::BreakpointModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return d->breakpoints.count(); } return 0; } int KDevelop::BreakpointModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 5; } QVariant BreakpointModel::data(const QModelIndex& index, int role) const { if (!index.parent().isValid() && index.row() < d->breakpoints.count()) { return d->breakpoints.at(index.row())->data(index.column(), role); } return QVariant(); } bool KDevelop::BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.parent().isValid() && index.row() < d->breakpoints.count() && (role == Qt::EditRole || role == Qt::CheckStateRole)) { return d->breakpoints.at(index.row())->setData(index.column(), value); } return false; } void BreakpointModel::updateState(int row, Breakpoint::BreakpointState state) { Breakpoint* breakpoint = d->breakpoints.at(row); if (state != breakpoint->m_state) { breakpoint->m_state = state; reportChange(breakpoint, Breakpoint::StateColumn); } } void BreakpointModel::updateHitCount(int row, int hitCount) { Breakpoint* breakpoint = d->breakpoints.at(row); if (hitCount != breakpoint->m_hitCount) { breakpoint->m_hitCount = hitCount; reportChange(breakpoint, Breakpoint::HitCountColumn); } } void BreakpointModel::updateErrorText(int row, const QString& errorText) { Breakpoint* breakpoint = d->breakpoints.at(row); if (breakpoint->m_errorText != errorText) { breakpoint->m_errorText = errorText; reportChange(breakpoint, Breakpoint::StateColumn); } if (!errorText.isEmpty()) { emit error(row, errorText); } } void BreakpointModel::notifyHit(int row) { emit hit(row); } void BreakpointModel::markChanged( KTextEditor::Document *document, KTextEditor::Mark mark, KTextEditor::MarkInterface::MarkChangeAction action) { int type = mark.type; /* Is this a breakpoint mark, to begin with? */ if (!(type & AllBreakpointMarks)) return; if (action == KTextEditor::MarkInterface::MarkAdded) { Breakpoint *b = breakpoint(document->url(), mark.line); if (b) { //there was already a breakpoint, so delete instead of adding b->setDeleted(); return; } Breakpoint *breakpoint = addCodeBreakpoint(document->url(), mark.line); KTextEditor::MovingInterface *moving = qobject_cast(document); if (moving) { KTextEditor::MovingCursor* cursor = moving->newMovingCursor(KTextEditor::Cursor(mark.line, 0)); // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(document, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), Qt::UniqueConnection); breakpoint->setMovingCursor(cursor); } } else { // Find this breakpoint and delete it Breakpoint *b = breakpoint(document->url(), mark.line); if (b) { b->setDeleted(); } } #if 0 if ( KDevelop::ICore::self()->documentController()->activeDocument() && KDevelop::ICore::self()->documentController()->activeDocument()->textDocument() == document ) { //bring focus back to the editor // TODO probably want a different command here KDevelop::ICore::self()->documentController()->activateDocument(KDevelop::ICore::self()->documentController()->activeDocument()); } #endif } const QPixmap* BreakpointModel::breakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Active, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::pendingBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Normal, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::reachedBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Selected, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::disabledBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Disabled, QIcon::Off); return &pixmap; } void BreakpointModel::toggleBreakpoint(const QUrl& url, const KTextEditor::Cursor& cursor) { Breakpoint *b = breakpoint(url, cursor.line()); if (b) { b->setDeleted(); } else { addCodeBreakpoint(url, cursor.line()); } } void BreakpointModel::reportChange(Breakpoint* breakpoint, Breakpoint::Column column) { // note: just a portion of Breakpoint::Column is displayed in this model! if (column >= 0 && column < columnCount()) { QModelIndex idx = breakpointIndex(breakpoint, column); Q_ASSERT(idx.isValid()); // make sure we don't pass invalid indices to dataChanged() emit dataChanged(idx, idx); } if (IBreakpointController* controller = breakpointController()) { int row = d->breakpoints.indexOf(breakpoint); Q_ASSERT(row != -1); controller->breakpointModelChanged(row, ColumnFlags(1 << column)); } scheduleSave(); } uint BreakpointModel::breakpointType(Breakpoint *breakpoint) const { uint type = BreakpointMark; if (!breakpoint->enabled()) { type = DisabledBreakpointMark; } else if (breakpoint->hitCount() > 0) { type = ReachedBreakpointMark; } else if (breakpoint->state() == Breakpoint::PendingState) { type = PendingBreakpointMark; } return type; } void KDevelop::BreakpointModel::updateMarks() { if (d->dontUpdateMarks) return; //add marks foreach (Breakpoint* breakpoint, d->breakpoints) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (breakpoint->line() == -1) continue; IDocument *doc = ICore::self()->documentController()->documentForUrl(breakpoint->url()); if (!doc) continue; KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; uint type = breakpointType(breakpoint); IF_DEBUG( qCDebug(DEBUGGER) << type << breakpoint->url() << mark->mark(breakpoint->line()); ) { QSignalBlocker blocker(doc->textDocument()); if (mark->mark(breakpoint->line()) & AllBreakpointMarks) { if (!(mark->mark(breakpoint->line()) & type)) { mark->removeMark(breakpoint->line(), AllBreakpointMarks); mark->addMark(breakpoint->line(), type); } } else { mark->addMark(breakpoint->line(), type); } } } //remove marks foreach (IDocument *doc, ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; { QSignalBlocker blocker(doc->textDocument()); foreach (KTextEditor::Mark *m, mark->marks()) { if (!(m->type & AllBreakpointMarks)) continue; IF_DEBUG( qCDebug(DEBUGGER) << m->line << m->type; ) foreach (Breakpoint* breakpoint, d->breakpoints) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (doc->url() == breakpoint->url() && m->line == breakpoint->line()) { goto continueNextMark; } } mark->removeMark(m->line, AllBreakpointMarks); continueNextMark:; } } } } void BreakpointModel::documentSaved(KDevelop::IDocument* doc) { IF_DEBUG( qCDebug(DEBUGGER); ) foreach (Breakpoint* breakpoint, d->breakpoints) { if (breakpoint->movingCursor()) { if (breakpoint->movingCursor()->document() != doc->textDocument()) continue; if (breakpoint->movingCursor()->line() == breakpoint->line()) continue; d->dontUpdateMarks = true; breakpoint->setLine(breakpoint->movingCursor()->line()); d->dontUpdateMarks = false; } } } void BreakpointModel::aboutToDeleteMovingInterfaceContent(KTextEditor::Document* document) { foreach (Breakpoint* breakpoint, d->breakpoints) { if (breakpoint->movingCursor() && breakpoint->movingCursor()->document() == document) { breakpoint->setMovingCursor(nullptr); } } } void BreakpointModel::load() { KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); int count = breakpoints.readEntry("number", 0); if (count == 0) return; beginInsertRows(QModelIndex(), 0, count - 1); for (int i = 0; i < count; ++i) { if (!breakpoints.group(QString::number(i)).readEntry("kind", "").isEmpty()) { new Breakpoint(this, breakpoints.group(QString::number(i))); } } endInsertRows(); } void BreakpointModel::save() { d->dirty = false; KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); breakpoints.writeEntry("number", d->breakpoints.count()); int i = 0; foreach (Breakpoint* b, d->breakpoints) { KConfigGroup g = breakpoints.group(QString::number(i)); b->save(g); ++i; } breakpoints.sync(); } void BreakpointModel::scheduleSave() { if (d->dirty) return; d->dirty = true; QTimer::singleShot(0, this, &BreakpointModel::save); } QList KDevelop::BreakpointModel::breakpoints() const { return d->breakpoints; } Breakpoint* BreakpointModel::breakpoint(int row) const { if (row >= d->breakpoints.count()) return nullptr; return d->breakpoints.at(row); } Breakpoint* BreakpointModel::addCodeBreakpoint() { beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::CodeBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QUrl& url, int line) { Breakpoint* n = addCodeBreakpoint(); n->setLocation(url, line); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QString& expression) { Breakpoint* n = addCodeBreakpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addWatchpoint() { beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::WriteBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addWatchpoint(const QString& expression) { Breakpoint* n = addWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addReadWatchpoint() { beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::ReadBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addReadWatchpoint(const QString& expression) { Breakpoint* n = addReadWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint() { beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count()); Breakpoint* n = new Breakpoint(this, Breakpoint::AccessBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint(const QString& expression) { Breakpoint* n = addAccessWatchpoint(); n->setExpression(expression); return n; } void BreakpointModel::registerBreakpoint(Breakpoint* breakpoint) { Q_ASSERT(!d->breakpoints.contains(breakpoint)); int row = d->breakpoints.size(); d->breakpoints << breakpoint; if (IBreakpointController* controller = breakpointController()) { controller->breakpointAdded(row); } scheduleSave(); } Breakpoint* BreakpointModel::breakpoint(const QUrl& url, int line) const { foreach (Breakpoint* b, d->breakpoints) { if (b->url() == url && b->line() == line) { return b; } } return nullptr; } diff --git a/kdevplatform/interfaces/idocumentcontroller.h b/kdevplatform/interfaces/idocumentcontroller.h index 08cfed197b..e90f85c500 100644 --- a/kdevplatform/interfaces/idocumentcontroller.h +++ b/kdevplatform/interfaces/idocumentcontroller.h @@ -1,239 +1,239 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_IDOCUMENTCONTROLLER_H #define KDEVPLATFORM_IDOCUMENTCONTROLLER_H #include #include #include -#include +#include #include "interfacesexport.h" #include "idocument.h" namespace KTextEditor { class View; } namespace KDevelop { class ICore; class KDEVPLATFORMINTERFACES_EXPORT IDocumentFactory { public: virtual ~IDocumentFactory() {} virtual IDocument* create(const QUrl&, ICore* ) = 0; }; /** * * Allows to access the open documents and also open new ones * * @class IDocumentController */ class KDEVPLATFORMINTERFACES_EXPORT IDocumentController: public QObject { Q_OBJECT public: enum DocumentActivation { DefaultMode = 0, /**Activate document and create a view if no other flags passed.*/ DoNotActivate = 1, /**Don't activate the Document.*/ DoNotCreateView = 2, /**Don't create and show the view for the Document.*/ DoNotFocus = 4, /**Don't change the keyboard focus.*/ DoNotAddToRecentOpen = 8, /**Don't add the document to the File/Open Recent menu.*/ }; Q_DECLARE_FLAGS(DocumentActivationParams, DocumentActivation) explicit IDocumentController(QObject *parent); /** * Finds the first document object corresponding to a given url. * * @param url The Url of the document. * @return The corresponding document, or null if not found. */ virtual KDevelop::IDocument* documentForUrl( const QUrl & url ) const = 0; /// @return The list of all open documents virtual QList openDocuments() const = 0; /** * Returns the curently active or focused document. * * @return The active document. */ virtual KDevelop::IDocument* activeDocument() const = 0; virtual void activateDocument( KDevelop::IDocument * document, const KTextEditor::Range& range = KTextEditor::Range::invalid() ) = 0; virtual void registerDocumentForMimetype( const QString&, KDevelop::IDocumentFactory* ) = 0; virtual bool saveAllDocuments(KDevelop::IDocument::DocumentSaveMode mode = KDevelop::IDocument::Default) = 0; virtual bool saveSomeDocuments(const QList& list, KDevelop::IDocument::DocumentSaveMode mode = KDevelop::IDocument::Default) = 0; virtual bool saveAllDocumentsForWindow(KParts::MainWindow* mw, IDocument::DocumentSaveMode mode, bool currentAreaOnly = false) = 0; /// Opens a text document containing the @p data text. virtual KDevelop::IDocument* openDocumentFromText( const QString& data ) = 0; virtual IDocumentFactory* factory(const QString& mime) const = 0; virtual KTextEditor::Document* globalTextEditorInstance()=0; /** * @returns the KTextEditor::View of the current document, in case it is a text document */ virtual KTextEditor::View* activeTextDocumentView() const = 0; public Q_SLOTS: /** * Opens a new or existing document. * * @param url The full Url of the document to open. * @param cursor The location information, if applicable. * @param activationParams Indicates whether to fully activate the document. * @param encoding the encoding for the document, the name must be accepted by QTextCodec, * if an empty encoding name is given, the document should fallback to its * own default encoding, e.g. the system encoding or the global user settings */ KDevelop::IDocument* openDocument( const QUrl &url, const KTextEditor::Cursor& cursor, DocumentActivationParams activationParams = {}, const QString& encoding = {}); /** * Opens a new or existing document. * * @param url The full Url of the document to open. * @param range The range of text to select, if applicable. * @param activationParams Indicates whether to fully activate the document * @param encoding the encoding for the document, the name must be accepted by QTextCodec, * if an empty encoding name is given, the document should fallback to its * own default encoding, e.g. the system encoding or the global user settings * @param buddy Optional buddy document. If 0, the registered IBuddyDocumentFinder * for the URL's mimetype will be queried to find a fitting buddy. * If a buddy was found (or passed) @p url will be opened next * to its buddy. * * @return The opened document */ virtual KDevelop::IDocument* openDocument( const QUrl &url, const KTextEditor::Range& range = KTextEditor::Range::invalid(), DocumentActivationParams activationParams = {}, const QString& encoding = {}, IDocument* buddy = nullptr) = 0; /** * Opens a document from the IDocument instance. * * @param doc The IDocument to add * @param range The location information, if applicable. * @param activationParams Indicates whether to fully activate the document. * @param buddy Optional buddy document. If 0, the registered IBuddyDocumentFinder * for the Documents mimetype will be queried to find a fitting buddy. * If a buddy was found (or passed) @p url will be opened next * to its buddy. */ virtual bool openDocument(IDocument* doc, const KTextEditor::Range& range = KTextEditor::Range::invalid(), DocumentActivationParams activationParams = {}, IDocument* buddy = nullptr) = 0; /** * Opens a new or existing document. * * @param url The full Url of the document to open. * @param prefName The name of the preferred KPart to open that document */ virtual KDevelop::IDocument* openDocument( const QUrl &url, const QString& prefName ) = 0; virtual bool closeAllDocuments() = 0; Q_SIGNALS: /// Emitted when the document has been activated. void documentActivated( KDevelop::IDocument* document ); /** * Emitted whenever the active cursor jumps from one document+cursor to another, * as e.g. caused by a call to openDocument(..). * * This is also emitted when a document is only activated. Then previousDocument is zero. */ void documentJumpPerformed( KDevelop::IDocument* newDocument, KTextEditor::Cursor newCursor, KDevelop::IDocument* previousDocument, KTextEditor::Cursor previousCursor); /// Emitted when a document has been saved. void documentSaved( KDevelop::IDocument* document ); /** * Emitted when a document has been opened. * * NOTE: The document may not be loaded from disk/network at this point. * NOTE: No views exist for the document at the time this signal is emitted. */ void documentOpened( KDevelop::IDocument* document ); /** * Emitted when a document has been loaded. * * NOTE: No views exist for the document at the time this signal is emitted. */ void documentLoaded( KDevelop::IDocument* document ); /** * Emitted when a text document has been loaded, and the text document created. * * NOTE: no views exist for the document at the time this signal is emitted. */ void textDocumentCreated( KDevelop::IDocument* document ); /// Emitted when a document has been closed. void documentClosed( KDevelop::IDocument* document ); /** * This is emitted when the document state(the relationship * between the file in the editor and the file stored on disk) changes. */ void documentStateChanged( KDevelop::IDocument* document ); /// This is emitted when the document content changed. void documentContentChanged( KDevelop::IDocument* document ); /** * Emitted when a document has been loaded, but before documentLoaded(..) is emitted. * * This allows parts of kdevplatform to prepare data-structures that can be used by other parts * during documentLoaded(..). */ void documentLoadedPrepare( KDevelop::IDocument* document ); /// Emitted when a document url has changed. void documentUrlChanged( KDevelop::IDocument* document ); friend class IDocument; }; } // namespace KDevelop Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::IDocumentController::DocumentActivationParams) #endif diff --git a/kdevplatform/interfaces/ipartcontroller.h b/kdevplatform/interfaces/ipartcontroller.h index c5a26f4a23..458c3244ee 100644 --- a/kdevplatform/interfaces/ipartcontroller.h +++ b/kdevplatform/interfaces/ipartcontroller.h @@ -1,56 +1,56 @@ /*************************************************************************** * Copyright 2009 Andreas Pakulat * * * * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_IPARTCONTROLLER_H #define KDEVPLATFORM_IPARTCONTROLLER_H -#include - #include "interfacesexport.h" +#include + class KPluginFactory; namespace KTextEditor { class Editor; } namespace KDevelop { class ICore; class KDEVPLATFORMINTERFACES_EXPORT IPartController : public KParts::PartManager { Q_OBJECT public: explicit IPartController( QWidget* parent ); static KPluginFactory* findPartFactory( const QString& mimetype, const QString& parttype, const QString& preferredName = QString() ); KParts::Part* createPart( const QString& mimetype, const QString& prefName = QString() ); /** * Returns the global editor instance. */ virtual KTextEditor::Editor* editorPart() const = 0; }; } #endif diff --git a/kdevplatform/interfaces/iplugincontroller.cpp b/kdevplatform/interfaces/iplugincontroller.cpp index b8a5202d08..05a583cf15 100644 --- a/kdevplatform/interfaces/iplugincontroller.cpp +++ b/kdevplatform/interfaces/iplugincontroller.cpp @@ -1,43 +1,44 @@ /* This file is part of the KDE project Copyright 2004 Alexander Dymo Copyright 2006 Matt Rogers Based on code from Kopete Copyright 2002-2003 Martijn Klingens 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 "iplugincontroller.h" -#include #include "ipluginversion.h" +#include + namespace KDevelop { IPluginController::IPluginController( QObject* parent ) : QObject( parent ) { } IPluginController::~IPluginController() { } } diff --git a/kdevplatform/interfaces/iruncontroller.h b/kdevplatform/interfaces/iruncontroller.h index 18dbe9bb74..34dcbe823e 100644 --- a/kdevplatform/interfaces/iruncontroller.h +++ b/kdevplatform/interfaces/iruncontroller.h @@ -1,169 +1,169 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda 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 KDEVPLATFORM_IRUNCONTROLLER_H #define KDEVPLATFORM_IRUNCONTROLLER_H -#include - #include "interfacesexport.h" +#include + class KJob; namespace KDevelop { class IProject; class ILaunchMode; class ILaunchConfiguration; class LaunchConfigurationType; /** * The main controller for running processes. */ class KDEVPLATFORMINTERFACES_EXPORT IRunController : public KJobTrackerInterface { Q_OBJECT public: ///Constructor. explicit IRunController(QObject *parent); /** * Interrogate the current managed jobs */ virtual QList currentJobs() const = 0; /** * An enumeration of the possible states for the run controller. */ enum State { Idle /**< No processes are currently running */, Running /**< processes are currently running */ }; Q_ENUM(State) /** * Get a list of all launch modes that the app knows * @returns a list of registered launch modes */ virtual QList launchModes() const = 0; /** * Get a list of all available launch configurations */ virtual QList launchConfigurations() const = 0; /** * Get a specific launch mode based using its \a id * @param id the identifier of the launchmode to get * @returns launch mode for the given id or 0 if no such mode is known */ virtual ILaunchMode* launchModeForId( const QString& id ) const = 0; /** * add @p mode to the list of registered launch modes * @param mode the mode to be registered */ virtual void addLaunchMode( ILaunchMode* mode ) = 0; /** * remove @p mode from the list of registered launch modes * @param mode the mode to be unregistered */ virtual void removeLaunchMode( ILaunchMode* mode ) = 0; /** * Get a list of all configuration types that are registered * @returns a list of run configuration types */ virtual QList launchConfigurationTypes() const = 0; /** * Adds @p type to the list of known run config types * @param type the new run configuration type */ virtual void addConfigurationType( LaunchConfigurationType* type ) = 0; /** * Removes @p type from the list of known run config types * @param type run configuration type that should be removed */ virtual void removeConfigurationType( LaunchConfigurationType* type ) = 0; /** * Executes the default launch in the given mode * @param runMode the launch mode to start with */ virtual void executeDefaultLaunch( const QString& runMode ) = 0; virtual KJob* execute(const QString& launchMode, ILaunchConfiguration* launch) = 0; /** * tries to find a launch config type for the given @p id * @param id the id of the launch configuration type to search * @returns the launch configuration type if found, or 0 otherwise */ virtual LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) = 0; /** * Creates a new launch configuration in the given project or in the session if no project * was provided using the given launch configuration type and launcher. The configuration * is also added to the list of configurations in the runcontroller. * * @param type the launch configuration type to be used for the new config * @param launcher the mode and id of the launcher to be used in the config * @param project the project in which the launch configuration should be stored * @param name the name of the new launch configuration, if this is empty a new default name will be generated * @returns a new launch configuration */ virtual ILaunchConfiguration* createLaunchConfiguration( LaunchConfigurationType* type, const QPair& launcher, KDevelop::IProject* project = nullptr, const QString& name = QString() ) = 0; /// Opens a dialog to setup new launch configurations, or to change the existing ones. virtual void showConfigurationDialog() const = 0; public Q_SLOTS: /** * Request for all running processes to be killed. */ virtual void stopAllProcesses() = 0; Q_SIGNALS: /** * Notify that the state of the run controller has changed to \a {state}. */ void runStateChanged(State state); /** * Notify that a new job has been registered. */ void jobRegistered(KJob* job); /** * Notify that a job has been unregistered. */ void jobUnregistered(KJob* job); }; } #endif // KDEVPLATFORM_IRUNCONTROLLER_H diff --git a/kdevplatform/interfaces/isession.h b/kdevplatform/interfaces/isession.h index 7cd87fed1e..ece32b64cc 100644 --- a/kdevplatform/interfaces/isession.h +++ b/kdevplatform/interfaces/isession.h @@ -1,77 +1,78 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat 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 KDEVPLATFORM_ISESSION_H #define KDEVPLATFORM_ISESSION_H #include "interfacesexport.h" + +#include + #include #include -#include - class QUuid; class QString; namespace KDevelop { class IPlugin; /** * @class ISession */ class KDEVPLATFORMINTERFACES_EXPORT ISession : public QObject { Q_OBJECT public: explicit ISession( QObject* parent = nullptr ); ~ISession() override; /** * A short string nicely identifying the session, including contained projects * * The string is empty if the session is empty and has no name. */ virtual QString description() const = 0; virtual QString name() const = 0; virtual QList containedProjects() const = 0; virtual void setContainedProjects( const QList& projects ) = 0; virtual QUrl pluginDataArea( const IPlugin* ) = 0; virtual KSharedConfigPtr config() = 0; virtual QUuid id() const = 0; /** * Mark session as temporary. It will then be deleted on close. * * This is mainly useful for unit tests etc. */ virtual void setTemporary(bool temp) = 0; virtual bool isTemporary() const = 0; Q_SIGNALS: void sessionUpdated( KDevelop::ISession* session ); }; } Q_DECLARE_METATYPE( KDevelop::ISession* ) #endif diff --git a/kdevplatform/language/assistant/renameassistant.cpp b/kdevplatform/language/assistant/renameassistant.cpp index eebd25bc13..c156cfc704 100644 --- a/kdevplatform/language/assistant/renameassistant.cpp +++ b/kdevplatform/language/assistant/renameassistant.cpp @@ -1,238 +1,238 @@ /* Copyright 2010 Olivier de Gaalon 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 "renameassistant.h" #include "renameaction.h" #include "renamefileaction.h" #include #include "../codegen/basicrefactoring.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainutils.h" #include "../duchain/declaration.h" #include "../duchain/functiondefinition.h" #include "../duchain/classfunctiondeclaration.h" #include #include #include -#include +#include #include using namespace KDevelop; namespace { bool rangesConnect(const KTextEditor::Range& firstRange, const KTextEditor::Range& secondRange) { return !firstRange.intersect(secondRange + KTextEditor::Range(0, -1, 0, +1)).isEmpty(); } Declaration* declarationForChangedRange(KTextEditor::Document* doc, const KTextEditor::Range& changed) { const KTextEditor::Cursor cursor(changed.start()); Declaration* declaration = DUChainUtils::itemUnderCursor(doc->url(), cursor).declaration; //If it's null we could be appending, but there's a case where appending gives a wrong decl //and not a null declaration ... "type var(init)", so check for that too if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { declaration = DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(cursor.line(), cursor.column()-1)).declaration; } //In this case, we may either not have a decl at the cursor, or we got a decl, but are editing its use. //In either of those cases, give up and return 0 if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { return nullptr; } return declaration; } } class KDevelop::RenameAssistantPrivate { public: explicit RenameAssistantPrivate(RenameAssistant* qq) : q(qq) , m_isUseful(false) , m_renameFile(false) { } void reset() { q->doHide(); q->clearActions(); m_oldDeclarationName = Identifier(); m_newDeclarationRange.reset(); m_oldDeclarationUses.clear(); m_isUseful = false; m_renameFile = false; } RenameAssistant* q; KDevelop::Identifier m_oldDeclarationName; QString m_newDeclarationName; KDevelop::PersistentMovingRange::Ptr m_newDeclarationRange; QVector m_oldDeclarationUses; bool m_isUseful; bool m_renameFile; KTextEditor::Cursor m_lastChangedLocation; QPointer m_lastChangedDocument = nullptr; }; RenameAssistant::RenameAssistant(ILanguageSupport* supportedLanguage) : StaticAssistant(supportedLanguage) , d(new RenameAssistantPrivate(this)) { } RenameAssistant::~RenameAssistant() { } QString RenameAssistant::title() const { return i18n("Rename"); } bool RenameAssistant::isUseful() const { return d->m_isUseful; } void RenameAssistant::textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, const QString& removedText) { clearActions(); d->m_lastChangedLocation = invocationRange.end(); d->m_lastChangedDocument = doc; if (!supportedLanguage()->refactoring()) { qCWarning(LANGUAGE) << "Refactoring not supported. Aborting."; return; } if (!doc) return; //If the inserted text isn't valid for a variable name, consider the editing ended QRegExp validDeclName(QStringLiteral("^[0-9a-zA-Z_]*$")); if (removedText.isEmpty() && !validDeclName.exactMatch(doc->text(invocationRange))) { d->reset(); return; } const QUrl url = doc->url(); const IndexedString indexedUrl(url); DUChainReadLocker lock; //If we've stopped editing m_newDeclarationRange or switched the view, // reset and see if there's another declaration being edited if (!d->m_newDeclarationRange.data() || !rangesConnect(d->m_newDeclarationRange->range(), invocationRange) || d->m_newDeclarationRange->document() != indexedUrl) { d->reset(); Declaration* declAtCursor = declarationForChangedRange(doc, invocationRange); if (!declAtCursor) { // not editing a declaration return; } if (supportedLanguage()->refactoring()->shouldRenameUses(declAtCursor)) { const auto declUses = declAtCursor->uses(); if (declUses.isEmpty()) { // new declaration has no uses return; } for (auto it = declUses.constBegin(); it != declUses.constEnd(); ++it) { foreach(const RangeInRevision range, it.value()) { KTextEditor::Range currentRange = declAtCursor->transformFromLocalRevision(range); if(currentRange.isEmpty() || doc->text(currentRange) != declAtCursor->identifier().identifier().str()) { return; // One of the uses is invalid. Maybe the replacement has already been performed. } } } d->m_oldDeclarationUses = RevisionedFileRanges::convert(declUses); } else if (supportedLanguage()->refactoring()->shouldRenameFile(declAtCursor)) { d->m_renameFile = true; } else { // not a valid declaration return; } d->m_oldDeclarationName = declAtCursor->identifier(); KTextEditor::Range newRange = declAtCursor->rangeInCurrentRevision(); if (removedText.isEmpty() && newRange.intersect(invocationRange).isEmpty()) { newRange = newRange.encompass(invocationRange); //if text was added to the ends, encompass it } d->m_newDeclarationRange = new PersistentMovingRange(newRange, indexedUrl, true); } //Unfortunately this happens when you make a selection including one end of the decl's range and replace it if (removedText.isEmpty() && d->m_newDeclarationRange->range().intersect(invocationRange).isEmpty()) { d->m_newDeclarationRange = new PersistentMovingRange( d->m_newDeclarationRange->range().encompass(invocationRange), indexedUrl, true); } d->m_newDeclarationName = doc->text(d->m_newDeclarationRange->range()).trimmed(); if (d->m_newDeclarationName == d->m_oldDeclarationName.toString()) { d->reset(); return; } if (d->m_renameFile && supportedLanguage()->refactoring()->newFileName(url, d->m_newDeclarationName) == url.fileName()) { // no change, don't do anything return; } d->m_isUseful = true; IAssistantAction::Ptr action; if (d->m_renameFile) { action = new RenameFileAction(supportedLanguage()->refactoring(), url, d->m_newDeclarationName); } else { action = new RenameAction(d->m_oldDeclarationName, d->m_newDeclarationName, d->m_oldDeclarationUses); } connect(action.data(), &IAssistantAction::executed, this, [&] { d->reset(); }); addAction(action); emit actionsChanged(); } KTextEditor::Range KDevelop::RenameAssistant::displayRange() const { if ( !d->m_lastChangedDocument ) { return {}; } auto range = d->m_lastChangedDocument->wordRangeAt(d->m_lastChangedLocation); qCDebug(LANGUAGE) << "range:" << range; return range; } #include "moc_renameassistant.cpp" diff --git a/kdevplatform/language/backgroundparser/documentchangetracker.cpp b/kdevplatform/language/backgroundparser/documentchangetracker.cpp index 2ad193fda8..00f750ca76 100644 --- a/kdevplatform/language/backgroundparser/documentchangetracker.cpp +++ b/kdevplatform/language/backgroundparser/documentchangetracker.cpp @@ -1,489 +1,490 @@ /* * This file is part of KDevelop * * Copyright 2010 David Nolden * * 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 "documentchangetracker.h" -#include -#include - #include #include #include #include #include #include "backgroundparser.h" #include + +#include +#include + #include // Can be used to disable the 'clever' updating logic that ignores whitespace-only changes and such. // #define ALWAYS_UPDATE using namespace KTextEditor; /** * @todo Track the exact changes to the document, and then: * Do not reparse if: * - Comment added/changed * - Newlines added/changed (ready) * Complete the document for validation: * - Incomplete for-loops * - ... * Only reparse after a statement was completed (either with statement-completion or manually), or after the cursor was switched away * Incremental parsing: * - All changes within a local function (or function parameter context): Update only the context (and all its importers) * * @todo: Prevent recursive updates after insignificant changes * (whitespace changes, or changes that don't affect publically visible stuff, eg. local incremental changes) * -> Maybe alter the file-modification caches directly * */ namespace KDevelop { /** * Internal helper class for RevisionLockerAndClearer * */ class RevisionLockerAndClearerPrivate : public QObject { Q_OBJECT public: RevisionLockerAndClearerPrivate(DocumentChangeTracker* tracker, qint64 revision); ~RevisionLockerAndClearerPrivate() override; inline qint64 revision() const { return m_revision; } private: friend class RevisionLockerAndClearer; QPointer m_tracker; qint64 m_revision; }; DocumentChangeTracker::DocumentChangeTracker( KTextEditor::Document* document ) : m_needUpdate(false) , m_document(document) , m_moving(nullptr) , m_url(IndexedString(document->url())) { Q_ASSERT(document); Q_ASSERT(document->url().isValid()); connect(document, &Document::textInserted, this, &DocumentChangeTracker::textInserted); connect(document, &Document::lineWrapped, this, &DocumentChangeTracker::lineWrapped); connect(document, &Document::lineUnwrapped, this, &DocumentChangeTracker::lineUnwrapped); connect(document, &Document::textRemoved, this, &DocumentChangeTracker::textRemoved); connect(document, &Document::destroyed, this, &DocumentChangeTracker::documentDestroyed); connect(document, &Document::documentSavedOrUploaded, this, &DocumentChangeTracker::documentSavedOrUploaded); m_moving = dynamic_cast(document); Q_ASSERT(m_moving); // can't use new connect syntax here, MovingInterface is not a QObject connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); reset(); } QList< QPair< KTextEditor::Range, QString > > DocumentChangeTracker::completions() const { VERIFY_FOREGROUND_LOCKED QList< QPair< KTextEditor::Range , QString > > ret; return ret; } void DocumentChangeTracker::reset() { VERIFY_FOREGROUND_LOCKED // We don't reset the insertion here, as it may continue m_needUpdate = false; m_revisionAtLastReset = acquireRevision(m_moving->revision()); Q_ASSERT(m_revisionAtLastReset); } RevisionReference DocumentChangeTracker::currentRevision() { VERIFY_FOREGROUND_LOCKED return acquireRevision(m_moving->revision()); } RevisionReference DocumentChangeTracker::revisionAtLastReset() const { VERIFY_FOREGROUND_LOCKED return m_revisionAtLastReset; } bool DocumentChangeTracker::needUpdate() const { VERIFY_FOREGROUND_LOCKED return m_needUpdate; } void DocumentChangeTracker::updateChangedRange(int delay) { // Q_ASSERT(m_moving->revision() != m_revisionAtLastReset->revision()); // May happen after reload // When reloading, textRemoved is called with an invalid m_document->url(). For that reason, we use m_url instead. ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); if(needUpdate()) { ICore::self()->languageController()->backgroundParser()->addDocument(m_url, TopDUContext::AllDeclarationsContextsAndUses, 0, nullptr, ParseJob::IgnoresSequentialProcessing, delay); } } static Cursor cursorAdd(Cursor c, const QString& text) { c.setLine(c.line() + text.count(QLatin1Char('\n'))); c.setColumn(c.column() + (text.length() - qMin(0, text.lastIndexOf(QLatin1Char('\n'))))); return c; } int DocumentChangeTracker::recommendedDelay(KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& text, bool removal) { const auto languages = ICore::self()->languageController()->languagesForUrl(doc->url()); int delay = ILanguageSupport::NoUpdateRequired; for (const auto& lang : languages) { // take the largest value, because NoUpdateRequired is -2 and we want to make sure // that if one language requires an update it actually happens delay = qMax(lang->suggestedReparseDelayForChange(doc, range, text, removal), delay); } return delay; } void DocumentChangeTracker::lineWrapped(KTextEditor::Document* document, const KTextEditor::Cursor& position) { textInserted(document, position, QStringLiteral("\n")); } void DocumentChangeTracker::lineUnwrapped(KTextEditor::Document* document, int line) { textRemoved(document, {{document->lineLength(line), line}, {0, line+1}}, QStringLiteral("\n")); } void DocumentChangeTracker::textInserted(Document* document, const Cursor& cursor, const QString& text) { /// TODO: get this data from KTextEditor directly, make its signal public KTextEditor::Range range(cursor, cursorAdd(cursor, text)); if(!m_lastInsertionPosition.isValid() || m_lastInsertionPosition == cursor) { m_currentCleanedInsertion.append(text); m_lastInsertionPosition = range.end(); } auto delay = recommendedDelay(document, range, text, false); m_needUpdate = delay != ILanguageSupport::NoUpdateRequired; updateChangedRange(delay); } void DocumentChangeTracker::textRemoved( Document* document, const KTextEditor::Range& oldRange, const QString& oldText ) { m_currentCleanedInsertion.clear(); m_lastInsertionPosition = KTextEditor::Cursor::invalid(); auto delay = recommendedDelay(document, oldRange, oldText, true); m_needUpdate = delay != ILanguageSupport::NoUpdateRequired; updateChangedRange(delay); } void DocumentChangeTracker::documentSavedOrUploaded(KTextEditor::Document* doc,bool) { ModificationRevision::clearModificationCache(IndexedString(doc->url())); } void DocumentChangeTracker::documentDestroyed( QObject* ) { m_document = nullptr; m_moving = nullptr; } DocumentChangeTracker::~DocumentChangeTracker() { Q_ASSERT(m_document); ModificationRevision::clearEditorRevisionForFile(KDevelop::IndexedString(m_document->url())); } Document* DocumentChangeTracker::document() const { return m_document; } MovingInterface* DocumentChangeTracker::documentMovingInterface() const { return m_moving; } void DocumentChangeTracker::aboutToInvalidateMovingInterfaceContent ( Document* ) { // Release all revisions! They must not be used any more. qCDebug(LANGUAGE) << "clearing all revisions"; m_revisionLocks.clear(); m_revisionAtLastReset = RevisionReference(); ModificationRevision::setEditorRevisionForFile(m_url, 0); } KDevelop::RangeInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::RangeInRevision range, qint64 fromRevision, qint64 toRevision) const { VERIFY_FOREGROUND_LOCKED if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { m_moving->transformCursor(range.start.line, range.start.column, KTextEditor::MovingCursor::MoveOnInsert, fromRevision, toRevision); m_moving->transformCursor(range.end.line, range.end.column, KTextEditor::MovingCursor::StayOnInsert, fromRevision, toRevision); } return range; } KDevelop::CursorInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::CursorInRevision cursor, qint64 fromRevision, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { m_moving->transformCursor(cursor.line, cursor.column, behavior, fromRevision, toRevision); } return cursor; } RangeInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Range range, qint64 toRevision) const { return transformBetweenRevisions(RangeInRevision::castFromSimpleRange(range), -1, toRevision); } CursorInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Cursor cursor, qint64 toRevision, MovingCursor::InsertBehavior behavior) const { return transformBetweenRevisions(CursorInRevision::castFromSimpleCursor(cursor), -1, toRevision, behavior); } KTextEditor::Range DocumentChangeTracker::transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const { return transformBetweenRevisions(range, fromRevision, -1).castToSimpleRange(); } KTextEditor::Cursor DocumentChangeTracker::transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision, MovingCursor::InsertBehavior behavior) const { return transformBetweenRevisions(cursor, fromRevision, -1, behavior).castToSimpleCursor(); } RevisionLockerAndClearerPrivate::RevisionLockerAndClearerPrivate(DocumentChangeTracker* tracker, qint64 revision) : m_tracker(tracker), m_revision(revision) { VERIFY_FOREGROUND_LOCKED moveToThread(QApplication::instance()->thread()); // Lock the revision m_tracker->lockRevision(revision); } RevisionLockerAndClearerPrivate::~RevisionLockerAndClearerPrivate() { if (m_tracker) m_tracker->unlockRevision(m_revision); } RevisionLockerAndClearer::~RevisionLockerAndClearer() { m_p->deleteLater(); // Will be deleted in the foreground thread, as the object was re-owned to the foreground } RevisionReference DocumentChangeTracker::acquireRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED if(!holdingRevision(revision) && revision != m_moving->revision()) return RevisionReference(); RevisionReference ret(new RevisionLockerAndClearer); ret->m_p = new RevisionLockerAndClearerPrivate(this, revision); return ret; } bool DocumentChangeTracker::holdingRevision(qint64 revision) const { VERIFY_FOREGROUND_LOCKED return m_revisionLocks.contains(revision); } void DocumentChangeTracker::lockRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED QMap< qint64, int >::iterator it = m_revisionLocks.find(revision); if(it != m_revisionLocks.end()) ++(*it); else { m_revisionLocks.insert(revision, 1); m_moving->lockRevision(revision); } } void DocumentChangeTracker::unlockRevision(qint64 revision) { VERIFY_FOREGROUND_LOCKED QMap< qint64, int >::iterator it = m_revisionLocks.find(revision); if(it == m_revisionLocks.end()) { qCDebug(LANGUAGE) << "cannot unlock revision" << revision << ", probably the revisions have been cleared"; return; } --(*it); if(*it == 0) { m_moving->unlockRevision(revision); m_revisionLocks.erase(it); } } qint64 RevisionLockerAndClearer::revision() const { return m_p->revision(); } RangeInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& to) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid() || (to && !to->valid())) return range; qint64 fromRevision = revision(); qint64 toRevision = -1; if(to) toRevision = to->revision(); return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); } CursorInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& to, MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid() || (to && !to->valid())) return cursor; qint64 fromRevision = revision(); qint64 toRevision = -1; if(to) toRevision = to->revision(); return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); } RangeInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& from) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker || !valid()) return range; qint64 toRevision = revision(); qint64 fromRevision = -1; if(from) fromRevision = from->revision(); return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); } CursorInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& from, MovingCursor::InsertBehavior behavior) const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker) return cursor; qint64 toRevision = revision(); qint64 fromRevision = -1; if(from) fromRevision = from->revision(); return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); } KTextEditor::Range RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::RangeInRevision& range) const { return transformToRevision(range, KDevelop::RevisionLockerAndClearer::Ptr()).castToSimpleRange(); } KTextEditor::Cursor RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::CursorInRevision& cursor, MovingCursor::InsertBehavior behavior) const { return transformToRevision(cursor, KDevelop::RevisionLockerAndClearer::Ptr(), behavior).castToSimpleCursor(); } RangeInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Range& range) const { return transformFromRevision(RangeInRevision::castFromSimpleRange(range), RevisionReference()); } CursorInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Cursor& cursor, MovingCursor::InsertBehavior behavior) const { return transformFromRevision(CursorInRevision::castFromSimpleCursor(cursor), RevisionReference(), behavior); } bool RevisionLockerAndClearer::valid() const { VERIFY_FOREGROUND_LOCKED if(!m_p->m_tracker) return false; if(revision() == -1) return true; // The 'current' revision is always valid return m_p->m_tracker->holdingRevision(revision()); } RevisionReference DocumentChangeTracker::diskRevision() const { ///@todo Track which revision was last saved to disk return RevisionReference(); } } #include "documentchangetracker.moc" diff --git a/kdevplatform/language/backgroundparser/documentchangetracker.h b/kdevplatform/language/backgroundparser/documentchangetracker.h index b25d73586f..c10c7385cf 100644 --- a/kdevplatform/language/backgroundparser/documentchangetracker.h +++ b/kdevplatform/language/backgroundparser/documentchangetracker.h @@ -1,243 +1,244 @@ /* * This file is part of KDevelop * * Copyright 2010 David Nolden * * 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 KDEVPLATFORM_DOCUMENTCHANGETRACKER_H #define KDEVPLATFORM_DOCUMENTCHANGETRACKER_H #include #include #include #include -#include #include #include +#include + namespace KTextEditor { class Document; class MovingRange; class MovingInterface; } namespace KDevelop { class DocumentChangeTracker; /** * These objects belongs to the foreground, and thus can only be accessed from background threads if the foreground lock is held. * */ class RevisionLockerAndClearerPrivate; /** * Helper class that locks a revision, and clears it on its destruction within the foreground thread. * Just delete it using deleteLater(). * */ class KDEVPLATFORMLANGUAGE_EXPORT RevisionLockerAndClearer : public QSharedData { public: typedef QExplicitlySharedDataPointer Ptr; ~RevisionLockerAndClearer(); /** * Returns the revision number * */ qint64 revision() const; /** * Whether the revision is still being held. It may have been lost due to document-reloads, * in which case the revision must not be used. * */ bool valid() const; /** * Transform a range from this document revision to the given @p to. * */ RangeInRevision transformToRevision(const RangeInRevision& range, const Ptr& to) const; /** * Transform a cursor from this document revision to the given @p to. * If a zero target revision is given, the transformation is done to the current document revision. * */ CursorInRevision transformToRevision(const CursorInRevision& cursor, const Ptr& to, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /** * Transforms the given range from this revision into the current revision. */ KTextEditor::Range transformToCurrentRevision(const RangeInRevision& range) const; /** * Transforms the given cursor from this revision into the current revision. */ KTextEditor::Cursor transformToCurrentRevision(const CursorInRevision& cursor, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /** * Transform ranges from the given document revision @p from to the this one. * If a zero @p from revision is given, the transformation is done from the current document revision. * */ RangeInRevision transformFromRevision(const RangeInRevision& range, const Ptr& from = Ptr()) const; /** * Transform ranges from the given document revision @p from to the this one. * If a zero @p from revision is given, the transformation is done from the current document revision. * */ CursorInRevision transformFromRevision(const CursorInRevision& cursor, const Ptr& from = Ptr(), KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /** * Transforms the given range from the current revision into this revision. */ RangeInRevision transformFromCurrentRevision(const KTextEditor::Range& range) const; /** * Transforms the given cursor from the current revision into this revision. */ CursorInRevision transformFromCurrentRevision(const KTextEditor::Cursor& cursor, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; private: friend class DocumentChangeTracker; RevisionLockerAndClearerPrivate* m_p; }; typedef RevisionLockerAndClearer::Ptr RevisionReference; class KDEVPLATFORMLANGUAGE_EXPORT DocumentChangeTracker : public QObject { Q_OBJECT public: explicit DocumentChangeTracker( KTextEditor::Document* document ); ~DocumentChangeTracker() override; /** * Completions of the users current edits that are supposed to complete * not-yet-finished statements, like for example for-loops for parsing. * */ virtual QList > completions() const; /** * Resets the tracking to the current revision. * */ virtual void reset(); /** * Returns the document revision at which reset() was called last. * * The revision is being locked by the tracker in MovingInterface, * it will be unlocked as soon as reset() is called, so if you want to use * the revision afterwards, you have to lock it before calling reset. * * zero is returned if the revisions were invalidated after the last call. * */ RevisionReference revisionAtLastReset() const; /** * Returns the current revision (which is not locked by the tracker) * */ RevisionReference currentRevision(); /** * Whether the changes that happened since the last reset are significant enough to require an update * */ virtual bool needUpdate() const; /** * Returns the tracked document **/ KTextEditor::Document* document() const; KTextEditor::MovingInterface* documentMovingInterface() const; /** * Returns the revision object which locks the revision representing the on-disk state. * Returns a zero object if the file is not on disk. * */ RevisionReference diskRevision() const; /** * Returns whether the given revision is being current held, so that it can be used * for transformations in MovingInterface * */ bool holdingRevision(qint64 revision) const; /** * Use this function to acquire a revision. As long as the returned object is stored somewhere, * the revision can be used for transformations in MovingInterface, and especially for * DocumentChangeTracker::transformBetweenRevisions. * * Returns a zero revision object if the revision could not be acquired (it wasn't held). * */ RevisionReference acquireRevision(qint64 revision); /** * Safely maps the given range between the two given revisions. * The mapping is only done if both the from- and to- revision are held, * else the original range is returned. * * @warning: Make sure that you actually hold the referenced revisions, else no transformation will be done. * @note It is much less error-prone to use RevisionReference->transformToRevision() and RevisionReference->transformFromRevision() directly. * */ RangeInRevision transformBetweenRevisions(RangeInRevision range, qint64 fromRevision, qint64 toRevision) const; CursorInRevision transformBetweenRevisions(CursorInRevision cursor, qint64 fromRevision, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; KTextEditor::Range transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const; KTextEditor::Cursor transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /// Transform the range from the current revision into the given one RangeInRevision transformToRevision(KTextEditor::Range range, qint64 toRevision) const; /// Transform the cursor from the current revision into the given one CursorInRevision transformToRevision(KTextEditor::Cursor cursor, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; protected: RevisionReference m_revisionAtLastReset; bool m_needUpdate; QString m_currentCleanedInsertion; KTextEditor::Cursor m_lastInsertionPosition; KTextEditor::MovingRange* m_changedRange; KTextEditor::Document* m_document; KTextEditor::MovingInterface* m_moving; KDevelop::IndexedString m_url; void updateChangedRange(int delay); int recommendedDelay(KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& text, bool removal); public Q_SLOTS: void textInserted(KTextEditor::Document* document, const KTextEditor::Cursor& position, const QString& inserted); void textRemoved(KTextEditor::Document* document, const KTextEditor::Range& range, const QString& oldText); void lineWrapped(KTextEditor::Document* document, const KTextEditor::Cursor& position); void lineUnwrapped(KTextEditor::Document* document, int line); void documentDestroyed( QObject* ); void aboutToInvalidateMovingInterfaceContent ( KTextEditor::Document* document ); void documentSavedOrUploaded(KTextEditor::Document*,bool); private: bool checkMergeTokens(const KTextEditor::Range& range); friend class RevisionLockerAndClearerPrivate; void lockRevision(qint64 revision); void unlockRevision(qint64 revision); QMap m_revisionLocks; }; } #endif diff --git a/kdevplatform/language/backgroundparser/parsejob.cpp b/kdevplatform/language/backgroundparser/parsejob.cpp index 2a0c541c66..a173289033 100644 --- a/kdevplatform/language/backgroundparser/parsejob.cpp +++ b/kdevplatform/language/backgroundparser/parsejob.cpp @@ -1,538 +1,538 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * * 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 "parsejob.h" #include #include #include #include #include #include -#include +#include #include "backgroundparser.h" #include #include "duchain/topducontext.h" #include "duchain/duchainlock.h" #include "duchain/duchain.h" #include "duchain/parsingenvironment.h" #include "editor/documentrange.h" #include #include #include #include #include #include #include #include #include using namespace KTextEditor; static QMutex minimumFeaturesMutex; static QHash > staticMinimumFeatures; namespace KDevelop { class ParseJobPrivate { public: ParseJobPrivate(const IndexedString& url_, ILanguageSupport* languageSupport_) : url( url_ ) , languageSupport( languageSupport_ ) , abortRequested( 0 ) , hasReadContents( false ) , aborted( false ) , features( TopDUContext::VisibleDeclarationsAndContexts ) , parsePriority( 0 ) , sequentialProcessingFlags( ParseJob::IgnoresSequentialProcessing ) { } ~ParseJobPrivate() { } ReferencedTopDUContext duContext; IndexedString url; ILanguageSupport* languageSupport; ParseJob::Contents contents; QAtomicInt abortRequested; bool hasReadContents : 1; bool aborted : 1; TopDUContext::Features features; QVector> notify; QPointer tracker; RevisionReference revision; RevisionReference previousRevision; int parsePriority; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; }; ParseJob::ParseJob( const IndexedString& url, KDevelop::ILanguageSupport* languageSupport ) : ThreadWeaver::Sequence(), d(new ParseJobPrivate(url, languageSupport)) { } ParseJob::~ParseJob() { typedef QPointer QObjectPointer; foreach(const QObjectPointer &p, d->notify) { if(p) { QMetaObject::invokeMethod(p.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, d->url), Q_ARG(KDevelop::ReferencedTopDUContext, d->duContext)); } } } ILanguageSupport* ParseJob::languageSupport() const { return d->languageSupport; } void ParseJob::setParsePriority(int priority) { d->parsePriority = priority; } int ParseJob::parsePriority() const { return d->parsePriority; } bool ParseJob::requiresSequentialProcessing() const { return d->sequentialProcessingFlags & RequiresSequentialProcessing; } bool ParseJob::respectsSequentialProcessing() const { return d->sequentialProcessingFlags & RespectsSequentialProcessing; } void ParseJob::setSequentialProcessingFlags(SequentialProcessingFlags flags) { d->sequentialProcessingFlags = flags; } IndexedString ParseJob::document() const { return d->url; } bool ParseJob::success() const { return !d->aborted; } void ParseJob::setMinimumFeatures(TopDUContext::Features features) { d->features = features; } bool ParseJob::hasStaticMinimumFeatures() { QMutexLocker lock(&minimumFeaturesMutex); return !::staticMinimumFeatures.isEmpty(); } TopDUContext::Features ParseJob::staticMinimumFeatures(const IndexedString& url) { QMutexLocker lock(&minimumFeaturesMutex); TopDUContext::Features features = (TopDUContext::Features)0; if(::staticMinimumFeatures.contains(url)) foreach(const TopDUContext::Features f, ::staticMinimumFeatures[url]) features = (TopDUContext::Features)(features | f); return features; } TopDUContext::Features ParseJob::minimumFeatures() const { return (TopDUContext::Features)(d->features | staticMinimumFeatures(d->url)); } void ParseJob::setDuChain(const ReferencedTopDUContext& duChain) { d->duContext = duChain; } ReferencedTopDUContext ParseJob::duChain() const { return d->duContext; } bool ParseJob::abortRequested() const { return d->abortRequested.load(); } void ParseJob::requestAbort() { d->abortRequested = 1; } void ParseJob::abortJob() { d->aborted = true; setStatus(Status_Aborted); } void ParseJob::setNotifyWhenReady(const QVector>& notify) { d->notify = notify; } void ParseJob::setStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].append(features); } void ParseJob::unsetStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].removeOne(features); if(::staticMinimumFeatures[url].isEmpty()) ::staticMinimumFeatures.remove(url); } KDevelop::ProblemPointer ParseJob::readContents() { Q_ASSERT(!d->hasReadContents); d->hasReadContents = true; QString localFile(document().toUrl().toLocalFile()); QFileInfo fileInfo( localFile ); QDateTime lastModified = fileInfo.lastModified(); d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); //Try using an artificial code-representation, which overrides everything else if(artificialCodeRepresentationExists(document())) { CodeRepresentation::Ptr repr = createCodeRepresentation(document()); d->contents.contents = repr->text().toUtf8(); qCDebug(LANGUAGE) << "took contents for " << document().str() << " from artificial code-representation"; return KDevelop::ProblemPointer(); } bool hadTracker = false; if(d->tracker) { ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { // The file is open in an editor d->previousRevision = t->revisionAtLastReset(); t->reset(); // Reset the tracker to the current revision Q_ASSERT(t->revisionAtLastReset()); d->contents.contents = t->document()->text().toUtf8(); d->contents.modification = KDevelop::ModificationRevision( lastModified, t->revisionAtLastReset()->revision() ); d->revision = t->acquireRevision(d->contents.modification.revision); hadTracker = true; } } if (!hadTracker) { // We have to load the file from disk static const int maximumFileSize = 5 * 1024 * 1024; // 5 MB if (fileInfo.size() > maximumFileSize) { QStringList paths = QStandardPaths::standardLocations(QStandardPaths::StandardLocation::GenericDataLocation); bool internalFile = false; QString internalFilePath = fileInfo.canonicalPath(); foreach (const QString path, paths) { QDir dataPath = QDir(path); if (internalFilePath.startsWith(dataPath.canonicalPath() + QStringLiteral("/kdev"))) { qCInfo(LANGUAGE) << "Found internal file " << fileInfo.absoluteFilePath() << " in " << path << ". Ignoring file size limit!"; internalFile = true; break; } } if (!internalFile) { KFormat f; KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18nc("%1: filename", "Skipped file that is too large: '%1'", localFile )); p->setExplanation(i18nc("%1: file size, %2: limit file size", "The file is %1 and exceeds the limit of %2.", f.formatByteSize(fileInfo.size()), f.formatByteSize(maximumFileSize))); p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << p->description() << p->explanation(); return p; } } QFile file( localFile ); if ( !file.open( QIODevice::ReadOnly ) ) { KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18n( "Could not open file '%1'", localFile )); switch (file.error()) { case QFile::ReadError: p->setExplanation(i18n("File could not be read from disk.")); break; case QFile::OpenError: p->setExplanation(i18n("File could not be opened.")); break; case QFile::PermissionsError: p->setExplanation(i18n("File could not be read from disk due to permissions.")); break; default: break; } p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << "Could not open file" << document().str() << "(path" << localFile << ")" ; return p; } d->contents.contents = file.readAll(); ///@todo Convert from local encoding to utf-8 if they don't match // This is consistent with KTextEditor::Document::text() as used for already-open files. normalizeLineEndings(d->contents.contents); d->contents.modification = KDevelop::ModificationRevision(lastModified); file.close(); } return KDevelop::ProblemPointer(); } const KDevelop::ParseJob::Contents& ParseJob::contents() const { Q_ASSERT(d->hasReadContents); return d->contents; } struct MovingRangeTranslator : public DUChainVisitor { MovingRangeTranslator(qint64 _source, qint64 _target, MovingInterface* _moving) : source(_source), target(_target), moving(_moving) { } void visit(DUContext* context) override { translateRange(context); ///@todo Also map import-positions // Translate uses uint usesCount = context->usesCount(); for(uint u = 0; u < usesCount; ++u) { RangeInRevision r = context->uses()[u].m_range; translateRange(r); context->changeUseRange(u, r); } } void visit(Declaration* declaration) override { translateRange(declaration); } void translateRange(DUChainBase* object) { RangeInRevision r = object->range(); translateRange(r); object->setRange(r); } void translateRange(RangeInRevision& r) { // PHP and python use top contexts that start at (0, 0) end at INT_MAX, so make sure that doesn't overflow // or translate the start of the top context away from (0, 0) if ( r.start.line != 0 || r.start.column != 0 ) { moving->transformCursor(r.start.line, r.start.column, MovingCursor::MoveOnInsert, source, target); } if ( r.end.line != std::numeric_limits::max() || r.end.column != std::numeric_limits::max() ) { moving->transformCursor(r.end.line, r.end.column, MovingCursor::StayOnInsert, source, target); } } KTextEditor::Range range; qint64 source; qint64 target; MovingInterface* moving; }; void ParseJob::translateDUChainToRevision(TopDUContext* context) { qint64 targetRevision = d->contents.modification.revision; if(targetRevision == -1) { qCDebug(LANGUAGE) << "invalid target revision" << targetRevision; return; } qint64 sourceRevision; { DUChainReadLocker duChainLock; Q_ASSERT(context->parsingEnvironmentFile()); // Cannot map if there is no source revision sourceRevision = context->parsingEnvironmentFile()->modificationRevision().revision; if(sourceRevision == -1) { qCDebug(LANGUAGE) << "invalid source revision" << sourceRevision; return; } } if(sourceRevision > targetRevision) { qCDebug(LANGUAGE) << "for document" << document().str() << ": source revision is higher than target revision:" << sourceRevision << " > " << targetRevision; return; } ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { if(!d->previousRevision) { qCDebug(LANGUAGE) << "not translating because there is no valid predecessor-revision"; return; } if(sourceRevision != d->previousRevision->revision() || !d->previousRevision->valid()) { qCDebug(LANGUAGE) << "not translating because the document revision does not match the tracker start revision (maybe the document was cleared)"; return; } if(!t->holdingRevision(sourceRevision) || !t->holdingRevision(targetRevision)) { qCDebug(LANGUAGE) << "lost one of the translation revisions, not doing the map"; return; } // Perform translation MovingInterface* moving = t->documentMovingInterface(); DUChainWriteLocker wLock; MovingRangeTranslator translator(sourceRevision, targetRevision, moving); context->visit(translator); QList< ProblemPointer > problems = context->problems(); for(QList< ProblemPointer >::iterator problem = problems.begin(); problem != problems.end(); ++problem) { RangeInRevision r = (*problem)->range(); translator.translateRange(r); (*problem)->setRange(r); } // Update the modification revision in the meta-data ModificationRevision modRev = context->parsingEnvironmentFile()->modificationRevision(); modRev.revision = targetRevision; context->parsingEnvironmentFile()->setModificationRevision(modRev); } } bool ParseJob::isUpdateRequired(const IndexedString& languageString) { if (abortRequested()) { return false; } if (minimumFeatures() & TopDUContext::ForceUpdate) { return true; } DUChainReadLocker lock; if (abortRequested()) { return false; } foreach(const ParsingEnvironmentFilePointer &file, DUChain::self()->allEnvironmentFiles(document())) { if (file->language() != languageString) { continue; } if (!file->needsUpdate(environment()) && file->featuresSatisfied(minimumFeatures())) { qCDebug(LANGUAGE) << "Already up to date" << document().str(); setDuChain(file->topContext()); lock.unlock(); highlightDUChain(); return false; } break; } return !abortRequested(); } const ParsingEnvironment* ParseJob::environment() const { return nullptr; } void ParseJob::highlightDUChain() { ENSURE_CHAIN_NOT_LOCKED if (!d->languageSupport->codeHighlighting() || !duChain() || abortRequested()) { // language doesn't support highlighting return; } if (!d->hasReadContents && !d->tracker) { d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); } if (d->tracker) { d->languageSupport->codeHighlighting()->highlightDUChain(duChain()); } } ControlFlowGraph* ParseJob::controlFlowGraph() { return nullptr; } DataAccessRepository* ParseJob::dataAccessInformation() { return nullptr; } bool ParseJob::hasTracker() const { return d->tracker; } } diff --git a/kdevplatform/language/backgroundparser/parseprojectjob.h b/kdevplatform/language/backgroundparser/parseprojectjob.h index 0b4c14259f..66cae29f4b 100644 --- a/kdevplatform/language/backgroundparser/parseprojectjob.h +++ b/kdevplatform/language/backgroundparser/parseprojectjob.h @@ -1,54 +1,55 @@ /* Copyright 2009 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. */ #ifndef KDEVPLATFORM_PARSEPROJECTJOB_H #define KDEVPLATFORM_PARSEPROJECTJOB_H -#include #include #include +#include + namespace KDevelop { class ReferencedTopDUContext; class IProject; ///A job that parses all project-files in the given project ///Deletes itself as soon as the project is deleted class KDEVPLATFORMLANGUAGE_EXPORT ParseProjectJob : public KJob { Q_OBJECT public: explicit ParseProjectJob(KDevelop::IProject* project, bool forceUpdate = false ); ~ParseProjectJob() override; void start() override; bool doKill() override; private Q_SLOTS: void deleteNow(); void updateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext& topContext); private: void updateProgress(); private: const QScopedPointer d; }; } #endif // KDEVPLATFORM_PARSEPROJECTJOB_H diff --git a/kdevplatform/language/classmodel/classmodelnode.h b/kdevplatform/language/classmodel/classmodelnode.h index 9c46c06b6e..e4d79d23e5 100644 --- a/kdevplatform/language/classmodel/classmodelnode.h +++ b/kdevplatform/language/classmodel/classmodelnode.h @@ -1,331 +1,333 @@ /* * KDevelop Class Browser * * Copyright 2007-2009 Hamish Rodda * Copyright 2009 Lior Mualem * * 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 KDEVPLATFORM_CLASSMODELNODE_H #define KDEVPLATFORM_CLASSMODELNODE_H #include "classmodel.h" -#include + #include "../duchain/identifier.h" #include "../duchain/duchainpointer.h" #include "classmodelnodescontroller.h" +#include + class NodesModelInterface; namespace KDevelop { class ClassDeclaration; class ClassFunctionDeclaration; class ClassMemberDeclaration; class Declaration; } namespace ClassModelNodes { /// Base node class - provides basic functionality. class Node { public: Node(const QString& a_displayName, NodesModelInterface* a_model); virtual ~Node(); public: // Operations /// Clear all the children from the node. void clear(); /// Called by the model to collapse the node and remove sub-items if needed. virtual void collapse() {}; /// Called by the model to expand the node and populate it with sub-nodes if needed. virtual void expand() {}; /// Append a new child node to the list. void addNode(Node* a_child); /// Remove child node from the list and delete it. void removeNode(Node* a_child); /// Remove this node and delete it. void removeSelf() { m_parentNode->removeNode(this); } /// Called once the node has been populated to sort the entire tree / branch. void recursiveSort(); public: // Info retrieval /// Return the parent associated with this node. Node* getParent() const { return m_parentNode; } /// Get my index in the parent node int row(); /// Return the display name for the node. QString displayName() const { return m_displayName; } /// Returns a list of child nodes const QList& getChildren() const { return m_children; } /// Return an icon representation for the node. /// @note It calls the internal getIcon and caches the result. QIcon getCachedIcon(); public: // overridables /// Return a score when sorting the nodes. virtual int getScore() const = 0; /// Return true if the node contains sub-nodes. virtual bool hasChildren() const { return !m_children.empty(); } /// We use this string when sorting items. virtual QString getSortableString() const { return m_displayName; } protected: /// fill a_resultIcon with a display icon for the node. /// @param a_resultIcon returned icon. /// @return true if result was returned. virtual bool getIcon(QIcon& a_resultIcon) = 0; private: Node* m_parentNode; /// Called once the node has been populated to sort the entire tree / branch. void recursiveSortInternal(); protected: typedef QList< Node* > NodesList; NodesList m_children; QString m_displayName; QIcon m_cachedIcon; NodesModelInterface* m_model; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Base class for nodes that generate and populate their child nodes dynamically class DynamicNode : public Node { public: DynamicNode(const QString& a_displayName, NodesModelInterface* a_model); /// Return true if the node was populated already. bool isPopulated() const { return m_populated; } /// Populate the node and mark the flag - called from expand or can be used internally. void performPopulateNode(bool a_forceRepopulate = false); public: // Node overrides. void collapse() override; void expand() override; bool hasChildren() const override; protected: // overridables /// Called by the framework when the node is about to be expanded /// it should be populated with sub-nodes if applicable. virtual void populateNode() {} /// Called after the nodes have been removed. /// It's for derived classes to clean cached data. virtual void nodeCleared() {} private: bool m_populated; /// Clear all the child nodes and mark flag. void performNodeCleanup(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Base class for nodes associated with a @ref KDevelop::QualifiedIdentifier class IdentifierNode : public DynamicNode { public: IdentifierNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model, const QString& a_displayName = QString()); public: /// Returns the qualified identifier for this node by going through the tree const KDevelop::IndexedQualifiedIdentifier& getIdentifier() const { return m_identifier; } public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; public: // Overridables /// Return the associated declaration /// @note DU CHAIN MUST BE LOCKED FOR READ virtual KDevelop::Declaration* getDeclaration(); private: KDevelop::IndexedQualifiedIdentifier m_identifier; KDevelop::IndexedDeclaration m_indexedDeclaration; KDevelop::DeclarationPointer m_cachedDeclaration; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// A node that represents an enum value. class EnumNode : public IdentifierNode { public: EnumNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); public: // Node overrides int getScore() const override { return 102; } bool getIcon(QIcon& a_resultIcon) override; void populateNode() override; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides display for a single class. class ClassNode : public IdentifierNode, public ClassModelNodeDocumentChangedInterface { public: ClassNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); ~ClassNode() override; /// Lookup a contained class and return the related node. /// @return the node pointer or 0 if non was found. ClassNode* findSubClass(const KDevelop::IndexedQualifiedIdentifier& a_id); public: // Node overrides int getScore() const override { return 300; } void populateNode() override; void nodeCleared() override; bool hasChildren() const override { return true; } protected: // ClassModelNodeDocumentChangedInterface overrides void documentChanged(const KDevelop::IndexedString& a_file) override; private: typedef QMap< uint, Node* > SubIdentifiersMap; /// Set of known sub-identifiers. It's used for updates check. SubIdentifiersMap m_subIdentifiers; /// We use this variable to know if we've registered for change notification or not. KDevelop::IndexedString m_cachedUrl; /// Updates the node to reflect changes in the declaration. /// @note DU CHAIN MUST BE LOCKED FOR READ /// @return true if something was updated. bool updateClassDeclarations(); /// Add "Base classes" and "Derived classes" folders, if needed /// @return true if one of the folders was added. bool addBaseAndDerived(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a display for a single class function. class FunctionNode : public IdentifierNode { public: FunctionNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); public: // Node overrides int getScore() const override { return 400; } QString getSortableString() const override { return m_sortableString; } private: QString m_sortableString; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides display for a single class variable. class ClassMemberNode : public IdentifierNode { public: ClassMemberNode(KDevelop::ClassMemberDeclaration* a_decl, NodesModelInterface* a_model); public: // Node overrides int getScore() const override { return 500; } bool getIcon(QIcon& a_resultIcon) override; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a folder node with a static list of nodes. class FolderNode : public Node { public: FolderNode(const QString& a_displayName, NodesModelInterface* a_model); public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; int getScore() const override { return 100; } }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a folder node with a dynamic list of nodes. class DynamicFolderNode : public DynamicNode { public: DynamicFolderNode(const QString& a_displayName, NodesModelInterface* a_model); public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; int getScore() const override { return 100; } }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Special folder - the parent is assumed to be a ClassNode. /// It then displays the base classes for the class it sits in. class BaseClassesFolderNode : public DynamicFolderNode { public: explicit BaseClassesFolderNode(NodesModelInterface* a_model); public: // Node overrides void populateNode() override; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Special folder - the parent is assumed to be a ClassNode. /// It then displays list of derived classes from the parent class. class DerivedClassesFolderNode : public DynamicFolderNode { public: explicit DerivedClassesFolderNode(NodesModelInterface* a_model); public: // Node overrides void populateNode() override; }; } // namespace classModelNodes #endif // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/kdevplatform/language/codecompletion/codecompletion.cpp b/kdevplatform/language/codecompletion/codecompletion.cpp index 6bfa4c6a10..81f3a4e2d9 100644 --- a/kdevplatform/language/codecompletion/codecompletion.cpp +++ b/kdevplatform/language/codecompletion/codecompletion.cpp @@ -1,134 +1,134 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006 Hamish Rodda * * 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 "codecompletion.h" -#include -#include -#include -#include - #include #include #include #include "../duchain/duchain.h" #include "../duchain/topducontext.h" #include #include "codecompletionmodel.h" #include +#include +#include +#include +#include + using namespace KTextEditor; using namespace KDevelop; CodeCompletion::CodeCompletion(QObject *parent, KTextEditor::CodeCompletionModel* aModel, const QString& language) : QObject(parent), m_model(aModel), m_language(language) { KDevelop::CodeCompletionModel* kdevModel = dynamic_cast(aModel); if(kdevModel) kdevModel->initialize(); connect(KDevelop::ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &CodeCompletion::textDocumentCreated); connect( ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &CodeCompletion::documentUrlChanged ); aModel->setParent(this); // prevent deadlock QMetaObject::invokeMethod(this, "checkDocuments", Qt::QueuedConnection); } CodeCompletion::~CodeCompletion() { } void CodeCompletion::checkDocuments() { foreach( KDevelop::IDocument* doc, KDevelop::ICore::self()->documentController()->openDocuments() ) { if (doc->textDocument()) { checkDocument(doc->textDocument()); } } } void CodeCompletion::viewCreated(KTextEditor::Document * document, KTextEditor::View * view) { Q_UNUSED(document); if (CodeCompletionInterface* cc = dynamic_cast(view)) { cc->registerCompletionModel(m_model); qCDebug(LANGUAGE) << "Registered completion model"; emit registeredToView(view); } } void CodeCompletion::documentUrlChanged(KDevelop::IDocument* document) { // The URL has changed (might have a different language now), so we re-register the document Document* textDocument = document->textDocument(); if(textDocument) { checkDocument(textDocument); } } void CodeCompletion::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); checkDocument(document->textDocument()); } void CodeCompletion::unregisterDocument(Document* textDocument) { foreach (KTextEditor::View* view, textDocument->views()) { if (CodeCompletionInterface* cc = dynamic_cast(view)) { cc->unregisterCompletionModel(m_model); emit unregisteredFromView(view); } } disconnect(textDocument, &Document::viewCreated, this, &CodeCompletion::viewCreated); } void CodeCompletion::checkDocument(Document* textDocument) { unregisterDocument(textDocument); const auto langs = ICore::self()->languageController()->languagesForUrl( textDocument->url() ); bool found = false; for (const auto lang : langs) { if(m_language==lang->name()) { found=true; break; } } if(!found && !m_language.isEmpty()) return; foreach (KTextEditor::View* view, textDocument->views()) viewCreated(textDocument, view); connect(textDocument, &Document::viewCreated, this, &CodeCompletion::viewCreated); } diff --git a/kdevplatform/language/codecompletion/codecompletionitem.cpp b/kdevplatform/language/codecompletion/codecompletionitem.cpp index b6734a9970..8cb2b82fb1 100644 --- a/kdevplatform/language/codecompletion/codecompletionitem.cpp +++ b/kdevplatform/language/codecompletion/codecompletionitem.cpp @@ -1,164 +1,164 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2007-2008 David Nolden * * 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 "codecompletionitem.h" #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include "../duchain/declaration.h" #include "../duchain/duchainutils.h" using namespace KTextEditor; namespace KDevelop { ///Intermediate nodes struct CompletionTreeNode; ///Leaf items class CompletionTreeItem; CompletionTreeElement::CompletionTreeElement() : m_parent(nullptr), m_rowInParent(0) { } CompletionTreeElement::~CompletionTreeElement() { } CompletionTreeElement* CompletionTreeElement::parent() const { return m_parent; } void CompletionTreeElement::setParent(CompletionTreeElement* parent) { Q_ASSERT(m_parent == nullptr); m_parent = parent; auto node = parent ? parent->asNode() : nullptr; if (node) { m_rowInParent = node->children.count(); } } void CompletionTreeNode::appendChildren(const QList>& children) { for (const auto& child : children) { appendChild(child); } } void CompletionTreeNode::appendChildren(const QList>& children) { for (const auto& child : children) { appendChild(CompletionTreeElementPointer(child.data())); } } void CompletionTreeNode::appendChild(QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > child) { child->setParent(this); children << child; } int CompletionTreeElement::columnInParent() const { return 0; } CompletionTreeNode::CompletionTreeNode() : CompletionTreeElement(), role((KTextEditor::CodeCompletionModel::ExtraItemDataRoles)Qt::DisplayRole) {} CompletionTreeNode::~CompletionTreeNode() { } CompletionTreeNode* CompletionTreeElement::asNode() { return dynamic_cast(this); } CompletionTreeItem* CompletionTreeElement::asItem() { return dynamic_cast(this); } const CompletionTreeNode* CompletionTreeElement::asNode() const { return dynamic_cast(this); } const CompletionTreeItem* CompletionTreeElement::asItem() const { return dynamic_cast(this); } int CompletionTreeElement::rowInParent() const { return m_rowInParent; } void CompletionTreeItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { Q_UNUSED(view) Q_UNUSED(word) qCWarning(LANGUAGE) << "doing nothing"; } QVariant CompletionTreeItem::data(const QModelIndex& index, int role, const CodeCompletionModel* model) const { Q_UNUSED(index) Q_UNUSED(model) if(role == Qt::DisplayRole) return i18n("not implemented"); return QVariant(); } int CompletionTreeItem::inheritanceDepth() const { return 0; } int CompletionTreeItem::argumentHintDepth() const { return 0; } KTextEditor::CodeCompletionModel::CompletionProperties CompletionTreeItem::completionProperties() const { Declaration* dec = declaration().data(); if(!dec) { return {}; } return DUChainUtils::completionProperties(dec); } DeclarationPointer CompletionTreeItem::declaration() const { return DeclarationPointer(); } QList CompletionTreeItem::typeForArgumentMatching() const { return QList(); } CompletionCustomGroupNode::CompletionCustomGroupNode(const QString& groupName, int _inheritanceDepth) { role = (KTextEditor::CodeCompletionModel::ExtraItemDataRoles)Qt::DisplayRole; roleValue = groupName; inheritanceDepth = _inheritanceDepth; } bool CompletionTreeItem::dataChangedWithInput() const { return false; } } diff --git a/kdevplatform/language/codecompletion/codecompletionitem.h b/kdevplatform/language/codecompletion/codecompletionitem.h index 8fdc6981b3..5834f57a50 100644 --- a/kdevplatform/language/codecompletion/codecompletionitem.h +++ b/kdevplatform/language/codecompletion/codecompletionitem.h @@ -1,150 +1,150 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2007-2008 David Nolden * * 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 KDEVPLATFORM_KDEV_CODECOMPLETIONITEM_H #define KDEVPLATFORM_KDEV_CODECOMPLETIONITEM_H -#include - #include "../duchain/duchainpointer.h" +#include + namespace KTextEditor { class CodeCompletionModel; class Range; class Cursor; } class QModelIndex; namespace KDevelop { class CodeCompletionModel; struct CompletionTreeNode; class CompletionTreeItem; class IndexedType; class KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeElement : public QSharedData { public: CompletionTreeElement(); virtual ~CompletionTreeElement(); CompletionTreeElement* parent() const; /// Reparenting is not supported. This is only allowed if parent() is still zero. void setParent(CompletionTreeElement*); int rowInParent() const; int columnInParent() const; /// Each element is either a node, or an item. CompletionTreeNode* asNode(); CompletionTreeItem* asItem(); template T* asItem() { return dynamic_cast(this); } template const T* asItem() const { return dynamic_cast(this); } const CompletionTreeNode* asNode() const; const CompletionTreeItem* asItem() const; private: CompletionTreeElement* m_parent; int m_rowInParent; }; struct KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeNode : public CompletionTreeElement { CompletionTreeNode(); ~CompletionTreeNode() override; KTextEditor::CodeCompletionModel::ExtraItemDataRoles role; QVariant roleValue; /// Will append the child, and initialize it correctly to create a working tree-structure void appendChild(QExplicitlySharedDataPointer); void appendChildren(const QList>& children); void appendChildren(const QList>& children); /// @warning Do not manipulate this directly, that's bad for consistency. Use appendChild instead. QList > children; }; class KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeItem : public CompletionTreeElement { public: /// Execute the completion item. The default implementation does nothing. virtual void execute(KTextEditor::View* view, const KTextEditor::Range& word); /// Should return normal completion data, @see KTextEditor::CodeCompletionModel /// The default implementation returns "unimplemented", so re-implement it! /// The duchain is not locked when this is called virtual QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const; /// Should return the inheritance-depth. The completion-items don't need to return it through the data() function. virtual int inheritanceDepth() const; /// Should return the argument-hint depth. The completion-items don't need to return it through the data() function. virtual int argumentHintDepth() const; /// The default-implementation calls DUChainUtils::completionProperties virtual KTextEditor::CodeCompletionModel::CompletionProperties completionProperties() const; /// If this item represents a Declaration, this should return the declaration. /// The default-implementation returns zero. virtual DeclarationPointer declaration() const; /// Should return the types should be used for matching items against this one when it's an argument hint. /// The matching against all types should be done, and the best one will be used as final match result. virtual QList typeForArgumentMatching() const; /// Should return whether this completion-items data changes with input done by the user during code-completion. /// Returning true is very expensive. virtual bool dataChangedWithInput() const; }; /// A custom-group node, that can be used as-is. Just create it, and call appendChild to add group items. /// The items in the group will be shown in the completion-list with a group-header that contains the given name struct KDEVPLATFORMLANGUAGE_EXPORT CompletionCustomGroupNode : public CompletionTreeNode { /// @param inheritanceDepth See KTextEditor::CodeCompletionModel::GroupRole explicit CompletionCustomGroupNode(const QString& groupName, int inheritanceDepth = 700); int inheritanceDepth; }; typedef QExplicitlySharedDataPointer CompletionTreeItemPointer; typedef QExplicitlySharedDataPointer CompletionTreeElementPointer; } Q_DECLARE_METATYPE(KDevelop::CompletionTreeElementPointer) #endif diff --git a/kdevplatform/language/codecompletion/codecompletionmodel.cpp b/kdevplatform/language/codecompletion/codecompletionmodel.cpp index 2cbd48653c..2e3520699f 100644 --- a/kdevplatform/language/codecompletion/codecompletionmodel.cpp +++ b/kdevplatform/language/codecompletion/codecompletionmodel.cpp @@ -1,431 +1,431 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 Hamish Rodda * Copyright 2007-2008 David Nolden * * 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 "codecompletionmodel.h" #include -#include -#include +#include +#include #include "../duchain/declaration.h" #include "../duchain/classfunctiondeclaration.h" #include "../duchain/ducontext.h" #include "../duchain/duchain.h" #include "../duchain/namespacealiasdeclaration.h" #include "../duchain/parsingenvironment.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainbase.h" #include "../duchain/topducontext.h" #include "../duchain/duchainutils.h" #include "../interfaces/quickopendataprovider.h" #include "../interfaces/icore.h" #include "../interfaces/ilanguagecontroller.h" #include "../interfaces/icompletionsettings.h" #include #include "codecompletionworker.h" #include "codecompletioncontext.h" #include using namespace KTextEditor; //Multi-threaded completion creates some multi-threading related crashes, and sometimes shows the completions in the wrong position if the cursor was moved // #define SINGLE_THREADED_COMPLETION namespace KDevelop { class CompletionWorkerThread : public QThread { Q_OBJECT public: explicit CompletionWorkerThread(CodeCompletionModel* model) : QThread(model), m_model(model), m_worker(m_model->createCompletionWorker()) { Q_ASSERT(m_worker->parent() == nullptr); // Must be null, else we cannot change the thread affinity! m_worker->moveToThread(this); Q_ASSERT(m_worker->thread() == this); } ~CompletionWorkerThread() override { delete m_worker; } void run () override { //We connect directly, so we can do the pre-grouping within the background thread connect(m_worker, &CodeCompletionWorker::foundDeclarationsReal, m_model, &CodeCompletionModel::foundDeclarations, Qt::QueuedConnection); connect(m_model, &CodeCompletionModel::completionsNeeded, m_worker, static_cast&, const Cursor&, View*)>(&CodeCompletionWorker::computeCompletions), Qt::QueuedConnection); connect(m_model, &CodeCompletionModel::doSpecialProcessingInBackground, m_worker, &CodeCompletionWorker::doSpecialProcessing); exec(); } CodeCompletionModel* m_model; CodeCompletionWorker* m_worker; }; bool CodeCompletionModel::forceWaitForModel() { return m_forceWaitForModel; } void CodeCompletionModel::setForceWaitForModel(bool wait) { m_forceWaitForModel = wait; } CodeCompletionModel::CodeCompletionModel( QObject * parent ) : KTextEditor::CodeCompletionModel(parent) , m_forceWaitForModel(false) , m_fullCompletion(true) , m_mutex(new QMutex) , m_thread(nullptr) { qRegisterMetaType(); } void CodeCompletionModel::initialize() { if(!m_thread) { m_thread = new CompletionWorkerThread(this); #ifdef SINGLE_THREADED_COMPLETION m_thread->m_worker = createCompletionWorker(); #endif m_thread->start(); } } CodeCompletionModel::~CodeCompletionModel() { if(m_thread->m_worker) m_thread->m_worker->abortCurrentCompletion(); m_thread->quit(); m_thread->wait(); delete m_thread; delete m_mutex; } bool CodeCompletionModel::fullCompletion() const { return m_fullCompletion; } KDevelop::CodeCompletionWorker* CodeCompletionModel::worker() const { return m_thread->m_worker; } void CodeCompletionModel::clear() { beginResetModel(); m_completionItems.clear(); m_completionContext.reset(); endResetModel(); } void CodeCompletionModel::completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType, const QUrl& url) { Q_ASSERT(m_thread == worker()->thread()); Q_UNUSED(invocationType) DUChainReadLocker lock(DUChain::lock(), 400); if( !lock.locked() ) { qCDebug(LANGUAGE) << "could not lock du-chain in time"; return; } TopDUContext* top = DUChainUtils::standardContextForUrl( url ); if(!top) { qCDebug(LANGUAGE) << "================== NO CONTEXT FOUND ======================="; beginResetModel(); m_completionItems.clear(); endResetModel(); qCDebug(LANGUAGE) << "Completion invoked for unknown context. Document:" << url << ", Known documents:" << DUChain::self()->documents(); return; } setCurrentTopContext(TopDUContextPointer(top)); RangeInRevision rangeInRevision = top->transformToLocalRevision(KTextEditor::Range(range)); qCDebug(LANGUAGE) << "completion invoked for context" << (DUContext*)top; if( top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->modificationRevision() != ModificationRevision::revisionForFile(IndexedString(url.toString())) ) { qCDebug(LANGUAGE) << "Found context is not current."; } DUContextPointer thisContext; { qCDebug(LANGUAGE) << "apply specialization:" << range.start(); thisContext = SpecializationStore::self().applySpecialization(top->findContextAt(rangeInRevision.start), top); if ( thisContext ) { qCDebug(LANGUAGE) << "after specialization:" << thisContext->localScopeIdentifier().toString() << thisContext->rangeInCurrentRevision(); } else { thisContext = top; } qCDebug(LANGUAGE) << "context is set to" << thisContext.data(); } lock.unlock(); if(m_forceWaitForModel) emit waitForReset(); emit completionsNeeded(thisContext, range.start(), view); } void CodeCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType) { //If this triggers, initialize() has not been called after creation. Q_ASSERT(m_thread); KDevelop::ICompletionSettings::CompletionLevel level = KDevelop::ICore::self()->languageController()->completionSettings()->completionLevel(); if(level == KDevelop::ICompletionSettings::AlwaysFull || (invocationType != AutomaticInvocation && level == KDevelop::ICompletionSettings::MinimalWhenAutomatic)) m_fullCompletion = true; else m_fullCompletion = false; //Only use grouping in full completion mode setHasGroups(m_fullCompletion); Q_UNUSED(invocationType) if (!worker()) { qCWarning(LANGUAGE) << "Completion invoked on a completion model which has no code completion worker assigned!"; } beginResetModel(); m_completionItems.clear(); endResetModel(); worker()->abortCurrentCompletion(); worker()->setFullCompletion(m_fullCompletion); QUrl url = view->document()->url(); completionInvokedInternal(view, range, invocationType, url); } void CodeCompletionModel::foundDeclarations(const QList>& items, const QExplicitlySharedDataPointer& completionContext) { m_completionContext = completionContext; if(m_completionItems.isEmpty() && items.isEmpty()) { if(m_forceWaitForModel) { // TODO KF5: Check if this actually works beginResetModel(); endResetModel(); //If we need to reset the model, reset it } return; //We don't need to reset, which is bad for target model } beginResetModel(); m_completionItems = items; endResetModel(); if(m_completionContext) { qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements"; } } KTextEditor::CodeCompletionModelControllerInterface::MatchReaction CodeCompletionModel::matchingItem(const QModelIndex& /*matched*/) { return None; } void CodeCompletionModel::setCompletionContext(const QExplicitlySharedDataPointer& completionContext) { QMutexLocker lock(m_mutex); m_completionContext = completionContext; if(m_completionContext) { qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements"; } } QExplicitlySharedDataPointer CodeCompletionModel::completionContext() const { QMutexLocker lock(m_mutex); return m_completionContext; } void CodeCompletionModel::executeCompletionItem(View* view, const KTextEditor::Range& word, const QModelIndex& index) const { //We must not lock the duchain at this place, because the items might rely on that CompletionTreeElement* element = static_cast(index.internalPointer()); if( !element || !element->asItem() ) return; element->asItem()->execute(view, word); } QExplicitlySharedDataPointer CodeCompletionModel::itemForIndex(const QModelIndex& index) const { CompletionTreeElement* element = static_cast(index.internalPointer()); return QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement >(element); } QVariant CodeCompletionModel::data(const QModelIndex& index, int role) const { if ( role == Qt::TextAlignmentRole && index.column() == 0 ) { return Qt::AlignRight; } auto element = static_cast(index.internalPointer()); if( !element ) return QVariant(); if( role == CodeCompletionModel::GroupRole ) { if( element->asNode() ) { return QVariant(element->asNode()->role); }else { qCDebug(LANGUAGE) << "Requested group-role from leaf tree element"; return QVariant(); } }else{ if( element->asNode() ) { if( role == CodeCompletionModel::InheritanceDepth ) { auto customGroupNode = dynamic_cast(element); if(customGroupNode) return QVariant(customGroupNode->inheritanceDepth); } if( role == element->asNode()->role ) { return element->asNode()->roleValue; } else { return QVariant(); } } } if(!element->asItem()) { qCWarning(LANGUAGE) << "Error in completion model"; return QVariant(); } //Navigation widget interaction is done here, the other stuff is done within the tree-elements switch (role) { case CodeCompletionModel::InheritanceDepth: return element->asItem()->inheritanceDepth(); case CodeCompletionModel::ArgumentHintDepth: return element->asItem()->argumentHintDepth(); case CodeCompletionModel::ItemSelected: { DeclarationPointer decl = element->asItem()->declaration(); if(decl) { DUChain::self()->emitDeclarationSelected(decl); } break; } } //In minimal completion mode, hide all columns except the "name" one if(!m_fullCompletion && role == Qt::DisplayRole && index.column() != Name && (element->asItem()->argumentHintDepth() == 0 || index.column() == Prefix)) return QVariant(); //In reduced completion mode, don't show information text with the selected items if(role == ItemSelected && (!m_fullCompletion || !ICore::self()->languageController()->completionSettings()->showMultiLineSelectionInformation())) return QVariant(); return element->asItem()->data(index, role, this); } KDevelop::TopDUContextPointer CodeCompletionModel::currentTopContext() const { return m_currentTopContext; } void CodeCompletionModel::setCurrentTopContext(const KDevelop::TopDUContextPointer& topContext) { m_currentTopContext = topContext; } QModelIndex CodeCompletionModel::index(int row, int column, const QModelIndex& parent) const { if( parent.isValid() ) { CompletionTreeElement* element = static_cast(parent.internalPointer()); CompletionTreeNode* node = element->asNode(); if( !node ) { qCDebug(LANGUAGE) << "Requested sub-index of leaf node"; return QModelIndex(); } if (row < 0 || row >= node->children.count() || column < 0 || column >= ColumnCount) return QModelIndex(); return createIndex(row, column, node->children[row].data()); } else { if (row < 0 || row >= m_completionItems.count() || column < 0 || column >= ColumnCount) return QModelIndex(); return createIndex(row, column, const_cast(m_completionItems[row].data())); } } QModelIndex CodeCompletionModel::parent ( const QModelIndex & index ) const { if(rowCount() == 0) return QModelIndex(); if( index.isValid() ) { CompletionTreeElement* element = static_cast(index.internalPointer()); if( element->parent() ) return createIndex( element->rowInParent(), element->columnInParent(), element->parent() ); } return QModelIndex(); } int CodeCompletionModel::rowCount ( const QModelIndex & parent ) const { if( parent.isValid() ) { CompletionTreeElement* element = static_cast(parent.internalPointer()); CompletionTreeNode* node = element->asNode(); if( !node ) return 0; return node->children.count(); }else{ return m_completionItems.count(); } } QString CodeCompletionModel::filterString(KTextEditor::View* view, const KTextEditor::Range& range, const KTextEditor::Cursor& position) { m_filterString = KTextEditor::CodeCompletionModelControllerInterface::filterString(view, range, position); return m_filterString; } } #include "moc_codecompletionmodel.cpp" #include "codecompletionmodel.moc" diff --git a/kdevplatform/language/codecompletion/codecompletionmodel.h b/kdevplatform/language/codecompletion/codecompletionmodel.h index d3c936c24c..500017a3f8 100644 --- a/kdevplatform/language/codecompletion/codecompletionmodel.h +++ b/kdevplatform/language/codecompletion/codecompletionmodel.h @@ -1,144 +1,145 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 Hamish Rodda * Copyright 2007-2008 David Nolden * * 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 KDEVPLATFORM_CODECOMPLETIONMODEL_H #define KDEVPLATFORM_CODECOMPLETIONMODEL_H #include #include #include #include #include #include "../duchain/duchainpointer.h" #include #include "codecompletioncontext.h" #include "codecompletionitem.h" -#include -#include + +#include +#include class QMutex; namespace KDevelop { class DUContext; class Declaration; class CodeCompletionWorker; class CompletionWorkerThread; class KDEVPLATFORMLANGUAGE_EXPORT CodeCompletionModel : public KTextEditor::CodeCompletionModel , public KTextEditor::CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: explicit CodeCompletionModel(QObject* parent); ~CodeCompletionModel() override; ///This MUST be called after the creation of this completion-model. ///If you use use the KDevelop::CodeCompletion helper-class, that one cares about it. virtual void initialize(); ///Entry-point for code-completion. This determines ome settings, clears the model, and then calls completionInvokedInternal for further processing. void completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, KTextEditor::CodeCompletionModel::InvocationType invocationType) override; QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount ( const QModelIndex & parent = QModelIndex() ) const override; QModelIndex parent ( const QModelIndex & index ) const override; ///Use this to set whether the code-completion widget should wait for this model until it's shown ///This makes sense when the model takes some time but not too much time, to make the UI less flickering and ///annoying. ///The default is false ///@todo Remove this option, make it true by default, and make sure in CodeCompletionWorker that the whole thing cannot break void setForceWaitForModel(bool wait); bool forceWaitForModel(); ///Convenience-storage for use by the inherited completion model void setCompletionContext(const QExplicitlySharedDataPointer& completionContext); QExplicitlySharedDataPointer completionContext() const; ///Convenience-storage for use by the inherited completion model KDevelop::TopDUContextPointer currentTopContext() const; void setCurrentTopContext(const KDevelop::TopDUContextPointer& topContext); ///Whether the completion should be fully detailed. If false, it should be simplifed, so no argument-hints, ///no expanding information, no type-information, etc. bool fullCompletion() const; MatchReaction matchingItem(const QModelIndex& matched) override; QString filterString(KTextEditor::View* view, const KTextEditor::Range& range, const KTextEditor::Cursor& position) override; void clear(); ///Returns the tree-element that belogns to the index, or zero QExplicitlySharedDataPointer itemForIndex(const QModelIndex& index) const; Q_SIGNALS: ///Connection from this completion-model into the background worker thread. You should emit this from within completionInvokedInternal. void completionsNeeded(const KDevelop::DUContextPointer& context, const KTextEditor::Cursor& position, KTextEditor::View* view); ///Additional signal that allows directly stepping into the worker-thread, bypassing computeCompletions(..) etc. ///doSpecialProcessing(data) will be executed in the background thread. void doSpecialProcessingInBackground(uint data); protected Q_SLOTS: ///Connection from the background-thread into the model: This is called when the background-thread is ready virtual void foundDeclarations(const QList>& item, const QExplicitlySharedDataPointer& completionContext); protected: ///Eventually override this, determine the context or whatever, and then emit completionsNeeded(..) to continue processing in the background tread. ///The default-implementation does this completely, so if you don't need to do anything special, you can just leave it. virtual void completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, KTextEditor::CodeCompletionModel::InvocationType invocationType, const QUrl& url); void executeCompletionItem(KTextEditor::View* view, const KTextEditor::Range& word, const QModelIndex& index) const override; QExplicitlySharedDataPointer m_completionContext; typedef QPair > DeclarationContextPair; QList< QExplicitlySharedDataPointer > m_completionItems; /// Should create a completion-worker. The worker must have no parent object, /// because else its thread-affinity can not be changed. virtual CodeCompletionWorker* createCompletionWorker() = 0; friend class CompletionWorkerThread; CodeCompletionWorker* worker() const; private: bool m_forceWaitForModel; bool m_fullCompletion; QMutex* m_mutex; CompletionWorkerThread* m_thread; QString m_filterString; KDevelop::TopDUContextPointer m_currentTopContext; }; } #endif diff --git a/kdevplatform/language/codecompletion/codecompletionworker.cpp b/kdevplatform/language/codecompletion/codecompletionworker.cpp index 9676265d5d..1eb8c362fc 100644 --- a/kdevplatform/language/codecompletion/codecompletionworker.cpp +++ b/kdevplatform/language/codecompletion/codecompletionworker.cpp @@ -1,226 +1,226 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 Hamish Rodda * Copyright 2007-2008 David Nolden * * 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 "codecompletionworker.h" -#include -#include +#include +#include #include #include "../duchain/ducontext.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include #include "codecompletion.h" #include "codecompletionitem.h" #include "codecompletionmodel.h" #include "codecompletionitemgrouper.h" #include using namespace KTextEditor; using namespace KDevelop; CodeCompletionWorker::CodeCompletionWorker(KDevelop::CodeCompletionModel* model) : m_hasFoundDeclarations(false) , m_mutex(new QMutex()) , m_abort(false) , m_fullCompletion(true) , m_model(model) { } CodeCompletionWorker::~CodeCompletionWorker() { delete m_mutex; } void CodeCompletionWorker::setFullCompletion(bool full) { m_fullCompletion = full; } bool CodeCompletionWorker::fullCompletion() const { return m_fullCompletion; } void CodeCompletionWorker::failed() { foundDeclarations({}, {}); } void CodeCompletionWorker::foundDeclarations(const QList& items, const CodeCompletionContext::Ptr& completionContext) { m_hasFoundDeclarations = true; emit foundDeclarationsReal(items, completionContext); } void CodeCompletionWorker::computeCompletions(const KDevelop::DUContextPointer& context, const KTextEditor::Cursor& position, KTextEditor::View* view) { { QMutexLocker lock(m_mutex); m_abort = false; } ///@todo It's not entirely safe to pass KTextEditor::View* through a queued connection // We access the view/document which is not thread-safe, so we need the foreground lock ForegroundLock foreground; //Compute the text we should complete on KTextEditor::Document* doc = view->document(); if( !doc ) { qCDebug(LANGUAGE) << "No document for completion"; failed(); return; } KTextEditor::Range range; QString text; { QMutexLocker lock(m_mutex); DUChainReadLocker lockDUChain; if(context) { qCDebug(LANGUAGE) << context->localScopeIdentifier().toString(); range = KTextEditor::Range(context->rangeInCurrentRevision().start(), position); } else range = KTextEditor::Range(KTextEditor::Cursor(position.line(), 0), position); updateContextRange(range, view, context); text = doc->text(range); } if( position.column() == 0 ) //Seems like when the cursor is a the beginning of a line, kate does not give the \n text += QLatin1Char('\n'); if (aborting()) { failed(); return; } m_hasFoundDeclarations = false; KTextEditor::Cursor cursorPosition = view->cursorPosition(); QString followingText; //followingText may contain additional text that stands for the current item. For example in the case "QString|", QString is in addedText. if(position < cursorPosition) followingText = view->document()->text( KTextEditor::Range( position, cursorPosition ) ); foreground.unlock(); computeCompletions(context, position, followingText, range, text); if(!m_hasFoundDeclarations) failed(); } void KDevelop::CodeCompletionWorker::doSpecialProcessing(uint) { } CodeCompletionContext* CodeCompletionWorker::createCompletionContext(const KDevelop::DUContextPointer& context, const QString& contextText, const QString& followingText, const KDevelop::CursorInRevision& position) const { Q_UNUSED(context); Q_UNUSED(contextText); Q_UNUSED(followingText); Q_UNUSED(position); return nullptr; } void CodeCompletionWorker::computeCompletions(const KDevelop::DUContextPointer& context, const KTextEditor::Cursor& position, const QString& followingText, const KTextEditor::Range& contextRange, const QString& contextText) { Q_UNUSED(contextRange); qCDebug(LANGUAGE) << "added text:" << followingText; CodeCompletionContext::Ptr completionContext( createCompletionContext( context, contextText, followingText, CursorInRevision::castFromSimpleCursor(KTextEditor::Cursor(position)) ) ); if (KDevelop::CodeCompletionModel* m = model()) m->setCompletionContext(completionContext); if( completionContext && completionContext->isValid() ) { { DUChainReadLocker lock(DUChain::lock()); if (!context) { failed(); qCDebug(LANGUAGE) << "Completion context disappeared before completions could be calculated"; return; } } QList items = completionContext->completionItems(aborting(), fullCompletion()); if (aborting()) { failed(); return; } QList > tree = computeGroups( items, completionContext ); if(aborting()) { failed(); return; } tree += completionContext->ungroupedElements(); foundDeclarations( tree, completionContext ); } else { qCDebug(LANGUAGE) << "setContext: Invalid code-completion context"; } } QList> CodeCompletionWorker::computeGroups(const QList& items, const QExplicitlySharedDataPointer& completionContext) { Q_UNUSED(completionContext); QList > tree; /** * 1. Group by argument-hint depth * 2. Group by inheritance depth * 3. Group by simplified attributes * */ CodeCompletionItemGrouper > > argumentHintDepthGrouper(tree, nullptr, std::move(items)); return tree; } void CodeCompletionWorker::abortCurrentCompletion() { QMutexLocker lock(m_mutex); m_abort = true; } bool& CodeCompletionWorker::aborting() { return m_abort; } KDevelop::CodeCompletionModel* CodeCompletionWorker::model() const { return m_model; } void CodeCompletionWorker::updateContextRange(Range& contextRange, View* view, const DUContextPointer& context) const { Q_UNUSED(contextRange); Q_UNUSED(view); Q_UNUSED(context); } diff --git a/kdevplatform/language/codegen/applychangeswidget.cpp b/kdevplatform/language/codegen/applychangeswidget.cpp index a842743108..cba1582b10 100644 --- a/kdevplatform/language/codegen/applychangeswidget.cpp +++ b/kdevplatform/language/codegen/applychangeswidget.cpp @@ -1,203 +1,203 @@ /* Copyright 2008 Aleix Pol * Copyright 2009 Ramón Zarazúa * * 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 "applychangeswidget.h" -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include "coderepresentation.h" #include #include namespace KDevelop { class ApplyChangesWidgetPrivate { public: explicit ApplyChangesWidgetPrivate(ApplyChangesWidget * p) : parent(p), m_index(0) {} ~ApplyChangesWidgetPrivate() { qDeleteAll(m_temps); } void createEditPart(const KDevelop::IndexedString& url); ApplyChangesWidget * const parent; int m_index; QList m_editParts; QList m_temps; QList m_files; QTabWidget * m_documentTabs; QLabel* m_info; }; ApplyChangesWidget::ApplyChangesWidget(QWidget* parent) : QDialog(parent), d(new ApplyChangesWidgetPrivate(this)) { setSizeGripEnabled(true); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto mainLayout = new QVBoxLayout(this); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &ApplyChangesWidget::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ApplyChangesWidget::reject); QWidget* w=new QWidget(this); d->m_info=new QLabel(w); d->m_documentTabs = new QTabWidget(w); connect(d->m_documentTabs, &QTabWidget::currentChanged, this, &ApplyChangesWidget::indexChanged); QVBoxLayout* l = new QVBoxLayout(w); l->addWidget(d->m_info); l->addWidget(d->m_documentTabs); mainLayout->addWidget(w); mainLayout->addWidget(buttonBox); resize(QSize(800, 400)); } ApplyChangesWidget::~ApplyChangesWidget() = default; bool ApplyChangesWidget::hasDocuments() const { return d->m_editParts.size() > 0; } KTextEditor::Document* ApplyChangesWidget::document() const { return qobject_cast(d->m_editParts[d->m_index]); } void ApplyChangesWidget::setInformation(const QString & info) { d->m_info->setText(info); } void ApplyChangesWidget::addDocuments(const IndexedString & original) { int idx=d->m_files.indexOf(original); if(idx<0) { QWidget * w = new QWidget; d->m_documentTabs->addTab(w, original.str()); d->m_documentTabs->setCurrentWidget(w); d->m_files.insert(d->m_index, original); d->createEditPart(original); } else { d->m_index=idx; } } bool ApplyChangesWidget::applyAllChanges() { /// @todo implement safeguard in case a file saving fails bool ret = true; for(int i = 0; i < d->m_files.size(); ++i ) if(d->m_editParts[i]->saveAs(d->m_files[i].toUrl())) { IDocument* doc = ICore::self()->documentController()->documentForUrl(d->m_files[i].toUrl()); if(doc && doc->state()==IDocument::Dirty) doc->reload(); } else ret = false; return ret; } } namespace KDevelop { void ApplyChangesWidgetPrivate::createEditPart(const IndexedString & file) { QWidget * widget = m_documentTabs->currentWidget(); Q_ASSERT(widget); QVBoxLayout *m=new QVBoxLayout(widget); QSplitter *v=new QSplitter(widget); m->addWidget(v); QUrl url = file.toUrl(); QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(url); KParts::ReadWritePart* part=KMimeTypeTrader::self()->createPartInstanceFromQuery(mimetype.name(), widget, widget); KTextEditor::Document* document=qobject_cast(part); Q_ASSERT(document); Q_ASSERT(document->action("file_save")); document->action("file_save")->setEnabled(false); m_editParts.insert(m_index, part); //Open the best code representation, even if it is artificial CodeRepresentation::Ptr repr = createCodeRepresentation(file); if(!repr->fileExists()) { const QString templateName = QDir::tempPath() + QLatin1Char('/') + url.fileName().split(QLatin1Char('.')).last(); QTemporaryFile * temp(new QTemporaryFile(templateName)); temp->open(); temp->write(repr->text().toUtf8()); temp->close(); url = QUrl::fromLocalFile(temp->fileName()); m_temps << temp; } m_editParts[m_index]->openUrl(url); v->addWidget(m_editParts[m_index]->widget()); v->setSizes(QList{400, 100}); } void ApplyChangesWidget::indexChanged(int newIndex) { Q_ASSERT(newIndex != -1); d->m_index = newIndex; } void ApplyChangesWidget::updateDiffView(int index) { d->m_index = index == -1 ? d->m_index : index; } } diff --git a/kdevplatform/language/codegen/coderepresentation.h b/kdevplatform/language/codegen/coderepresentation.h index ecdbf4e1e3..b930af252f 100644 --- a/kdevplatform/language/codegen/coderepresentation.h +++ b/kdevplatform/language/codegen/coderepresentation.h @@ -1,186 +1,186 @@ /* Copyright 2008 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. */ #ifndef KDEVPLATFORM_CODEREPRESENTATION_H #define KDEVPLATFORM_CODEREPRESENTATION_H #include #include -#include -#include +#include +#include #include class QString; namespace KTextEditor { class Range; } namespace KDevelop { struct KDevEditingTransaction; class IndexedString; //NOTE: this is ugly, but otherwise kate might remove tabs again :-/ // see also: https://bugs.kde.org/show_bug.cgi?id=291074 struct EditorDisableReplaceTabs { explicit EditorDisableReplaceTabs(KTextEditor::Document* document) : m_iface(qobject_cast(document)), m_count(0) { ++m_count; if( m_count > 1 ) return; if ( m_iface ) { m_oldReplaceTabs = m_iface->configValue(configKey()); m_iface->setConfigValue(configKey(), false); } } ~EditorDisableReplaceTabs() { --m_count; if( m_count > 0 ) return; Q_ASSERT( m_count == 0 ); if (m_iface) m_iface->setConfigValue(configKey(), m_oldReplaceTabs); } inline QString configKey() const { return QStringLiteral("replace-tabs"); } KTextEditor::ConfigInterface* m_iface; int m_count; QVariant m_oldReplaceTabs; }; struct KDevEditingTransaction { explicit KDevEditingTransaction(KTextEditor::Document* document) : edit(document) , disableReplaceTabs(document) {} // NOTE: It's important to close the transaction first and only then destroy the EditorDisableReplaceTabs. Otherwise we hit asserts in KTextEditor. KTextEditor::Document::EditingTransaction edit; EditorDisableReplaceTabs disableReplaceTabs; typedef std::unique_ptr Ptr; }; /** * Allows getting code-lines conveniently, either through an open editor, or from a disk-loaded file. */ class KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation : public QSharedData { public: virtual ~CodeRepresentation() { } virtual QString line(int line) const = 0; virtual int lines() const = 0; virtual QString text() const = 0; virtual QString rangeText(const KTextEditor::Range& range) const; /** * Search for the given identifier in the document, and returns all ranges * where it was found. * @param identifier The identifier to search for * @param surroundedByBoundary Whether only matches that are surrounded by typical word-boundaries * should be acceded. Everything except letters, numbers, and the _ character * counts as word boundary. * */ virtual QVector grep(const QString& identifier, bool surroundedByBoundary = true) const = 0; /** * Overwrites the text in the file with the new given one * * @return true on success */ virtual bool setText(const QString&) = 0; /** @return true if this representation represents an actual file on disk */ virtual bool fileExists() const = 0; /** * Can be used for example from tests to disallow on-disk changes. When such a change is done, an assertion is triggered. * You should enable this within tests, unless you really want to work on the disk. */ static void setDiskChangesForbidden(bool changesForbidden); /** * Returns the specified name as a url for aritificial source code * suitable for code being inserted into the parser */ static QString artificialPath(const QString & name); typedef QExplicitlySharedDataPointer Ptr; }; class KDEVPLATFORMLANGUAGE_EXPORT DynamicCodeRepresentation : public CodeRepresentation { public: /** Used to group edit-history together. Call this optionally before a bunch * of replace() calls, to group them together. */ virtual KDevEditingTransaction::Ptr makeEditTransaction() = 0; virtual bool replace(const KTextEditor::Range& range, const QString& oldText, const QString& newText, bool ignoreOldText = false) = 0; typedef QExplicitlySharedDataPointer Ptr; }; /** * Creates a code-representation for the given url, that allows conveniently accessing its data. Returns zero on failure. */ KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation::Ptr createCodeRepresentation(const IndexedString& url); /** * @return true if an artificial code representation already exists for the specified URL */ KDEVPLATFORMLANGUAGE_EXPORT bool artificialCodeRepresentationExists(const IndexedString& url); /** * Allows inserting artificial source-code into the code-representation and parsing framework. * The source-code logically represents a file. * * The artificial code is automatically removed when the object is destroyed. */ class KDEVPLATFORMLANGUAGE_EXPORT InsertArtificialCodeRepresentation : public QSharedData { public: /** * Inserts an artifial source-code representation with filename @p file and the contents @p text * If @p file is not an absolute path or url, it will be made absolute using the CodeRepresentation::artifialUrl() * function, while ensuring that the name is unique. */ InsertArtificialCodeRepresentation(const IndexedString& file, const QString& text); ~InsertArtificialCodeRepresentation(); void setText(const QString& text); QString text() const; /** * Returns the file-name for this code-representation. */ IndexedString file(); private: InsertArtificialCodeRepresentation(const InsertArtificialCodeRepresentation&); InsertArtificialCodeRepresentation& operator=(const InsertArtificialCodeRepresentation&); IndexedString m_file; }; typedef QExplicitlySharedDataPointer InsertArtificialCodeRepresentationPointer; } #endif diff --git a/kdevplatform/language/duchain/duchainutils.h b/kdevplatform/language/duchain/duchainutils.h index 46234dc89e..b68d6e3f83 100644 --- a/kdevplatform/language/duchain/duchainutils.h +++ b/kdevplatform/language/duchain/duchainutils.h @@ -1,132 +1,132 @@ /* * DUChain Utilities * * Copyright 2007 Hamish Rodda * Copyright 2007-2009 David Nolden * * 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 KDEVPLATFORM_DUCHAINUTILS_H #define KDEVPLATFORM_DUCHAINUTILS_H #include -#include - #include #include #include +#include + class QIcon; namespace KTextEditor { class Cursor; } namespace KDevelop { class Declaration; class DUChainBase; class DUContext; class IndexedString; class TopDUContext; class IndexedDeclaration; /** * A namespace which contains convenience utilities for navigating definition-use chains. */ namespace DUChainUtils { KDEVPLATFORMLANGUAGE_EXPORT KTextEditor::CodeCompletionModel::CompletionProperties completionProperties(const Declaration* dec); KDEVPLATFORMLANGUAGE_EXPORT QIcon iconForProperties(KTextEditor::CodeCompletionModel::CompletionProperties p); KDEVPLATFORMLANGUAGE_EXPORT QIcon iconForDeclaration(const Declaration* dec); /** Asks the language-plugins for standard-contexts for the given url, and returns one if available. * If there is no language-plugin registered for the given url, it will just try to get any top-context for the file from the du-chain. * NOTE: The DUChain needs to be read or write locked when you call this. * @param preferProxyContext Whether the returned context should be a proxy context. When no proxy-context is found, a normal context is returned. * * FIXME: this should operate on IndexedString */ KDEVPLATFORMLANGUAGE_EXPORT KDevelop::TopDUContext* standardContextForUrl(const QUrl& url, bool preferProxyContext = false); /** * Returns the content-context associated to the given proxy-contex. * Returns the same context if it is not a proxy-context. * Returns zero if no content-context could be acquired. * */ KDEVPLATFORMLANGUAGE_EXPORT TopDUContext* contentContextFromProxyContext(TopDUContext* top); struct KDEVPLATFORMLANGUAGE_EXPORT ItemUnderCursor { Declaration* declaration; // found declaration (either declared/defined or used) DUContext* context; // context in which the declaration, definition, or use was found KTextEditor::Range range; // range of the declaration/definition/use }; /** Returns 1. the Declaration/Definition either declared or used under the cursor, * or zero; and 2. the context in which the declaration, definition, or use was found. * DUChain must be locked. * Must only be called from the foreground or with the foreground lock held. */ KDEVPLATFORMLANGUAGE_EXPORT ItemUnderCursor itemUnderCursor(const QUrl& url, const KTextEditor::Cursor& cursor); /**If the given declaration is a definition, and has a real declaration *attached, returns that declarations. Else returns the given argument. */ KDEVPLATFORMLANGUAGE_EXPORT Declaration* declarationForDefinition(Declaration* definition, TopDUContext* topContext = nullptr); ///Returns the first declaration in the given line. Searches the given context and all sub-contexts. ///Must only be called from the foreground or with the foreground lock held. KDEVPLATFORMLANGUAGE_EXPORT Declaration* declarationInLine(const KTextEditor::Cursor& cursor, KDevelop::DUContext* ctx); class KDEVPLATFORMLANGUAGE_EXPORT DUChainItemFilter { public: virtual bool accept(Declaration* decl) = 0; //Should return whether processing should be deepened into the given context virtual bool accept(DUContext* ctx) = 0; virtual ~DUChainItemFilter(); }; ///walks a context, all its sub-contexts, and all its declarations in exactly the order they appear in in the file. ///Re-implement DUChainItemFilter to do something with the items. KDEVPLATFORMLANGUAGE_EXPORT void collectItems( DUContext* context, DUChainItemFilter& filter ); KDEVPLATFORMLANGUAGE_EXPORT DUContext* getArgumentContext(Declaration* decl); ///Uses the persistent symbol table to find all occurrences of this declaration, based on its identifier. ///The result should be filtered to make sure that the declaration is actually useful to you. KDEVPLATFORMLANGUAGE_EXPORT QList collectAllVersions(Declaration* decl); ///If the given declaration is a class, this gets all classes that inherit this one ///@param collectVersions If this is true, the persistent symbol table is used to first find all registered /// versions of this class, and then get the inheriters from them all together. This is needed for C++. ///@param maxAllowedSteps The maximum of steps allowed. If this is zero in the end, this means the search has been stopped with the max. reached /// If you really want _all_ inheriters, you should initialize it with a very large value. KDEVPLATFORMLANGUAGE_EXPORT QList getInheriters(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions = true); ///Gets all functions that override the function @p overriddenDeclaration, starting the search at @p currentClass ///@param maxAllowedSteps The maximum of steps allowed. If this is zero in the end, this means the search has been stopped with the max. reached KDEVPLATFORMLANGUAGE_EXPORT QList getOverriders(const Declaration* currentClass, const Declaration* overriddenDeclaration, uint& maxAllowedSteps); ///Returns whether the given context or any of its child-contexts contain a use of the given declaration. This is relatively expensive. KDEVPLATFORMLANGUAGE_EXPORT bool contextHasUse(DUContext* context, Declaration* declaration); ///Returns the total count of uses of the given declaration under the given context KDEVPLATFORMLANGUAGE_EXPORT uint contextCountUses(DUContext* context, Declaration* declaration); ///Returns the declaration that is overridden by the given one, or zero. KDEVPLATFORMLANGUAGE_EXPORT Declaration* getOverridden(const Declaration* decl); ///If the given declaration is a function-declaration, this follows the context-structure up to the function-context that contains the arguments, ///and returns it. KDEVPLATFORMLANGUAGE_EXPORT DUContext* getFunctionContext(Declaration* decl); KDEVPLATFORMLANGUAGE_EXPORT QVector allProblemsForContext(const ReferencedTopDUContext& top); } } #endif // KDEVPLATFORM_DUCHAINUTILS_H diff --git a/kdevplatform/language/duchain/navigation/navigationaction.h b/kdevplatform/language/duchain/navigation/navigationaction.h index fa3fa913ac..3f76b8df00 100644 --- a/kdevplatform/language/duchain/navigation/navigationaction.h +++ b/kdevplatform/language/duchain/navigation/navigationaction.h @@ -1,74 +1,74 @@ /* 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. */ #ifndef KDEVPLATFORM_NAVIGATIONACTION_H #define KDEVPLATFORM_NAVIGATIONACTION_H #include -#include - #include "../duchainpointer.h" +#include + namespace KDevelop { class AbstractNavigationContext; struct NavigationAction { enum Type { None, NavigateDeclaration, NavigateUses, ShowUses, JumpToSource, //If this is set, the action jumps to document and cursor if they are valid, else to the declaration-position of decl ExecuteKey, //This is used to do changes within one single navigation-context. executeKey(key) will be called in the current context, //and the context has the chance to react in an arbitrary way. ShowDocumentation }; ///When executed, this navigation-action calls the "executeKeyAction(QString) function in its navigation-context explicit NavigationAction(const QString& _key) : type(ExecuteKey), key(_key) { } NavigationAction() { } NavigationAction( const DeclarationPointer& decl_, Type type_ ) : decl(decl_), type(type_) { } NavigationAction( const QUrl& _document, const KTextEditor::Cursor& _cursor) : type(JumpToSource), document(_document), cursor(_cursor) { } explicit NavigationAction(AbstractNavigationContext* _targetContext) : targetContext(_targetContext) { } AbstractNavigationContext* targetContext = nullptr; //If this is set, this action does nothing else than jumping to that context DeclarationPointer decl; Type type = None; QUrl document; KTextEditor::Cursor cursor; QString key; }; } Q_DECLARE_TYPEINFO(KDevelop::NavigationAction, Q_MOVABLE_TYPE); #endif diff --git a/kdevplatform/language/duchain/navigation/problemnavigationcontext.h b/kdevplatform/language/duchain/navigation/problemnavigationcontext.h index 33238e993a..1351ace577 100644 --- a/kdevplatform/language/duchain/navigation/problemnavigationcontext.h +++ b/kdevplatform/language/duchain/navigation/problemnavigationcontext.h @@ -1,73 +1,74 @@ /* Copyright 2009 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. */ #ifndef KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H #define KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H #include #include #include #include -#include + +#include namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT ProblemNavigationContext : public AbstractNavigationContext { Q_OBJECT public: enum Flag { NoFlag = 0, ShowLocation = 1 << 0, }; Q_DECLARE_FLAGS(Flags, Flag) explicit ProblemNavigationContext(const QVector& problems, const Flags flags = {}); ~ProblemNavigationContext() override; QString name() const override; QString html(bool shorten = false) override; QWidget* widget() const override; bool isWidgetMaximized() const override; NavigationContextPointer executeKeyAction(const QString& key) override; public Q_SLOTS: void executeAction(int index); // TODO: Add API in base class? private: void html(IProblem::Ptr problem); /** * Return HTML-ized text. Used for processing problem's description and explanation. * Some plugins (kdev-cppcheck for example) return already HTML-ized strings, * therefore we should make check for this case. */ QString escapedHtml(const QString& text) const; QVector m_problems; Flags m_flags; QPointer m_widget; QVector m_assistantsActions; }; } #endif // KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H diff --git a/kdevplatform/language/editor/cursorinrevision.h b/kdevplatform/language/editor/cursorinrevision.h index f5e14213ad..9d6e2565ed 100644 --- a/kdevplatform/language/editor/cursorinrevision.h +++ b/kdevplatform/language/editor/cursorinrevision.h @@ -1,115 +1,115 @@ /* This file is part of KDevelop Copyright 2010 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. */ #ifndef KDEVPLATFORM_CURSORINREVISION_H #define KDEVPLATFORM_CURSORINREVISION_H #include -#include +#include namespace KDevelop { /** * Represents a cursor (line-number and column-number) within a text document. * * In KDevelop, this object is used when referencing a cursor that does _not_ point into the * most most current document revision. Therefore, before applying such a cursor in the text * documents, it has to be translated into the current document revision explicitly, thereby replaying * eventual changes (see DUChainBase::translate...) */ class KDEVPLATFORMLANGUAGE_EXPORT CursorInRevision { public: int line = 0, column = 0; CursorInRevision() { } CursorInRevision(int _line, int _column) : line(_line), column(_column) { } static CursorInRevision invalid() { return CursorInRevision(-1, -1); } bool isValid() const { return line != -1 || column != -1; } bool operator<(const CursorInRevision& rhs) const { return line < rhs.line || (line == rhs.line && column < rhs.column); } bool operator<=(const CursorInRevision& rhs) const { return line < rhs.line || (line == rhs.line && column <= rhs.column); } bool operator>(const CursorInRevision& rhs) const { return line > rhs.line || (line == rhs.line && column > rhs.column); } bool operator>=(const CursorInRevision& rhs) const { return line > rhs.line || (line == rhs.line && column >= rhs.column); } bool operator ==(const CursorInRevision& rhs) const { return line == rhs.line && column == rhs.column; } bool operator !=(const CursorInRevision& rhs) const { return !(*this == rhs); } CursorInRevision operator +(const CursorInRevision& rhs) const { return CursorInRevision(line + rhs.line, column + rhs.column); } /// @warning Using this is wrong in most cases! If you want /// to transform this cursor to the current revision, you should do a proper /// mapping instead through @ref KDevelop::DUChainBase or @ref KDevelop::RevisionReference /// or @ref KDevelop::DocumentChangeTracker KTextEditor::Cursor castToSimpleCursor() const { return KTextEditor::Cursor(line, column); } /// @warning Using this is wrong in most cases! If you want /// to transform this cursor to the current revision, you should do a proper /// mapping instead through @ref KDevelop::DUChainBase or @ref KDevelop::RevisionReference /// or @ref KDevelop::DocumentChangeTracker static CursorInRevision castFromSimpleCursor(const KTextEditor::Cursor& cursor) { return CursorInRevision(cursor.line(), cursor.column()); } /// qDebug() stream operator. Writes this cursor to the debug output in a nicely formatted way. inline friend QDebug operator<< (QDebug s, const CursorInRevision& cursor) { s.nospace() << "(" << cursor.line << ", " << cursor.column << ")"; return s.space(); } }; inline uint qHash(const KDevelop::CursorInRevision& cursor) { return cursor.line * 53 + cursor.column * 47; } } // namespace KDevelop Q_DECLARE_TYPEINFO(KDevelop::CursorInRevision, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::CursorInRevision) #endif diff --git a/kdevplatform/language/editor/documentcursor.h b/kdevplatform/language/editor/documentcursor.h index eedfefe82e..a0bea51eef 100644 --- a/kdevplatform/language/editor/documentcursor.h +++ b/kdevplatform/language/editor/documentcursor.h @@ -1,55 +1,55 @@ /* This file is part of KDevelop Copyright 2010 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. */ #ifndef KDEVPLATFORM_DOCUMENTCURSOR_H #define KDEVPLATFORM_DOCUMENTCURSOR_H #include #include -#include +#include namespace KDevelop { /** * Lightweight object that extends a cursor with information about the document URL to which the range * refers. */ class DocumentCursor : public KTextEditor::Cursor { public: DocumentCursor() { } DocumentCursor(const IndexedString& document, const KTextEditor::Cursor& cursor) : KTextEditor::Cursor(cursor), document(document) { } static DocumentCursor invalid() { return DocumentCursor({}, KTextEditor::Cursor::invalid()); } inline bool operator==(const DocumentCursor& rhs) const { return document == rhs.document && *static_cast(this) == rhs; } IndexedString document; }; } #endif // KDEVPLATFORM_DOCUMENTCURSOR_H diff --git a/kdevplatform/language/editor/modificationrevision.cpp b/kdevplatform/language/editor/modificationrevision.cpp index 835aa8ad07..17fa63c472 100644 --- a/kdevplatform/language/editor/modificationrevision.cpp +++ b/kdevplatform/language/editor/modificationrevision.cpp @@ -1,144 +1,144 @@ /* Copyright 2008 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 "modificationrevision.h" #include #include -#include - #include #include "modificationrevisionset.h" +#include + /// @todo Listen to filesystem changes (together with the project manager) /// and call fileModificationCache().clear(...) when a file has changed using namespace KDevelop; const int KDevelop::cacheModificationTimesForSeconds = 30; QMutex fileModificationTimeCacheMutex(QMutex::Recursive); struct FileModificationCache { QDateTime m_readTime; QDateTime m_modificationTime; }; Q_DECLARE_TYPEINFO(FileModificationCache, Q_MOVABLE_TYPE); typedef QHash FileModificationMap; FileModificationMap& fileModificationCache() { static FileModificationMap cache; return cache; } typedef QHash OpenDocumentRevisionsMap; OpenDocumentRevisionsMap& openDocumentsRevisionMap() { static OpenDocumentRevisionsMap map; return map; } QDateTime fileModificationTimeCached( const IndexedString& fileName ) { const auto currentTime = QDateTime::currentDateTime(); auto it = fileModificationCache().constFind( fileName ); if ( it != fileModificationCache().constEnd() ) { ///Use the cache for X seconds if (it.value().m_readTime.secsTo(currentTime) < cacheModificationTimesForSeconds ) { return it.value().m_modificationTime; } } QFileInfo fileInfo( fileName.str() ); FileModificationCache data = {currentTime, fileInfo.lastModified()}; fileModificationCache().insert(fileName, data); return data.m_modificationTime; } void ModificationRevision::clearModificationCache(const IndexedString& fileName) { ///@todo Make the cache management more clever (don't clear the whole) ModificationRevisionSet::clearCache(); QMutexLocker lock(&fileModificationTimeCacheMutex); fileModificationCache().remove(fileName); } ModificationRevision ModificationRevision::revisionForFile(const IndexedString& url) { QMutexLocker lock(&fileModificationTimeCacheMutex); ModificationRevision ret(fileModificationTimeCached(url)); OpenDocumentRevisionsMap::const_iterator it = openDocumentsRevisionMap().constFind(url); if(it != openDocumentsRevisionMap().constEnd()) { ret.revision = it.value(); } return ret; } void ModificationRevision::clearEditorRevisionForFile(const KDevelop::IndexedString& url) { ModificationRevisionSet::clearCache(); ///@todo Make the cache management more clever (don't clear the whole) QMutexLocker lock(&fileModificationTimeCacheMutex); openDocumentsRevisionMap().remove(url); } void ModificationRevision::setEditorRevisionForFile(const KDevelop::IndexedString& url, int revision) { ModificationRevisionSet::clearCache(); ///@todo Make the cache management more clever (don't clear the whole) QMutexLocker lock(&fileModificationTimeCacheMutex); openDocumentsRevisionMap().insert(url, revision); Q_ASSERT(revisionForFile(url).revision == revision); } ModificationRevision::ModificationRevision( const QDateTime& modTime , int revision_ ) : modificationTime(modTime.toTime_t()) , revision(revision_) { } bool ModificationRevision::operator <( const ModificationRevision& rhs ) const { return modificationTime < rhs.modificationTime || (modificationTime == rhs.modificationTime && revision < rhs.revision); } bool ModificationRevision::operator ==( const ModificationRevision& rhs ) const { return modificationTime == rhs.modificationTime && revision == rhs.revision; } bool ModificationRevision::operator !=( const ModificationRevision& rhs ) const { return modificationTime != rhs.modificationTime || revision != rhs.revision; } QString ModificationRevision::toString() const { return QStringLiteral("%1 (rev %2)").arg(QDateTime::fromTime_t(modificationTime).time().toString()).arg(revision); } diff --git a/kdevplatform/language/editor/persistentmovingrange.h b/kdevplatform/language/editor/persistentmovingrange.h index 1c46ae5c84..328e405bb9 100644 --- a/kdevplatform/language/editor/persistentmovingrange.h +++ b/kdevplatform/language/editor/persistentmovingrange.h @@ -1,96 +1,96 @@ /* Copyright 2010 David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef KDEVPLATFORM_PERSISTENTMOVINGRANGE_H #define KDEVPLATFORM_PERSISTENTMOVINGRANGE_H #include -#include -#include +#include +#include #include namespace KDevelop { class IndexedString; class PersistentMovingRangePrivate; /** * A range object that is automatically adapted to all changes a user does to a document. The object * also survives when the document is opened or closed, as long as the document is only edited from within * the application. * * This object must only be used from within the foreground, or with the foreground lock held. * * @todo The implementation of this object is not finished yet, the range is only persistent until the * document is closed/reloaded/cleared. * */ class KDEVPLATFORMLANGUAGE_EXPORT PersistentMovingRange : public QSharedData { public: typedef QExplicitlySharedDataPointer Ptr; /** * Creates a new persistent moving range based on the current revision of the given document * */ PersistentMovingRange(const KTextEditor::Range& range, const IndexedString& document, bool shouldExpand = false); ~PersistentMovingRange(); IndexedString document() const; /** * Returns the range in the current revision of the document */ KTextEditor::Range range() const; /** * Changes the z-depth for highlighting (see KTextEditor::MovingRange) * */ void setZDepth(float depth) const; /** * Returns the text contained by the range. Currently only works when the range is open in the editor. * */ QString text() const; /** * Change the highlighting attribute. * */ void setAttribute(const KTextEditor::Attribute::Ptr& attribute); /** * Whether this range is still valid. The range is invalidated if the document is changed externally, * as such a change can not be tracked correctly. * */ bool valid() const; private: PersistentMovingRange(const PersistentMovingRange& ); PersistentMovingRange& operator=(const PersistentMovingRange& rhs); PersistentMovingRangePrivate* m_p; }; } #endif // KDEVPLATFORM_PERSISTENTMOVINGRANGE_H diff --git a/kdevplatform/language/editor/persistentmovingrangeprivate.h b/kdevplatform/language/editor/persistentmovingrangeprivate.h index 797939c624..9aba44a886 100644 --- a/kdevplatform/language/editor/persistentmovingrangeprivate.h +++ b/kdevplatform/language/editor/persistentmovingrangeprivate.h @@ -1,67 +1,67 @@ /* Copyright 2010 David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef KDEVPLATFORM_PERSISTENTMOVINGRANGEPRIVATE_H #define KDEVPLATFORM_PERSISTENTMOVINGRANGEPRIVATE_H -#include -#include #include +#include +#include #include namespace KDevelop { class PersistentMovingRangePrivate : public QObject { Q_OBJECT public: PersistentMovingRangePrivate() { moveToThread(QApplication::instance()->thread()); } void connectTracker(); void disconnectTracker(); bool m_valid = false; bool m_shouldExpand = false; KTextEditor::Range m_range; IndexedString m_document; KTextEditor::Attribute::Ptr m_attribte; KTextEditor::MovingRange* m_movingRange = nullptr; QPointer m_tracker; float m_zDepth = 0; void updateRangeFromMoving() { if(m_movingRange) { m_range = m_movingRange->toRange(); } } private Q_SLOTS: void aboutToDeleteMovingInterfaceContent(); void aboutToInvalidateMovingInterfaceContent(); }; } #endif // KDEVPLATFORM_PERSISTENTMOVINGRANGEPRIVATE_H diff --git a/kdevplatform/language/editor/rangeinrevision.h b/kdevplatform/language/editor/rangeinrevision.h index 1327be84eb..280cc71c20 100644 --- a/kdevplatform/language/editor/rangeinrevision.h +++ b/kdevplatform/language/editor/rangeinrevision.h @@ -1,125 +1,125 @@ /* This file is part of KDevelop Copyright 2010 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. */ #ifndef KDEVPLATFORM_RANGEINREVISION_H #define KDEVPLATFORM_RANGEINREVISION_H #include #include "cursorinrevision.h" -#include +#include namespace KDevelop { /** * Represents a range (start- and end cursor) within a text document. * * In KDevelop, this object is used when referencing a ranges that do _not_ point into the * most current document revision. Therefore, before applying such a range in the text * documents, it has to be translated into the current document revision explicitly, thereby replaying * eventual changes (see DUChainBase::translate...) */ class KDEVPLATFORMLANGUAGE_EXPORT RangeInRevision { public: CursorInRevision start, end; RangeInRevision(const CursorInRevision& _start, const CursorInRevision& _end) : start(_start), end(_end) { } RangeInRevision(const CursorInRevision& _start, int length) : start(_start), end(_start.line, _start.column + length) { } RangeInRevision() { } RangeInRevision(int sLine, int sCol, int eLine, int eCol) : start(sLine, sCol), end(eLine, eCol) { } static RangeInRevision invalid() { return RangeInRevision(-1, -1, -1, -1); } bool isValid() const { return start.column != -1 || start.line != -1 || end.column != -1 || end.line != -1; } bool isEmpty() const { return start == end; } enum ContainsBehavior { Default = 0, IncludeBackEdge = 1 }; /** * Checks if @p position is contained within this range (i.e. >= start and < end) * If @p cb is IncludeBackEdge, also checks that @p position == end */ bool contains(const CursorInRevision& position, ContainsBehavior cb = Default) const { return (position >= start && position < end) || (cb == IncludeBackEdge && position == end ); } bool contains(const RangeInRevision& range) const { return range.start >= start && range.end <= end; } bool operator ==( const RangeInRevision& rhs ) const { return start == rhs.start && end == rhs.end; } bool operator !=( const RangeInRevision& rhs ) const { return !(*this == rhs); } bool operator <( const RangeInRevision& rhs ) const { return start < rhs.start; } /// @warning Using this is wrong in most cases! If you want /// to transform this range to the current revision, you should do a proper /// mapping instead through @ref KDevelop::DUChainBase or @ref KDevelop::RevisionReference /// or @ref KDevelop::DocumentChangeTracker KTextEditor::Range castToSimpleRange() const { return KTextEditor::Range(start.castToSimpleCursor(), end.castToSimpleCursor()); } /// @warning Using this is wrong in most cases! If you want /// to transform this range to the current revision, you should do a proper /// mapping instead through @ref KDevelop::DUChainBase or @ref KDevelop::RevisionReference /// or @ref KDevelop::DocumentChangeTracker static RangeInRevision castFromSimpleRange(const KTextEditor::Range& range) { return RangeInRevision(range.start().line(), range.start().column(), range.end().line(), range.end().column()); } ///qDebug() stream operator. Writes this range to the debug output in a nicely formatted way. inline friend QDebug operator<< (QDebug s, const RangeInRevision& range) { s.nospace() << '[' << range.start << ", " << range.end << ']'; return s.space(); } }; inline uint qHash(const KDevelop::RangeInRevision& range) { return qHash(range.start) + qHash(range.end)*41; } } // namespace KDevelop Q_DECLARE_TYPEINFO(KDevelop::RangeInRevision, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::RangeInRevision) #endif diff --git a/kdevplatform/language/highlighting/codehighlighting.cpp b/kdevplatform/language/highlighting/codehighlighting.cpp index 08338f0cb5..74458b9427 100644 --- a/kdevplatform/language/highlighting/codehighlighting.cpp +++ b/kdevplatform/language/highlighting/codehighlighting.cpp @@ -1,643 +1,643 @@ /* * This file is part of KDevelop * * Copyright 2007-2010 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 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 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 "codehighlighting.h" -#include - #include "../../interfaces/icore.h" #include "../../interfaces/ilanguagecontroller.h" #include "../../interfaces/icompletionsettings.h" #include "../../util/foregroundlock.h" #include #include "../duchain/declaration.h" #include "../duchain/types/functiontype.h" #include "../duchain/types/enumeratortype.h" #include "../duchain/types/typealiastype.h" #include "../duchain/types/enumerationtype.h" #include "../duchain/types/structuretype.h" #include "../duchain/functiondefinition.h" #include "../duchain/use.h" #include "colorcache.h" #include "configurablecolors.h" #include #include -#include #include +#include +#include + using namespace KTextEditor; static const float highlightingZDepth = -500; #define ifDebug(x) namespace KDevelop { ///@todo Don't highlighting everything, only what is visible on-demand CodeHighlighting::CodeHighlighting( QObject * parent ) : QObject(parent), m_localColorization(true), m_globalColorization(true), m_dataMutex(QMutex::Recursive) { qRegisterMetaType("KDevelop::IndexedString"); adaptToColorChanges(); connect(ColorCache::self(), &ColorCache::colorsGotChanged, this, &CodeHighlighting::adaptToColorChanges); } CodeHighlighting::~CodeHighlighting( ) { qDeleteAll(m_highlights); } void CodeHighlighting::adaptToColorChanges() { QMutexLocker lock(&m_dataMutex); // disable local highlighting if the ratio is set to 0 m_localColorization = ICore::self()->languageController()->completionSettings()->localColorizationLevel() > 0; // disable global highlighting if the ratio is set to 0 m_globalColorization = ICore::self()->languageController()->completionSettings()->globalColorizationLevel() > 0; m_declarationAttributes.clear(); m_definitionAttributes.clear(); m_depthAttributes.clear(); m_referenceAttributes.clear(); } KTextEditor::Attribute::Ptr CodeHighlighting::attributeForType( Types type, Contexts context, const QColor &color ) const { QMutexLocker lock(&m_dataMutex); KTextEditor::Attribute::Ptr a; switch (context) { case DefinitionContext: a = m_definitionAttributes[type]; break; case DeclarationContext: a = m_declarationAttributes[type]; break; case ReferenceContext: a = m_referenceAttributes[type]; break; } if ( !a || color.isValid() ) { a = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(*ColorCache::self()->defaultColors()->attribute(type))); if ( context == DefinitionContext || context == DeclarationContext ) { if (ICore::self()->languageController()->completionSettings()->boldDeclarations()) { a->setFontBold(); } } if( color.isValid() ) { a->setForeground(color); // a->setBackground(QColor(mix(0xffffff-color, backgroundColor(), 255-backgroundTinting))); } else { switch (context) { case DefinitionContext: m_definitionAttributes.insert(type, a); break; case DeclarationContext: m_declarationAttributes.insert(type, a); break; case ReferenceContext: m_referenceAttributes.insert(type, a); break; } } } return a; } ColorMap emptyColorMap() { ColorMap ret(ColorCache::self()->validColorCount()+1, nullptr); return ret; } CodeHighlightingInstance* CodeHighlighting::createInstance() const { return new CodeHighlightingInstance(this); } bool CodeHighlighting::hasHighlighting(IndexedString url) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); if(tracker) { QMutexLocker lock(&m_dataMutex); return m_highlights.contains(tracker) && !m_highlights[tracker]->m_highlightedRanges.isEmpty(); } return false; } void CodeHighlighting::highlightDUChain(ReferencedTopDUContext context) { ENSURE_CHAIN_NOT_LOCKED IndexedString url; { DUChainReadLocker lock; if (!context) return; url = context->url(); } // This prevents the background-parser from updating the top-context while we're working with it UrlParseLock urlLock(context->url()); DUChainReadLocker lock; qint64 revision = context->parsingEnvironmentFile()->modificationRevision().revision; qCDebug(LANGUAGE) << "highlighting du chain" << url.toUrl(); if ( !m_localColorization && !m_globalColorization ) { qCDebug(LANGUAGE) << "highlighting disabled"; QMetaObject::invokeMethod(this, "clearHighlightingForDocument", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url)); return; } CodeHighlightingInstance* instance = createInstance(); lock.unlock(); instance->highlightDUChain(context.data()); DocumentHighlighting* highlighting = new DocumentHighlighting; highlighting->m_document = url; highlighting->m_waitingRevision = revision; highlighting->m_waiting = instance->m_highlight; std::sort(highlighting->m_waiting.begin(), highlighting->m_waiting.end()); QMetaObject::invokeMethod(this, "applyHighlighting", Qt::QueuedConnection, Q_ARG(void*, highlighting)); delete instance; } void CodeHighlightingInstance::highlightDUChain(TopDUContext* context) { m_contextClasses.clear(); m_useClassCache = true; //Highlight highlightDUChain(context, QHash(), emptyColorMap()); m_functionColorsForDeclarations.clear(); m_functionDeclarationsForColors.clear(); m_useClassCache = false; m_contextClasses.clear(); } void CodeHighlightingInstance::highlightDUChain(DUContext* context, QHash colorsForDeclarations, ColorMap declarationsForColors) { DUChainReadLocker lock; TopDUContext* top = context->topContext(); //Merge the colors from the function arguments foreach( const DUContext::Import &imported, context->importedParentContexts() ) { if(!imported.context(top) || (imported.context(top)->type() != DUContext::Other && imported.context(top)->type() != DUContext::Function)) continue; //For now it's enough simply copying them, because we only pass on colors within function bodies. if (m_functionColorsForDeclarations.contains(imported.context(top))) colorsForDeclarations = m_functionColorsForDeclarations[imported.context(top)]; if (m_functionDeclarationsForColors.contains(imported.context(top))) declarationsForColors = m_functionDeclarationsForColors[imported.context(top)]; } QList takeFreeColors; foreach (Declaration* dec, context->localDeclarations()) { if (!useRainbowColor(dec)) { highlightDeclaration(dec, QColor(QColor::Invalid)); continue; } //Initially pick a color using the hash, so the chances are good that the same identifier gets the same color always. uint colorNum = dec->identifier().hash() % ColorCache::self()->primaryColorCount(); if( declarationsForColors[colorNum] ) { takeFreeColors << dec; //Use one of the colors that stays free continue; } colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } foreach (Declaration* dec, takeFreeColors) { uint colorNum = dec->identifier().hash() % ColorCache::self()->primaryColorCount(); uint oldColorNum = colorNum; while (declarationsForColors[colorNum]) { colorNum = (colorNum + 1) % ColorCache::self()->primaryColorCount(); if (colorNum == oldColorNum) { colorNum = ColorCache::self()->primaryColorCount(); break; } } if (colorNum < ColorCache::self()->primaryColorCount()) { // Use primary color colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } else { // Try to use supplementary color colorNum = ColorCache::self()->primaryColorCount(); while (declarationsForColors[colorNum]) { colorNum++; if (colorNum == ColorCache::self()->validColorCount()) { //If no color could be found, use default color highlightDeclaration(dec, QColor(QColor::Invalid)); break; } } if (colorNum < ColorCache::self()->validColorCount()) { // Use supplementary color colorsForDeclarations[dec] = colorNum; declarationsForColors[colorNum] = dec; highlightDeclaration(dec, ColorCache::self()->generatedColor(colorNum)); } } } for(int a = 0; a < context->usesCount(); ++a) { Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[a].m_declarationIndex); QColor color(QColor::Invalid); if( colorsForDeclarations.contains(decl) ) color = ColorCache::self()->generatedColor(colorsForDeclarations[decl]); highlightUse(context, a, color); } if(context->type() == DUContext::Other || context->type() == DUContext::Function) { m_functionColorsForDeclarations[IndexedDUContext(context)] = colorsForDeclarations; m_functionDeclarationsForColors[IndexedDUContext(context)] = declarationsForColors; } const QVector children = context->childContexts(); lock.unlock(); // Periodically release the lock, so that the UI won't be blocked too much for (DUContext* child : children) { highlightDUChain(child, colorsForDeclarations, declarationsForColors ); } } KTextEditor::Attribute::Ptr CodeHighlighting::attributeForDepth(int depth) const { while (depth >= m_depthAttributes.count()) { KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute()); a->setBackground(QColor(Qt::white).dark(100 + (m_depthAttributes.count() * 25))); a->setBackgroundFillWhitespace(true); if (depth % 2) a->setOutline(Qt::red); m_depthAttributes.append(a); } return m_depthAttributes[depth]; } KDevelop::Declaration* CodeHighlightingInstance::localClassFromCodeContext(KDevelop::DUContext* context) const { if(!context) return nullptr; if(m_contextClasses.contains(context)) return m_contextClasses[context]; DUContext* startContext = context; while( context->type() == DUContext::Other ) { //Move context to the top context of type "Other". This is needed because every compound-statement creates a new sub-context. auto parent = context->parentContext(); if (!parent || (parent->type() != DUContext::Other && parent->type() != DUContext::Function)) { break; } context = context->parentContext(); } ///Step 1: Find the function-declaration for the function we are in Declaration* functionDeclaration = nullptr; if( FunctionDefinition* def = dynamic_cast(context->owner()) ) { if(m_contextClasses.contains(context)) return m_contextClasses[context]; functionDeclaration = def->declaration(startContext->topContext()); } if( !functionDeclaration && context->owner() ) functionDeclaration = context->owner(); if(!functionDeclaration) { if(m_useClassCache) m_contextClasses[context] = nullptr; return nullptr; } Declaration* decl = functionDeclaration->context()->owner(); if(m_useClassCache) m_contextClasses[context] = decl; return decl; } CodeHighlightingInstance::Types CodeHighlightingInstance::typeForDeclaration(Declaration * dec, DUContext* context) const { /** * We highlight in 3 steps by priority: * 1. Is the item in the local class or an inherited class? If yes, highlight. * 2. What kind of item is it? If it's a type/function/enumerator, highlight by type. * 3. Else, highlight by scope. * * */ // if(ClassMemberDeclaration* classMember = dynamic_cast(dec)) // if(!Cpp::isAccessible(context, classMember)) // return ErrorVariableType; if(!dec) return ErrorVariableType; Types type = LocalVariableType; if(dec->kind() == Declaration::Namespace) return NamespaceType; if(dec->kind() == Declaration::Macro){ return MacroType; } if (context && dec->context() && dec->context()->type() == DUContext::Class) { //It is a use. //Determine the class we're in Declaration* klass = localClassFromCodeContext(context); if(klass) { if (klass->internalContext() == dec->context()) type = LocalClassMemberType; //Using Member of the local class else if (klass->internalContext() && klass->internalContext()->imports(dec->context())) type = InheritedClassMemberType; //Using Member of an inherited class } } if (type == LocalVariableType) { if (dec->kind() == Declaration::Type || dec->type() || dec->type()) { if (dec->isForwardDeclaration()) type = ForwardDeclarationType; else if (dec->type()) type = FunctionType; else if(dec->type()) type = ClassType; else if(dec->type()) type = TypeAliasType; else if(dec->type()) type = EnumType; else if(dec->type()) type = EnumeratorType; } } if (type == LocalVariableType) { switch (dec->context()->type()) { case DUContext::Namespace: type = NamespaceVariableType; break; case DUContext::Class: type = MemberVariableType; break; case DUContext::Function: type = FunctionVariableType; break; case DUContext::Global: type = GlobalVariableType; break; default: break; } } return type; } bool CodeHighlightingInstance::useRainbowColor(Declaration* dec) const { return dec->context()->type() == DUContext::Function || (dec->context()->type() == DUContext::Other && dec->context()->owner()); } void CodeHighlightingInstance::highlightDeclaration(Declaration * declaration, const QColor &color) { HighlightedRange h; h.range = declaration->range(); h.attribute = m_highlighting->attributeForType(typeForDeclaration(declaration, nullptr), DeclarationContext, color); m_highlight.push_back(h); } void CodeHighlightingInstance::highlightUse(DUContext* context, int index, const QColor &color) { Types type = ErrorVariableType; Declaration* decl = context->topContext()->usedDeclarationForIndex(context->uses()[index].m_declarationIndex); type = typeForDeclaration(decl, context); if(type != ErrorVariableType || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems()) { HighlightedRange h; h.range = context->uses()[index].m_range; h.attribute = m_highlighting->attributeForType(type, ReferenceContext, color); m_highlight.push_back(h); } } void CodeHighlightingInstance::highlightUses(DUContext* context) { for(int a = 0; a < context->usesCount(); ++a) highlightUse(context, a, QColor(QColor::Invalid)); } void CodeHighlighting::clearHighlightingForDocument(const IndexedString& document) { VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document); if(m_highlights.contains(tracker)) { disconnect(tracker, &DocumentChangeTracker::destroyed, this, &CodeHighlighting::trackerDestroyed); qDeleteAll(m_highlights[tracker]->m_highlightedRanges); delete m_highlights[tracker]; m_highlights.remove(tracker); } } void CodeHighlighting::applyHighlighting(void* _highlighting) { CodeHighlighting::DocumentHighlighting* highlighting = static_cast(_highlighting); VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(highlighting->m_document); if(!tracker) { qCDebug(LANGUAGE) << "no document found for the planned highlighting of" << highlighting->m_document.str(); delete highlighting; return; } if(!tracker->holdingRevision(highlighting->m_waitingRevision)) { qCDebug(LANGUAGE) << "not holding revision" << highlighting->m_waitingRevision << "not applying highlighting;" << "probably a new parse job has already updated the context"; delete highlighting; return; } QVector< MovingRange* > oldHighlightedRanges; if(m_highlights.contains(tracker)) { oldHighlightedRanges = m_highlights[tracker]->m_highlightedRanges; delete m_highlights[tracker]; }else{ // we newly add this tracker, so add the connection // This can't use new style connect syntax since MovingInterface is not a QObject connect(tracker->document(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); connect(tracker->document(), SIGNAL(aboutToRemoveText(KTextEditor::Range)), this, SLOT(aboutToRemoveText(KTextEditor::Range))); connect(tracker, &DocumentChangeTracker::destroyed, this, &CodeHighlighting::trackerDestroyed); } m_highlights[tracker] = highlighting; // Now create MovingRanges (match old ones with the incoming ranges) KTextEditor::Range tempRange; QVector::iterator movingIt = oldHighlightedRanges.begin(); QVector::iterator rangeIt = highlighting->m_waiting.begin(); while(rangeIt != highlighting->m_waiting.end()) { // Translate the range into the current revision KTextEditor::Range transformedRange = tracker->transformToCurrentRevision(rangeIt->range, highlighting->m_waitingRevision); while(movingIt != oldHighlightedRanges.end() && ((*movingIt)->start().line() < transformedRange.start().line() || ((*movingIt)->start().line() == transformedRange.start().line() && (*movingIt)->start().column() < transformedRange.start().column()))) { delete *movingIt; // Skip ranges that are in front of the current matched range ++movingIt; } tempRange = transformedRange; if(movingIt == oldHighlightedRanges.end() || transformedRange.start().line() != (*movingIt)->start().line() || transformedRange.start().column() != (*movingIt)->start().column() || transformedRange.end().line() != (*movingIt)->end().line() || transformedRange.end().column() != (*movingIt)->end().column()) { Q_ASSERT(rangeIt->attribute); // The moving range is behind or unequal, create a new range highlighting->m_highlightedRanges.push_back(tracker->documentMovingInterface()->newMovingRange(tempRange)); highlighting->m_highlightedRanges.back()->setAttribute(rangeIt->attribute); highlighting->m_highlightedRanges.back()->setZDepth(highlightingZDepth); } else { // Update the existing moving range (*movingIt)->setAttribute(rangeIt->attribute); (*movingIt)->setRange(tempRange); highlighting->m_highlightedRanges.push_back(*movingIt); ++movingIt; } ++rangeIt; } for(; movingIt != oldHighlightedRanges.end(); ++movingIt) delete *movingIt; // Delete unmatched moving ranges behind } void CodeHighlighting::trackerDestroyed(QObject* object) { // Called when a document is destroyed VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); DocumentChangeTracker* tracker = static_cast(object); Q_ASSERT(m_highlights.contains(tracker)); delete m_highlights[tracker]; // No need to care about the individual ranges, as the document is being destroyed m_highlights.remove(tracker); } void CodeHighlighting::aboutToInvalidateMovingInterfaceContent(Document* doc) { clearHighlightingForDocument(IndexedString(doc->url())); } void CodeHighlighting::aboutToRemoveText( const KTextEditor::Range& range ) { if (range.onSingleLine()) // don't try to optimize this return; VERIFY_FOREGROUND_LOCKED QMutexLocker lock(&m_dataMutex); Q_ASSERT(dynamic_cast(sender())); KTextEditor::Document* doc = static_cast(sender()); DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser() ->trackerForUrl(IndexedString(doc->url())); if(m_highlights.contains(tracker)) { QVector& ranges = m_highlights.value(tracker)->m_highlightedRanges; QVector::iterator it = ranges.begin(); while(it != ranges.end()) { if (range.contains((*it)->toRange())) { delete (*it); it = ranges.erase(it); } else { ++it; } } } } } // kate: space-indent on; indent-width 2; remove-trailing-spaces all; show-tabs on; tab-indents on; tab-width 2; diff --git a/kdevplatform/language/highlighting/codehighlighting.h b/kdevplatform/language/highlighting/codehighlighting.h index 17931306c5..850763625b 100644 --- a/kdevplatform/language/highlighting/codehighlighting.h +++ b/kdevplatform/language/highlighting/codehighlighting.h @@ -1,222 +1,222 @@ /* * This file is part of KDevelop * * Copyright 2007-2010 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 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 General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_CODEHIGHLIGHTING_H #define KDEVPLATFORM_CODEHIGHLIGHTING_H #include #include -#include -#include - #include #include #include #include +#include +#include + namespace KDevelop { class DUContext; class Declaration; typedef QVector ColorMap; class CodeHighlighting; struct HighlightingEnumContainer { enum Types { UnknownType, //Primary highlighting: LocalClassMemberType, InheritedClassMemberType, LocalVariableType, //Other highlighting: ClassType, FunctionType, ForwardDeclarationType, EnumType, EnumeratorType, TypeAliasType, MacroType, /// Declaration of a macro such as "#define FOO" MacroFunctionLikeType, /// Declaration of a function like macro such as "#define FOO()" //If none of the above match: MemberVariableType, NamespaceVariableType, GlobalVariableType, //Most of these are currently not used: ArgumentType, CodeType, FileType, NamespaceType, ScopeType, TemplateType, TemplateParameterType, FunctionVariableType, ErrorVariableType }; enum Contexts { DefinitionContext, DeclarationContext, ReferenceContext }; }; struct HighlightedRange { RangeInRevision range; KTextEditor::Attribute::Ptr attribute; bool operator<(const HighlightedRange& rhs) const { return range.start < rhs.range.start; } }; /** * Code highlighting instance that is used to apply code highlighting to one specific top context * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlightingInstance : public HighlightingEnumContainer { public: explicit CodeHighlightingInstance(const CodeHighlighting* highlighting) : m_useClassCache(false), m_highlighting(highlighting) { } virtual ~CodeHighlightingInstance() { } virtual void highlightDeclaration(KDevelop::Declaration* declaration, const QColor &color); virtual void highlightUse(KDevelop::DUContext* context, int index, const QColor &color); virtual void highlightUses(KDevelop::DUContext* context); void highlightDUChain(KDevelop::TopDUContext* context); void highlightDUChain(KDevelop::DUContext* context, QHash colorsForDeclarations, ColorMap); KDevelop::Declaration* localClassFromCodeContext(KDevelop::DUContext* context) const; /** * @param context Should be the context from where the declaration is used, if a use is highlighted. * */ virtual Types typeForDeclaration(KDevelop::Declaration* dec, KDevelop::DUContext* context) const; /** * Decides whether to apply auto-generated rainbow colors to @p dec. * Default implementation only applies that to local variables in functions. */ virtual bool useRainbowColor(KDevelop::Declaration* dec) const; //A temporary hash for speedup mutable QHash m_contextClasses; //Here the colors of function context are stored until they are merged into the function body mutable QMap > m_functionColorsForDeclarations; mutable QMap m_functionDeclarationsForColors; mutable bool m_useClassCache; const CodeHighlighting* m_highlighting; QVector m_highlight; }; /** * General class representing the code highlighting for one language * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeHighlighting : public QObject, public KDevelop::ICodeHighlighting, public HighlightingEnumContainer { Q_OBJECT Q_INTERFACES(KDevelop::ICodeHighlighting) public: explicit CodeHighlighting(QObject* parent); ~CodeHighlighting() override; /// This function is thread-safe /// @warning The duchain must not be locked when this is called (->possible deadlock) void highlightDUChain(ReferencedTopDUContext context) override; //color should be zero when undecided KTextEditor::Attribute::Ptr attributeForType(Types type, Contexts context, const QColor &color) const; KTextEditor::Attribute::Ptr attributeForDepth(int depth) const; /// This function is thread-safe /// Returns whether a highlighting is already given for the given url bool hasHighlighting(IndexedString url) const override; private: //Returns whether the given attribute was set by the code highlighting, and not by something else //Always returns true when the attribute is zero bool isCodeHighlight(KTextEditor::Attribute::Ptr attr) const; protected: //Can be overridden to create an own instance type virtual CodeHighlightingInstance* createInstance() const; private: /// Highlighting of one specific document struct DocumentHighlighting { IndexedString m_document; qint64 m_waitingRevision; // The ranges are sorted by range start, so they can easily be matched QVector m_waiting; QVector m_highlightedRanges; }; QMap m_highlights; friend class CodeHighlightingInstance; mutable QHash m_definitionAttributes; mutable QHash m_declarationAttributes; mutable QHash m_referenceAttributes; mutable QList m_depthAttributes; // Should be used to enable/disable the colorization of local variables and their uses bool m_localColorization; // Should be used to enable/disable the colorization of global types and their uses bool m_globalColorization; mutable QMutex m_dataMutex; private Q_SLOTS: void clearHighlightingForDocument(const KDevelop::IndexedString& document); void applyHighlighting(void* highlighting); void trackerDestroyed(QObject* object); /// when the colors change we must invalidate our local caches void adaptToColorChanges(); void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*); void aboutToRemoveText(const KTextEditor::Range&); }; } Q_DECLARE_TYPEINFO(KDevelop::HighlightedRange, Q_MOVABLE_TYPE); #endif // kate: space-indent on; indent-width 2; remove-trailing-spaces all; show-tabs on; tab-indents on; tab-width 2; diff --git a/kdevplatform/language/interfaces/codecontext.cpp b/kdevplatform/language/interfaces/codecontext.cpp index 14bfb88fcd..8eda07c200 100644 --- a/kdevplatform/language/interfaces/codecontext.cpp +++ b/kdevplatform/language/interfaces/codecontext.cpp @@ -1,135 +1,135 @@ /* This file is part of KDevelop Copyright 2001-2002 Matthias Hoelzer-Kluepfel Copyright 2001-2002 Bernd Gehrmann Copyright 2001 Sandy Meier Copyright 2002 Daniel Engelschalt Copyright 2002 Simon Hausmann Copyright 2002-2003 Roberto Raggi Copyright 2003 Mario Scalas Copyright 2003 Harald Fernengel Copyright 2003,2006,2008 Hamish Rodda Copyright 2004 Alexander Dymo Copyright 2006 Adam Treat 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 "codecontext.h" #include #include #include #include #include #include -#include -#include +#include +#include namespace KDevelop { class DUContextContextPrivate { public: explicit DUContextContextPrivate(const IndexedDUContext& item) : m_item(item) {} IndexedDUContext m_item; }; DUContextContext::DUContextContext( const IndexedDUContext& item ) : Context() , d(new DUContextContextPrivate(item)) {} DUContextContext::~DUContextContext() = default; int DUContextContext::type() const { return Context::CodeContext; } QList DUContextContext::urls() const { DUChainReadLocker lock; if (auto context = d->m_item.context()) { return {context->url().toUrl()}; } return {}; } IndexedDUContext DUContextContext::context() const { return d->m_item; } void DUContextContext::setContext(IndexedDUContext context) { d->m_item = context; } class DeclarationContextPrivate { public: DeclarationContextPrivate(const IndexedDeclaration& declaration, const DocumentRange& use) : m_declaration(declaration) , m_use(use) {} IndexedDeclaration m_declaration; DocumentRange m_use; }; DeclarationContext::DeclarationContext( const IndexedDeclaration& declaration, const DocumentRange& use, const IndexedDUContext& context ) : DUContextContext(context) , d(new DeclarationContextPrivate(declaration, use)) {} DeclarationContext::DeclarationContext(KTextEditor::View* view, const KTextEditor::Cursor& position) : DUContextContext(IndexedDUContext()) { const QUrl& url = view->document()->url(); DUChainReadLocker lock; DUChainUtils::ItemUnderCursor item = DUChainUtils::itemUnderCursor(url, position); DocumentRange useRange = DocumentRange(IndexedString(url), item.range); Declaration* declaration = item.declaration; IndexedDeclaration indexed; if ( declaration ) { indexed = IndexedDeclaration(declaration); } d.reset(new DeclarationContextPrivate(declaration, useRange)); setContext(IndexedDUContext(item.context)); } DeclarationContext::~DeclarationContext() = default; int DeclarationContext::type() const { return Context::CodeContext; } IndexedDeclaration DeclarationContext::declaration() const { return d->m_declaration; } DocumentRange DeclarationContext::use() const { return d->m_use; } } diff --git a/kdevplatform/language/interfaces/editorcontext.cpp b/kdevplatform/language/interfaces/editorcontext.cpp index d1567b2cf8..c187c73663 100644 --- a/kdevplatform/language/interfaces/editorcontext.cpp +++ b/kdevplatform/language/interfaces/editorcontext.cpp @@ -1,95 +1,96 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat Copyright 2008 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 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 "editorcontext.h" -#include -#include -#include + +#include +#include +#include namespace KDevelop { class EditorContextPrivate { public: EditorContextPrivate( KTextEditor::View* view, const KTextEditor::Cursor& position ) : m_url(view->document()->url()) , m_position(position) , m_currentLine(view->document()->line(m_position.line())) , m_view( view ) { int wordStart = m_position.column(); int wordEnd = m_position.column(); while (wordStart > 0 && wordStart < m_currentLine.length() && (m_currentLine[wordStart-1].isLetterOrNumber() || m_currentLine[wordStart-1] == QLatin1Char('_'))) --wordStart; while (wordEnd >= 0 && wordEnd < m_currentLine.length() && (m_currentLine[wordEnd].isLetterOrNumber() || m_currentLine[wordEnd] == QLatin1Char('_'))) ++wordEnd; } QUrl m_url; KTextEditor::Cursor m_position; QString m_currentLine, m_currentWord; KTextEditor::View* m_view; }; EditorContext::EditorContext( KTextEditor::View* view, const KTextEditor::Cursor& position ) : DeclarationContext( view, position ), d( new EditorContextPrivate( view, position ) ) {} EditorContext::~EditorContext() = default; int EditorContext::type() const { return Context::EditorContext; } QUrl EditorContext::url() const { return d->m_url; } QList EditorContext::urls() const { return {d->m_url}; } KTextEditor::Cursor EditorContext::position() const { return d->m_position; } QString EditorContext::currentLine() const { return d->m_currentLine; } QString EditorContext::currentWord() const { return d->m_currentWord; } KTextEditor::View* EditorContext::view() const { return d->m_view; } } diff --git a/kdevplatform/outputview/outputdelegate.cpp b/kdevplatform/outputview/outputdelegate.cpp index 65770a1ec9..f153076aee 100644 --- a/kdevplatform/outputview/outputdelegate.cpp +++ b/kdevplatform/outputview/outputdelegate.cpp @@ -1,93 +1,93 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright (C) 2007 Andreas Pakulat * * Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com * * * * 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 "outputdelegate.h" #include "outputmodel.h" #include "filtereditem.h" -#include +#include #include namespace KDevelop { class OutputDelegatePrivate { public: OutputDelegatePrivate(); KStatefulBrush errorBrush; KStatefulBrush warningBrush; KStatefulBrush informationBrush; KStatefulBrush builtBrush; }; OutputDelegatePrivate::OutputDelegatePrivate() : errorBrush( KColorScheme::View, KColorScheme::NegativeText ) , warningBrush( KColorScheme::View, KColorScheme::NeutralText ) //TODO: Maybe ActiveText would be better? Not quite sure... , informationBrush( KColorScheme::View, KColorScheme::LinkText ) , builtBrush( KColorScheme::View, KColorScheme::PositiveText ) { } OutputDelegate::OutputDelegate( QObject* parent ) : QItemDelegate( parent ) , d(new OutputDelegatePrivate) { } OutputDelegate::~OutputDelegate() = default; void OutputDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; QVariant status = index.data(OutputModel::OutputItemTypeRole); if( status.isValid() ) { FilteredItem::FilteredOutputItemType type = static_cast(status.toInt()); switch(type) { case FilteredItem::ErrorItem: opt.palette.setBrush( QPalette::Text, d->errorBrush.brush( option.palette ) ); opt.font.setBold( true ); break; case FilteredItem::WarningItem: opt.palette.setBrush( QPalette::Text, d->warningBrush.brush( option.palette ) ); break; case FilteredItem::InformationItem: opt.palette.setBrush( QPalette::Text, d->informationBrush.brush( option.palette ) ); break; case FilteredItem::ActionItem: opt.palette.setBrush( QPalette::Text, d->builtBrush.brush( option.palette ) ); opt.font.setBold( true ); break; default: break; } } QItemDelegate::paint(painter, opt, index); } } diff --git a/kdevplatform/outputview/outputjob.h b/kdevplatform/outputview/outputjob.h index be5194f8f9..53366c3af5 100644 --- a/kdevplatform/outputview/outputjob.h +++ b/kdevplatform/outputview/outputjob.h @@ -1,97 +1,97 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda 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 KDEVPLATFORM_OUTPUTJOB_H #define KDEVPLATFORM_OUTPUTJOB_H -#include - #include #include +#include + class QIcon; namespace KDevelop { class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputJob : public KJob { Q_OBJECT public: enum { FailedShownError = UserDefinedError + 100 //job failed and failure is shown in OutputView }; enum OutputJobVerbosity { Silent, Verbose }; explicit OutputJob(QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose); ~OutputJob() override; void startOutput(); OutputJobVerbosity verbosity() const; void setVerbosity(OutputJobVerbosity verbosity); QAbstractItemModel* model() const; /// Set the \a title for this job's output tab. If not set, will default to the job's objectName(). void setTitle(const QString& title); protected: void setStandardToolView(IOutputView::StandardToolView standard); void setToolTitle(const QString& title); void setToolIcon(const QIcon& icon); void setViewType(IOutputView::ViewType type); void setBehaviours(IOutputView::Behaviours behaviours); void setKillJobOnOutputClose(bool killJobOnOutputClose); /** * Sets the model for the view that shows this jobs output. * * The view takes ownership of the model, but it is safe to * use the model while the job is running. * * NOTE: Do not reuse the same model for different jobs. */ void setModel(QAbstractItemModel* model); /** * Sets the delegate for the view that shows this jobs output. * * The view takes ownership of the delegate, but it is safe to * use the delegate while the job is running. * * NOTE: Do not reuse the same delegate for different jobs. */ void setDelegate(QAbstractItemDelegate* delegate); int outputId() const; private Q_SLOTS: void outputViewRemoved(int , int id); private: const QScopedPointer d; }; } #endif diff --git a/kdevplatform/project/projectbuildsetmodel.cpp b/kdevplatform/project/projectbuildsetmodel.cpp index 03cafd8479..2983e11741 100644 --- a/kdevplatform/project/projectbuildsetmodel.cpp +++ b/kdevplatform/project/projectbuildsetmodel.cpp @@ -1,410 +1,410 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2009 Aleix Pol * * * * 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 "projectbuildsetmodel.h" #include #include -#include +#include #include #include #include #include #include "projectmodel.h" #include #include namespace KDevelop { BuildItem::BuildItem() { } BuildItem::BuildItem( const QStringList & itemPath ) : m_itemPath( itemPath ) { } BuildItem::BuildItem( KDevelop::ProjectBaseItem* item ) { initializeFromItem( item ); } BuildItem::BuildItem( const BuildItem& rhs ) : m_itemPath(rhs.itemPath()) { } void BuildItem::initializeFromItem( KDevelop::ProjectBaseItem* item ) { Q_ASSERT(item); KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel(); m_itemPath = model->pathFromIndex(item->index()); } QString BuildItem::itemName() const { return m_itemPath.last(); } QString BuildItem::itemProject() const { return m_itemPath.first(); } KDevelop::ProjectBaseItem* BuildItem::findItem() const { KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel(); QModelIndex idx = model->pathToIndex(m_itemPath); return model->itemFromIndex(idx); } bool operator==( const BuildItem& rhs, const BuildItem& lhs ) { return( rhs.itemPath() == lhs.itemPath() ); } BuildItem& BuildItem::operator=( const BuildItem& rhs ) { if( this == &rhs ) return *this; m_itemPath = rhs.itemPath(); return *this; } class ProjectBuildSetModelPrivate { public: QList items; QList orderingCache; }; ProjectBuildSetModel::ProjectBuildSetModel( QObject* parent ) : QAbstractTableModel( parent ) , d(new ProjectBuildSetModelPrivate) { } ProjectBuildSetModel::~ProjectBuildSetModel() = default; void ProjectBuildSetModel::loadFromSession( ISession* session ) { if (!session) { return; } // Load the item ordering cache KConfigGroup sessionBuildSetConfig = session->config()->group( "Buildset" ); const QVariantList sessionBuildItems = KDevelop::stringToQVariant(sessionBuildSetConfig.readEntry("BuildItems", QString())).toList(); d->orderingCache.reserve(d->orderingCache.size() + sessionBuildItems.size()); for (const QVariant& item : sessionBuildItems) { d->orderingCache.append(item.toStringList()); } } void ProjectBuildSetModel::storeToSession( ISession* session ) { if (!session) { return; } // Store the item ordering cache QVariantList sessionBuildItems; sessionBuildItems.reserve(d->orderingCache.size()); foreach (const QStringList& item, d->orderingCache) { sessionBuildItems.append( item ); } KConfigGroup sessionBuildSetConfig = session->config()->group( "Buildset" ); sessionBuildSetConfig.writeEntry("BuildItems", KDevelop::qvariantToString( QVariant( sessionBuildItems ) )); sessionBuildSetConfig.sync(); } int ProjectBuildSetModel::findInsertionPlace( const QStringList& itemPath ) { /* * The ordering cache list is a superset of the build set, and must be ordered in the same way. * Example: * (items) A - B ----- D --------- G * (orderingCache) A - B - C - D - E - F - G * * We scan orderingCache until we find the required item (absent in items: say, F). * In process of scanning we synchronize position in orderingCache with position in items; * so, when we reach F, we have D as last synchronization point and hence return it * as the insertion place (actually, we return the next item's index - here, index of G). * * If an item cannot be found in the ordering list, we append it to the list. */ int insertionIndex = 0; bool found = false; QList::iterator orderingCacheIterator = d->orderingCache.begin(); // Points to the item which is next to last synchronization point. QList::iterator nextItemIterator = d->items.begin(); while (orderingCacheIterator != d->orderingCache.end()) { if( itemPath == *orderingCacheIterator ) { found = true; break; } if (nextItemIterator != d->items.end() && nextItemIterator->itemPath() == *orderingCacheIterator ) { ++insertionIndex; ++nextItemIterator; } ++orderingCacheIterator; } // while if( !found ) { d->orderingCache.append(itemPath); } Q_ASSERT( insertionIndex >= 0 && insertionIndex <= d->items.size() ); return insertionIndex; } void ProjectBuildSetModel::removeItemsWithCache( const QList& itemIndices ) { /* * Removes the items with given indices from both the build set and the ordering cache. * List is given since removing many items together is more efficient than by one. * * Indices in the list shall be sorted. */ QList itemIndicesCopy = itemIndices; beginRemoveRows( QModelIndex(), itemIndices.first(), itemIndices.last() ); for (QList::iterator cacheIterator = d->orderingCache.end() - 1; cacheIterator >= d->orderingCache.begin() && !itemIndicesCopy.isEmpty();) { int index = itemIndicesCopy.back(); Q_ASSERT( index >= 0 && index < d->items.size() ); if (*cacheIterator == d->items.at(index).itemPath()) { cacheIterator = d->orderingCache.erase(cacheIterator); d->items.removeAt(index); itemIndicesCopy.removeLast(); } --cacheIterator; } // for endRemoveRows(); Q_ASSERT( itemIndicesCopy.isEmpty() ); } void ProjectBuildSetModel::insertItemWithCache( const BuildItem& item ) { int insertionPlace = findInsertionPlace( item.itemPath() ); beginInsertRows( QModelIndex(), insertionPlace, insertionPlace ); d->items.insert(insertionPlace, item); endInsertRows(); } void ProjectBuildSetModel::insertItemsOverrideCache( int index, const QList< BuildItem >& items ) { Q_ASSERT( index >= 0 && index <= d->items.size() ); if (index == d->items.size()) { beginInsertRows( QModelIndex(), index, index + items.size() - 1 ); d->items.append(items); d->orderingCache.reserve(d->orderingCache.size() + items.size()); for (const BuildItem& item : items) { d->orderingCache.append(item.itemPath()); } endInsertRows(); } else { int indexInCache = d->orderingCache.indexOf(d->items.at(index).itemPath()); Q_ASSERT( indexInCache >= 0 ); beginInsertRows( QModelIndex(), index, index + items.size() - 1 ); for( int i = 0; i < items.size(); ++i ) { const BuildItem& item = items.at( i ); d->items.insert(index + i, item); d->orderingCache.insert(indexInCache + i, item.itemPath()); } endInsertRows(); } } QVariant ProjectBuildSetModel::data( const QModelIndex& idx, int role ) const { if( !idx.isValid() || idx.row() < 0 || idx.column() < 0 || idx.row() >= rowCount() || idx.column() >= columnCount()) { return QVariant(); } if(role == Qt::DisplayRole) { switch( idx.column() ) { case 0: return d->items.at(idx.row()).itemName(); break; case 1: return KDevelop::joinWithEscaping(d->items.at(idx.row()).itemPath(), QLatin1Char('/'), QLatin1Char('\\')); break; } } else if(role == Qt::DecorationRole && idx.column()==0) { KDevelop::ProjectBaseItem* item = d->items.at(idx.row()).findItem(); if( item ) { return QIcon::fromTheme( item->iconName() ); } } return QVariant(); } QVariant ProjectBuildSetModel::headerData( int section, Qt::Orientation orientation, int role ) const { if( section < 0 || section >= columnCount() || orientation != Qt::Horizontal || role != Qt::DisplayRole ) return QVariant(); switch( section ) { case 0: return i18nc("@title:column buildset item name", "Name"); break; case 1: return i18nc("@title:column buildset item path", "Path"); break; } return QVariant(); } int ProjectBuildSetModel::rowCount( const QModelIndex& parent ) const { if( parent.isValid() ) return 0; return d->items.count(); } int ProjectBuildSetModel::columnCount( const QModelIndex& parent ) const { if( parent.isValid() ) return 0; return 2; } void ProjectBuildSetModel::addProjectItem( KDevelop::ProjectBaseItem* item ) { BuildItem buildItem( item ); if (d->items.contains(buildItem)) return; insertItemWithCache( buildItem ); } bool ProjectBuildSetModel::removeRows( int row, int count, const QModelIndex& parent ) { if( parent.isValid() || row > rowCount() || row < 0 || (row+count) > rowCount() || count <= 0 ) return false; QList itemsToRemove; itemsToRemove.reserve(count); for( int i = row; i < row+count; i++ ) { itemsToRemove.append( i ); } removeItemsWithCache( itemsToRemove ); return true; } QList ProjectBuildSetModel::items() { return d->items; } void ProjectBuildSetModel::projectClosed( KDevelop::IProject* project ) { for (int i = d->items.count() - 1; i >= 0; --i) { if (d->items.at(i).itemProject() == project->name()) { beginRemoveRows( QModelIndex(), i, i ); d->items.removeAt(i); endRemoveRows(); } } } void ProjectBuildSetModel::saveToProject( KDevelop::IProject* project ) const { QVariantList paths; foreach (const BuildItem& item, d->items) { if( item.itemProject() == project->name() ) paths.append(item.itemPath()); } KConfigGroup base = project->projectConfiguration()->group("Buildset"); base.writeEntry("BuildItems", KDevelop::qvariantToString( QVariant( paths ) )); base.sync(); } void ProjectBuildSetModel::loadFromProject( KDevelop::IProject* project ) { KConfigGroup base = project->projectConfiguration()->group("Buildset"); if (base.hasKey("BuildItems")) { const QVariantList items = KDevelop::stringToQVariant(base.readEntry("BuildItems", QString())).toList(); for (const QVariant& path : items) { insertItemWithCache( BuildItem( path.toStringList() ) ); } } else { // Add project to buildset, but only if there is no configuration for this project yet. addProjectItem( project->projectItem() ); } } void ProjectBuildSetModel::moveRowsDown(int row, int count) { QList items = d->items.mid(row, count); removeRows( row, count ); insertItemsOverrideCache( row + 1, items ); } void ProjectBuildSetModel::moveRowsToBottom(int row, int count) { QList items = d->items.mid(row, count); removeRows( row, count ); insertItemsOverrideCache( rowCount(), items ); } void ProjectBuildSetModel::moveRowsUp(int row, int count) { QList items = d->items.mid(row, count); removeRows( row, count ); insertItemsOverrideCache( row - 1, items ); } void ProjectBuildSetModel::moveRowsToTop(int row, int count) { QList items = d->items.mid(row, count); removeRows( row, count ); insertItemsOverrideCache( 0, items ); } } diff --git a/kdevplatform/project/projectconfigskeleton.h b/kdevplatform/project/projectconfigskeleton.h index 5f6c7dfcd1..8ff0886267 100644 --- a/kdevplatform/project/projectconfigskeleton.h +++ b/kdevplatform/project/projectconfigskeleton.h @@ -1,67 +1,68 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat 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 KDEVPLATFORM_PROJECTCONFIGSKELETON_H #define KDEVPLATFORM_PROJECTCONFIGSKELETON_H #include "projectexport.h" -#include + +#include namespace KDevelop { class Path; class KDEVPLATFORMPROJECT_EXPORT ProjectConfigSkeleton: public KConfigSkeleton { Q_OBJECT public: ~ProjectConfigSkeleton() override; void setDeveloperTempFile( const QString& ); void setProjectTempFile( const QString& ); void setProjectFile( const Path& ); void setDeveloperFile( const Path& ); void setDefaults() override; bool useDefaults( bool b ) override; bool writeConfig(); Path projectFile() const; Path developerFile() const; protected: explicit ProjectConfigSkeleton( KSharedConfigPtr config ); /** * Constructs a new skeleton, the skeleton will write to the developer * configuration file, which is by default located in projectdir/.kdev4 * The defaults will be set from the project file, which is in the projectdir * * @param configname The absolute filename of the developer configuration file */ explicit ProjectConfigSkeleton( const QString & configname ); private: const QScopedPointer d; }; } #endif diff --git a/kdevplatform/shell/launchconfiguration.h b/kdevplatform/shell/launchconfiguration.h index 34d6bf00a8..748f93eb7a 100644 --- a/kdevplatform/shell/launchconfiguration.h +++ b/kdevplatform/shell/launchconfiguration.h @@ -1,101 +1,102 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat 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 KDEVPLATFORM_LAUNCHCONFIGURATION_H #define KDEVPLATFORM_LAUNCHCONFIGURATION_H #include -#include #include "shellexport.h" +#include + class QString; namespace KDevelop { class LaunchConfigurationType; class IProject; /** * @copydoc KDevelop::ILaunchConfiguration */ class KDEVPLATFORMSHELL_EXPORT LaunchConfiguration : public QObject, public ILaunchConfiguration { Q_OBJECT public: explicit LaunchConfiguration( const KConfigGroup&, IProject* = nullptr, QObject* = nullptr ); ~LaunchConfiguration() override; static QString LaunchConfigurationNameEntry(); static QString LaunchConfigurationTypeEntry(); /** * Change the name of this launch configuration * @param name the new name for the launch configuration */ void setName( const QString& name ); /** * Changes the type of this launch configuration. Note that * this removes all existing config values from this configuration * @param typeId the id of the new type */ void setType( const QString& typeId ); /** * @copydoc KDevelop::ILaunchConfiguration::config() */ const KConfigGroup config() const override; /** * @copydoc KDevelop::ILaunchConfiguration::type() */ LaunchConfigurationType* type() const override; /** * @copydoc KDevelop::ILaunchConfiguration::name() */ QString name() const override; /** * @copydoc KDevelop::ILaunchConfiguration::project() */ IProject* project() const override; void save(); QString configGroupName() const; QString launcherForMode( const QString& mode ) const; void setLauncherForMode( const QString& mode, const QString& id ); KConfigGroup config() override; Q_SIGNALS: void nameChanged( LaunchConfiguration* ); void typeChanged( LaunchConfigurationType* ); private: const QScopedPointer d; }; } #endif diff --git a/kdevplatform/shell/settings/editstyledialog.cpp b/kdevplatform/shell/settings/editstyledialog.cpp index 308888ad61..6964f7c349 100644 --- a/kdevplatform/shell/settings/editstyledialog.cpp +++ b/kdevplatform/shell/settings/editstyledialog.cpp @@ -1,126 +1,128 @@ /* This file is part of KDevelop * Copyright (C) 2008 Cédric Pasteur 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 "editstyledialog.h" +#include + +#include +#include +#include +#include +#include + #include #include #include #include -#include -#include -#include -#include -#include -#include using namespace KDevelop; EditStyleDialog::EditStyleDialog(ISourceFormatter* formatter, const QMimeType& mime, const SourceFormatterStyle& style, QWidget* parent) : QDialog(parent) , m_sourceFormatter(formatter) , m_mimeType(mime) , m_style(style) { m_content = new QWidget(); m_ui.setupUi(m_content); QVBoxLayout* mainLayout = new QVBoxLayout(this); mainLayout->addWidget(m_content); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); connect(buttonBox, &QDialogButtonBox::accepted, this, &EditStyleDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &EditStyleDialog::reject); mainLayout->addWidget(buttonBox); m_settingsWidget = m_sourceFormatter->editStyleWidget(mime); init(); if (m_settingsWidget) { m_settingsWidget->load(style); } } EditStyleDialog::~EditStyleDialog() { } void EditStyleDialog::init() { // add plugin settings widget if (m_settingsWidget) { QVBoxLayout* layout = new QVBoxLayout(m_ui.settingsWidgetParent); layout->setMargin(0); layout->addWidget(m_settingsWidget); m_ui.settingsWidgetParent->setLayout(layout); connect(m_settingsWidget, &SettingsWidget::previewTextChanged, this, &EditStyleDialog::updatePreviewText); } m_document = KTextEditor::Editor::instance()->createDocument(this); m_document->setReadWrite(false); m_document->setHighlightingMode(m_style.modeForMimetype(m_mimeType)); m_view = m_document->createView(m_ui.textEditor); QVBoxLayout* layout2 = new QVBoxLayout(m_ui.textEditor); layout2->setMargin(0); layout2->addWidget(m_view); m_ui.textEditor->setLayout(layout2); m_view->setStatusBarEnabled(false); m_view->show(); KTextEditor::ConfigInterface* iface = qobject_cast(m_view); if (iface) { iface->setConfigValue(QStringLiteral("dynamic-word-wrap"), false); iface->setConfigValue(QStringLiteral("icon-bar"), false); } if (m_sourceFormatter) { QString text = m_sourceFormatter->previewText(m_style, m_mimeType); updatePreviewText(text); } } void EditStyleDialog::updatePreviewText(const QString &text) { m_document->setReadWrite(true); m_style.setContent(content()); if (m_sourceFormatter) { m_document->setText(m_sourceFormatter->formatSourceWithStyle(m_style, text, QUrl(), m_mimeType)); } else { m_document->setText(i18n("No Source Formatter available")); } m_view->setCursorPosition(KTextEditor::Cursor(0, 0)); m_document->setReadWrite(false); } QString EditStyleDialog::content() { if (m_settingsWidget) { return m_settingsWidget->save(); } return QString(); } diff --git a/kdevplatform/shell/settings/sessionconfigskeleton.h b/kdevplatform/shell/settings/sessionconfigskeleton.h index 01df00e84f..536aa9ede5 100644 --- a/kdevplatform/shell/settings/sessionconfigskeleton.h +++ b/kdevplatform/shell/settings/sessionconfigskeleton.h @@ -1,43 +1,43 @@ /* KDevelop Project Settings * * 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 KDEVPLATFORM_SESSIONCONFIGSKELETON_H #define KDEVPLATFORM_SESSIONCONFIGSKELETON_H -#include - #include "../core.h" #include "../session.h" +#include + namespace KDevelop { class SessionConfigSkeleton : public KConfigSkeleton { public: explicit SessionConfigSkeleton( const QString& ) : KConfigSkeleton( Core::self()->activeSession()->config() ) { } }; } #endif diff --git a/kdevplatform/shell/sourceformattercontroller.h b/kdevplatform/shell/sourceformattercontroller.h index 5d74d0280f..a0430ef4af 100644 --- a/kdevplatform/shell/sourceformattercontroller.h +++ b/kdevplatform/shell/sourceformattercontroller.h @@ -1,174 +1,174 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur 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 KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #define KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #include #include #include #include #include -#include +#include #include #include "shellexport.h" class QUrl; namespace KTextEditor { class Document; } namespace KDevelop { class Context; class ContextMenuExtension; class IDocument; class ISourceFormatter; class IPlugin; struct SourceFormatter { KDevelop::ISourceFormatter* formatter; // style name -> style. style objects owned by this typedef QMap StyleMap; StyleMap styles; // Get a list of supported mime types from the style map. QSet supportedMimeTypes() const { QSet supported; for ( auto style: styles ) { foreach ( auto& item, style->mimeTypes() ) { supported.insert(item.mimeType); } } return supported; } ~SourceFormatter() { qDeleteAll(styles); }; }; /** \short A singleton class managing all source formatter plugins */ class KDEVPLATFORMSHELL_EXPORT SourceFormatterController : public ISourceFormatterController, public KXMLGUIClient { Q_OBJECT friend class SourceFormatterJob; public: static QString kateModeLineConfigKey(); static QString kateOverrideIndentationConfigKey(); static QString styleCaptionKey(); static QString styleContentKey(); static QString styleMimeTypesKey(); static QString styleSampleKey(); explicit SourceFormatterController(QObject *parent = nullptr); ~SourceFormatterController() override; void initialize(); void cleanup(); //----------------- Public API defined in interfaces ------------------- /** \return The formatter corresponding to the language * of the document corresponding to the \arg url. */ ISourceFormatter* formatterForUrl(const QUrl &url) override; /** Loads and returns a source formatter for this mime type. * The language is then activated and the style is loaded. * The source formatter is then ready to use on a file. */ ISourceFormatter* formatterForUrl(const QUrl& url, const QMimeType& mime) override; bool hasFormatters() const override; /** \return Whether this mime type is supported by any plugin. */ bool isMimeTypeSupported(const QMimeType& mime) override; /** * @brief Instantiate a Formatter for the given plugin and load its configuration. * * @param ifmt The ISourceFormatter interface of the plugin * @return KDevelop::SourceFormatter* the SourceFormatter instance for the plugin, including config items */ SourceFormatter* createFormatterForPlugin(KDevelop::ISourceFormatter* ifmt) const; /** * @brief Find the first formatter which supports a given mime type. */ ISourceFormatter* findFirstFormatterForMimeType(const QMimeType& mime) const; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent); KDevelop::SourceFormatterStyle styleForUrl(const QUrl& url, const QMimeType& mime) override; KConfigGroup configForUrl(const QUrl& url) const; KConfigGroup sessionConfig() const; KConfigGroup globalConfig() const; void settingsChanged(); void disableSourceFormatting(bool disable) override; bool sourceFormattingEnabled() override; QVector formatters() const; Q_SIGNALS: void formatterLoaded(KDevelop::ISourceFormatter* ifmt); void formatterUnloading(KDevelop::ISourceFormatter* ifmt); private Q_SLOTS: void updateFormatTextAction(); void beautifySource(); void beautifyLine(); void formatFiles(); void documentLoaded( KDevelop::IDocument* ); void pluginLoaded(KDevelop::IPlugin* plugin); void unloadingPlugin(KDevelop::IPlugin* plugin); private: /** \return A modeline string (to add at the end or the beginning of a file) * corresponding to the settings of the active language. */ QString addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType&); /** \return The name of kate indentation mode for the mime type. * examples are cstyle, python, etc. */ QString indentationMode(const QMimeType& mime); void formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime); // Adapts the mode of the editor regarding indentation-style void adaptEditorIndentationMode(KTextEditor::Document* doc, KDevelop::ISourceFormatter* formatter, const QUrl& url, bool ignoreModeline = false); void resetUi(); private: const QScopedPointer d; }; } #endif // KDEVPLATFORM_SOURCEFORMATTERMANAGER_H diff --git a/kdevplatform/shell/tests/test_shellbuddy.cpp b/kdevplatform/shell/tests/test_shellbuddy.cpp index 53d1c68b68..fe5ef6e824 100644 --- a/kdevplatform/shell/tests/test_shellbuddy.cpp +++ b/kdevplatform/shell/tests/test_shellbuddy.cpp @@ -1,422 +1,422 @@ /*************************************************************************** * 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_shellbuddy.h" #include #include #include -#include -#include -#include - #include #include #include #include #include #include #include #include #include #include "../documentcontroller.h" #include "../uicontroller.h" +#include +#include +#include + // groups files like foo.l.txt and foo.r.txt such that l is left of r class TestBuddyFinder : public KDevelop::IBuddyDocumentFinder { bool areBuddies(const QUrl& url1, const QUrl& url2) override { const QStringList name1 = url1.fileName().split('.'); const QStringList name2 = url2.fileName().split('.'); if (name1.size() != 3 || name2.size() != 3) { return false; } if (name1.last() != name2.last() || name1.first() != name2.first()) { return false; } if (name1.at(1) == name2.at(1)) { return false; } if (name1.at(1) != QLatin1String("l") && name1.at(1) != QLatin1String("r")) { return false; } if (name2.at(1) != QLatin1String("l") && name2.at(1) != QLatin1String("r")) { return false; } qDebug() << "found buddies: " << url1 << url2; return true; } bool buddyOrder(const QUrl& url1, const QUrl& /*url2*/) override { const QStringList name1 = url1.fileName().split('.'); return name1.at(1) == QLatin1String("l"); } QVector potentialBuddies(const QUrl& url) const override { Q_UNUSED(url); return QVector(); } }; void TestShellBuddy::initTestCase() { AutoTestShell::init({{}}); // do not load plugins at all TestCore::initialize(); m_documentController = Core::self()->documentController(); m_uiController = Core::self()->uiControllerInternal(); m_finder = new TestBuddyFinder; KDevelop::IBuddyDocumentFinder::addFinder(QStringLiteral("text/plain"), m_finder); } void TestShellBuddy::cleanupTestCase() { KDevelop::IBuddyDocumentFinder::removeFinder(QStringLiteral("text/plain")); delete m_finder; m_finder = nullptr; TestCore::shutdown(); } //NOTE: macro for proper line-numbers in test's output in case the check fails #define verifyFilename(view, endOfFilename) \ QVERIFY(view); \ { \ Sublime::UrlDocument *urlDoc = dynamic_cast(view->document()); \ QVERIFY(urlDoc); \ QVERIFY(urlDoc->url().toLocalFile().endsWith(QStringLiteral(endOfFilename))); \ } void TestShellBuddy::createFile(const QTemporaryDir& dir, const QString& filename) { QFile file(dir.path() + '/' + filename); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); file.close(); } void TestShellBuddy::enableBuddies(bool enable) { { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); uiGroup.writeEntry("TabBarArrangeBuddies", (enable ? 1 : 0)); uiGroup.sync(); } Core::self()->uiControllerInternal()->loadSettings(); QCOMPARE(Core::self()->uiControllerInternal()->arrangeBuddies(), enable); } void TestShellBuddy::enableOpenAfterCurrent(bool enable) { { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); uiGroup.writeEntry("TabBarOpenAfterCurrent", (enable ? 1 : 0)); uiGroup.sync(); } Core::self()->uiControllerInternal()->loadSettings(); QCOMPARE(Core::self()->uiControllerInternal()->openAfterCurrent(), enable); } // ------------------ Tests ------------------------------------------------- void TestShellBuddy::testDeclarationDefinitionOrder() { QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; createFile(dirA, QStringLiteral("a.r.txt")); createFile(dirA, QStringLiteral("b.r.txt")); createFile(dirA, QStringLiteral("c.r.txt")); createFile(dirA, QStringLiteral("a.l.txt")); createFile(dirA, QStringLiteral("b.l.txt")); createFile(dirA, QStringLiteral("c.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "b.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "c.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "b.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "c.l.txt")); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(m_uiController->activeSublimeWindow()->activeView()); QCOMPARE(m_documentController->openDocuments().count(), 6); //QCOMPARE(m_uiController->documents().count(), 6); QCOMPARE(areaIndex->viewCount(), 6); qDebug() << dynamic_cast(areaIndex->viewAt(0)->document())->url(); verifyFilename(areaIndex->views().value(0), "a.l.txt"); verifyFilename(areaIndex->views().value(1), "a.r.txt"); verifyFilename(areaIndex->views().value(2), "b.l.txt"); verifyFilename(areaIndex->views().value(3), "b.r.txt"); verifyFilename(areaIndex->views().value(4), "c.l.txt"); verifyFilename(areaIndex->views().value(5), "c.r.txt"); for(int i = 0; i < 6; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestShellBuddy::testActivation() { QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; createFile(dirA, QStringLiteral("a.l.txt")); createFile(dirA, QStringLiteral("a.r.txt")); createFile(dirA, QStringLiteral("b.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.l.txt")); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "a.l.txt"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "b.r.txt")); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "b.r.txt"); QCOMPARE(m_documentController->openDocuments().count(), 3); for(int i = 0; i < 3; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestShellBuddy::testDisableBuddies() { /* 3. Deactivate buddy option, Activate open next to active tab Open a.cpp a.l.txt Verify order (a.cpp a.l.txt) Verify that a.l.txt is activated Activate a.cpp Open b.cpp Verify order (a.cpp b.cpp a.l.txt) */ QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(false); enableOpenAfterCurrent(); QTemporaryDir dirA; createFile(dirA, QStringLiteral("a.l.txt")); createFile(dirA, QStringLiteral("a.r.txt")); createFile(dirA, QStringLiteral("b.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "a.l.txt")); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(m_uiController->activeSublimeWindow()->activeView()); // Buddies disabled => order of tabs should be the order of file opening verifyFilename(areaIndex->views().value(0), "a.r.txt"); verifyFilename(areaIndex->views().value(1), "a.l.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "a.l.txt"); //activate a.cpp => new doc should be opened right next to it m_uiController->activeSublimeWindow()->activateView(areaIndex->views().value(0)); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "b.r.txt")); verifyFilename(areaIndex->views().value(0), "a.r.txt"); verifyFilename(areaIndex->views().value(1), "b.r.txt"); verifyFilename(areaIndex->views().value(2), "a.l.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "b.r.txt"); QCOMPARE(m_documentController->openDocuments().count(), 3); for(int i = 0; i < 3; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestShellBuddy::testDisableOpenAfterCurrent() { /* 5. Enable buddy option, Disable open next to active tab Open foo.l.txt bar.cpp foo.cpp Verify order (foo.l.txt foo.cpp bar.cpp) Verify that foo.cpp is activated Open x.cpp => tab must be placed at the end Verify order (foo.l.txt foo.cpp bar.cpp x.cpp) Verify that x.cpp is activated*/ QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(); enableOpenAfterCurrent(false); QTemporaryDir dirA; createFile(dirA, QStringLiteral("foo.l.txt")); createFile(dirA, QStringLiteral("bar.r.txt")); createFile(dirA, QStringLiteral("foo.r.txt")); createFile(dirA, QStringLiteral("x.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "bar.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.r.txt")); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(m_uiController->activeSublimeWindow()->activeView()); verifyFilename(areaIndex->views().value(0), "foo.l.txt"); verifyFilename(areaIndex->views().value(1), "foo.r.txt"); verifyFilename(areaIndex->views().value(2), "bar.r.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "foo.r.txt"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "x.r.txt")); verifyFilename(areaIndex->views().value(0), "foo.l.txt"); verifyFilename(areaIndex->views().value(1), "foo.r.txt"); verifyFilename(areaIndex->views().value(2), "bar.r.txt"); verifyFilename(areaIndex->views().value(3), "x.r.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "x.r.txt"); QCOMPARE(m_documentController->openDocuments().count(), 4); for(int i = 0; i < 4; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestShellBuddy::testDisableAll() { /* 6. Disable buddy option, Disable open next to active tab Open foo.cpp bar.l.txt foo.l.txt Activate bar.l.txt Open bar.cpp Verify order (foo.cpp bar.l.txt foo.l.txt bar.cpp) Verify that bar.cpp is activated*/ QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(false); enableOpenAfterCurrent(false); QTemporaryDir dirA; createFile(dirA, QStringLiteral("foo.l.txt")); createFile(dirA, QStringLiteral("foo.r.txt")); createFile(dirA, QStringLiteral("bar.l.txt")); createFile(dirA, QStringLiteral("bar.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.r.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "bar.l.txt")); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.l.txt")); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(m_uiController->activeSublimeWindow()->activeView()); //activate bar.l.txt m_uiController->activeSublimeWindow()->activateView(areaIndex->views().value(1)); m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "bar.r.txt")); verifyFilename(areaIndex->views().value(0), "foo.r.txt"); verifyFilename(areaIndex->views().value(1), "bar.l.txt"); verifyFilename(areaIndex->views().value(2), "foo.l.txt"); verifyFilename(areaIndex->views().value(3), "bar.r.txt"); verifyFilename(m_uiController->activeSublimeWindow()->activeView(), "bar.r.txt"); QCOMPARE(m_documentController->openDocuments().count(), 4); for(int i = 0; i < 4; i++) m_documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(m_documentController->openDocuments().count(), 0); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void TestShellBuddy::testsplitViewBuddies() { Sublime::MainWindow *pMainWindow = m_uiController->activeSublimeWindow(); QCOMPARE(m_documentController->openDocuments().count(), 0); enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; createFile(dirA, QStringLiteral("classA.r.txt")); createFile(dirA, QStringLiteral("classA.l.txt")); createFile(dirA, QStringLiteral("foo.txt")); Sublime::Area *pCodeArea = m_uiController->activeArea(); QVERIFY(pCodeArea); IDocument *pClassAHeader = m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "classA.l.txt")); QVERIFY(pClassAHeader); Sublime::View *pClassAHeaderView = pMainWindow->activeView(); pClassAHeaderView->setObjectName(QStringLiteral("classA.l.txt")); // now, create a split view of the active view (pClassAHeader) Sublime::View *pClassAHeaderSplitView = dynamic_cast(pClassAHeader)->createView(); pClassAHeaderSplitView->setObjectName("splitOf" + pMainWindow->activeView()->objectName()); pCodeArea->addView(pClassAHeaderSplitView, pMainWindow->activeView(), Qt::Vertical); // and activate it pMainWindow->activateView(pClassAHeaderSplitView); // 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 *pLeftContainer = pSplitter->findChildren().at(1); QVERIFY(pLeftContainer); Sublime::Container *pRightContainer = pSplitter->findChildren().at(0); QVERIFY(pRightContainer); // check that it only contains pClassAHeaderSplitView QVERIFY(pRightContainer->count() == 1 && pRightContainer->hasWidget(pClassAHeaderSplitView->widget())); // now open the correponding definition file, classA.r.txt IDocument *pClassAImplem = m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "classA.r.txt")); QVERIFY(pClassAImplem); pMainWindow->activeView()->setObjectName(QStringLiteral("classA.r.txt")); // and check its presence alongside pClassAHeaderSplitView in pRightContainer QVERIFY(pRightContainer->hasWidget(pClassAHeaderSplitView->widget())); QVERIFY(pRightContainer->hasWidget(pMainWindow->activeView()->widget())); // Now reactivate left side ClassAHeaderview pMainWindow->activateView(pClassAHeaderView); // open another file IDocument *pLeftSideCpp = m_documentController->openDocument(QUrl::fromLocalFile(dirA.path() + "foo.txt")); QVERIFY(pLeftSideCpp); pMainWindow->activeView()->setObjectName(QStringLiteral("foo.txt")); // and close left side ClassAHeaderview pCodeArea->closeView(pClassAHeaderView); // try to open classAImpl (which is already on the right) // but this time it should open on the left bool successfullyReOpened = m_documentController->openDocument(pClassAImplem); QVERIFY(successfullyReOpened); pMainWindow->activeView()->setObjectName(QStringLiteral("classA.r.txt")); // and check if it correctly opened on the left side QVERIFY(pLeftContainer->hasWidget(pMainWindow->activeView()->widget())); } QTEST_MAIN(TestShellBuddy) diff --git a/kdevplatform/shell/tests/test_shelldocumentoperation.cpp b/kdevplatform/shell/tests/test_shelldocumentoperation.cpp index d194a9810e..964ab86e0b 100644 --- a/kdevplatform/shell/tests/test_shelldocumentoperation.cpp +++ b/kdevplatform/shell/tests/test_shelldocumentoperation.cpp @@ -1,146 +1,146 @@ /*************************************************************************** * Copyright 2008 Alexander Dymo * * * * 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_shelldocumentoperation.h" #include #include -#include -#include -#include -#include -#include -#include - #include #include #include #include #include "../documentcontroller.h" #include "../uicontroller.h" +#include +#include +#include +#include +#include +#include + using namespace KDevelop; void TestShellDocumentOperation::initTestCase() { AutoTestShell::init({{}}); // do not load plugins at all TestCore::initialize(); } void TestShellDocumentOperation::cleanupTestCase() { TestCore::shutdown(); } void TestShellDocumentOperation::testOpenDocumentFromText() { //open some docs IDocumentController *documentController = Core::self()->documentController(); documentController->openDocumentFromText(QStringLiteral("Test1")); //test that we have this document in the list, signals are emitted and so on QCOMPARE(documentController->openDocuments().count(), 1); QCOMPARE(documentController->openDocuments().at(0)->textDocument()->text(), QStringLiteral("Test1")); Sublime::Area *area = Core::self()->uiControllerInternal()->activeArea(); QCOMPARE(area->views().count(), 1); documentController->openDocuments().at(0)->close(IDocument::Discard); // We used to have a bug where closing document failed to remove its // views from area, so check it here. QCOMPARE(area->views().count(), 0); } void TestShellDocumentOperation::testClosing() { // Test that both the view and the view widget is deleted when closing // document. { IDocumentController *documentController = Core::self()->documentController(); documentController->openDocumentFromText(QStringLiteral("Test1")); Sublime::Area *area = Core::self()->uiControllerInternal()->activeArea(); QCOMPARE(area->views().count(), 1); QPointer the_view = area->views().at(0); QPointer the_widget = the_view->widget(); documentController->openDocuments().at(0)->close(IDocument::Discard); QCOMPARE(the_view.data(), (Sublime::View*)nullptr); QCOMPARE(the_widget.data(), (QWidget*)nullptr); } // Now try the same, where there are two open documents. { IDocumentController *documentController = Core::self()->documentController(); // Annoying, the order of documents in // documentController->openDocuments() depends on how URLs hash. So, // to reliably close the second one, get hold of a pointer. IDocument* doc1 = documentController->openDocumentFromText(QStringLiteral("Test1")); IDocument* doc2 = documentController->openDocumentFromText(QStringLiteral("Test2")); Sublime::Area *area = Core::self()->uiControllerInternal()->activeArea(); QCOMPARE(area->views().count(), 2); QPointer the_view = area->views().at(1); qDebug() << this << "see views " << area->views().at(0) << " " << area->views().at(1); QPointer the_widget = the_view->widget(); doc2->close(IDocument::Discard); QCOMPARE(the_view.data(), (Sublime::View*)nullptr); QCOMPARE(the_widget.data(), (QWidget*)nullptr); doc1->close(IDocument::Discard); } } void TestShellDocumentOperation::testKateDocumentAndViewCreation() { //create one document IDocumentController *documentController = Core::self()->documentController(); documentController->openDocumentFromText(QString()); QCOMPARE(documentController->openDocuments().count(), 1); //assure we have only one kate view for the newly created document KTextEditor::Document *doc = documentController->openDocuments().at(0)->textDocument(); QCOMPARE(doc->views().count(), 1); QCOMPARE(dynamic_cast(doc)->revision(), qint64(0)); //also assure the view's xmlgui is plugged in KParts::MainWindow *main = Core::self()->uiControllerInternal()->activeMainWindow(); QVERIFY(main); QVERIFY(main->guiFactory()->clients().contains(doc->views().at(0))); //KTextEditor::views is internally a QHash::keys() call: so the order of the views will vary const auto originalView = doc->views().at(0); //create the new view and activate it (using split action from mainwindow) QAction *splitAction = main->actionCollection()->action(QStringLiteral("split_vertical")); QVERIFY(splitAction); splitAction->trigger(); const auto viewList = doc->views(); QCOMPARE(viewList.count(), 2); const auto newlySplitView = originalView == viewList[0] ? viewList[1] : viewList[0]; //check that we did switch to the new xmlguiclient QVERIFY(!main->guiFactory()->clients().contains(originalView)); QVERIFY(main->guiFactory()->clients().contains(newlySplitView)); documentController->openDocuments().at(0)->close(IDocument::Discard); } QTEST_MAIN(TestShellDocumentOperation) diff --git a/kdevplatform/sublime/container.cpp b/kdevplatform/sublime/container.cpp index 4da6cc0ebe..3eb3778d01 100644 --- a/kdevplatform/sublime/container.cpp +++ b/kdevplatform/sublime/container.cpp @@ -1,759 +1,760 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * 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 "container.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "view.h" #include "urldocument.h" -#include + +#include namespace Sublime { // class ContainerTabBar class ContainerTabBar : public QTabBar { Q_OBJECT public: explicit ContainerTabBar(Container* container) : QTabBar(container), m_container(container) { if (QApplication::style()->objectName() == QLatin1String("macintosh")) { static QPointer qTabBarStyle = QStyleFactory::create(QStringLiteral("fusion")); if (qTabBarStyle) { setStyle(qTabBarStyle); } } // configure the QTabBar style so it behaves as appropriately as possible, // even if we end up using the native Macintosh style because the user's // Qt doesn't have the Fusion style installed. setDocumentMode(true); setUsesScrollButtons(true); setElideMode(Qt::ElideNone); installEventFilter(this); } bool event(QEvent* ev) override { if(ev->type() == QEvent::ToolTip) { ev->accept(); QHelpEvent* helpEvent = static_cast(ev); int tab = tabAt(helpEvent->pos()); if(tab != -1) { m_container->showTooltipForTab(tab); } return true; } return QTabBar::event(ev); } void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::MidButton) { // just close on midbutton, drag can still be done with left mouse button int tab = tabAt(event->pos()); if (tab != -1) { emit tabCloseRequested(tab); } return; } QTabBar::mousePressEvent(event); } bool eventFilter(QObject* obj, QEvent* event) override { if (obj != this) { return QObject::eventFilter(obj, event); } // TODO Qt6: Move to mouseDoubleClickEvent when fixme in qttabbar.cpp is resolved // see "fixme Qt 6: move to mouseDoubleClickEvent(), here for BC reasons." in qtabbar.cpp if (event->type() == QEvent::MouseButtonDblClick) { // block tabBarDoubleClicked signals with RMB, see https://bugs.kde.org/show_bug.cgi?id=356016 auto mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::MidButton) { return true; } } return QObject::eventFilter(obj, event); } Q_SIGNALS: void newTabRequested(); private: Container* m_container; }; bool sortViews(const View* const lhs, const View* const rhs) { return lhs->document()->title().compare(rhs->document()->title(), Qt::CaseInsensitive) < 0; } #ifdef Q_OS_MACOS // only one of these per process: static QMenu* currentDockMenu = nullptr; #endif class ContainerPrivate { public: QBoxLayout* layout; QHash viewForWidget; ContainerTabBar *tabBar; QStackedWidget *stack; KSqueezedTextLabel *fileNameCorner; QLabel *shortcutHelpLabel; QLabel *fileStatus; KSqueezedTextLabel *statusCorner; QPointer leftCornerWidget; QToolButton* documentListButton; QMenu* documentListMenu; QHash documentListActionForView; /** * Updates the context menu which is shown when * the document list button in the tab bar is clicked. * * It shall build a popup menu which contains all currently * enabled views using the title their document provides. */ void updateDocumentListPopupMenu() { qDeleteAll(documentListActionForView); documentListActionForView.clear(); documentListMenu->clear(); // create a lexicographically sorted list QVector views; views.reserve(viewForWidget.size()); foreach(View* view, viewForWidget){ views << view; } std::sort(views.begin(), views.end(), sortViews); for (int i = 0; i < views.size(); ++i) { View *view = views.at(i); QString visibleEntryTitle; // if filename is not unique, prepend containing directory if ((i < views.size() - 1 && view->document()->title() == views.at(i + 1)->document()->title()) || (i > 0 && view->document()->title() == views.at(i - 1)->document()->title()) ) { auto urlDoc = qobject_cast(view->document()); if (!urlDoc) { visibleEntryTitle = view->document()->title(); } else { auto url = urlDoc->url().toString(); int secondOffset = url.lastIndexOf(QLatin1Char('/')); secondOffset = url.lastIndexOf(QLatin1Char('/'), secondOffset - 1); visibleEntryTitle = url.right(url.length() - url.lastIndexOf(QLatin1Char('/'), secondOffset) - 1); } } else { visibleEntryTitle = view->document()->title(); } QAction* action = documentListMenu->addAction(visibleEntryTitle); action->setData(QVariant::fromValue(view)); documentListActionForView[view] = action; action->setIcon(view->document()->icon()); ///FIXME: push this code somehow into shell, such that we can access the project model for /// icons and also get a neat, short path like the document switcher. } setAsDockMenu(); } void setAsDockMenu() { #ifdef Q_OS_MACOS if (documentListMenu != currentDockMenu) { documentListMenu->setAsDockMenu(); currentDockMenu = documentListMenu; } #endif } ~ContainerPrivate() { #ifdef Q_OS_MACOS if (documentListMenu == currentDockMenu) { QMenu().setAsDockMenu(); currentDockMenu = nullptr; } #endif } }; class UnderlinedLabel: public KSqueezedTextLabel { Q_OBJECT public: explicit UnderlinedLabel(QTabBar *tabBar, QWidget* parent = nullptr) :KSqueezedTextLabel(parent), m_tabBar(tabBar) { } protected: void paintEvent(QPaintEvent *ev) override { #ifndef Q_OS_OSX // getting the underlining right on OS X is tricky; omitting // the underlining attracts the eye less than not getting it // exactly right. if (m_tabBar->isVisible() && m_tabBar->count() > 0) { QStylePainter p(this); QStyleOptionTabBarBase optTabBase; optTabBase.init(m_tabBar); optTabBase.shape = m_tabBar->shape(); optTabBase.tabBarRect = m_tabBar->rect(); optTabBase.tabBarRect.moveRight(0); QStyleOptionTab tabOverlap; tabOverlap.shape = m_tabBar->shape(); int overlap = style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, m_tabBar); if( overlap > 0 ) { QRect rect; rect.setRect(0, height()-overlap, width(), overlap); optTabBase.rect = rect; } if( m_tabBar->drawBase() ) { p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase); } } #endif KSqueezedTextLabel::paintEvent(ev); } QTabBar *m_tabBar; }; class StatusLabel: public UnderlinedLabel { Q_OBJECT public: explicit StatusLabel(QTabBar *tabBar, QWidget* parent = nullptr): UnderlinedLabel(tabBar, parent) { setAlignment(Qt::AlignRight | Qt::AlignVCenter); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); } QSize minimumSizeHint() const override { QRect rect = style()->itemTextRect(fontMetrics(), QRect(), Qt::AlignRight, true, i18n("Line: 00000 Col: 000")); rect.setHeight(m_tabBar->height()); return rect.size(); } }; // class Container Container::Container(QWidget *parent) :QWidget(parent), d(new ContainerPrivate()) { KAcceleratorManager::setNoAccel(this); QBoxLayout *l = new QBoxLayout(QBoxLayout::TopToBottom, this); l->setMargin(0); l->setSpacing(0); d->layout = new QBoxLayout(QBoxLayout::LeftToRight); d->layout->setMargin(0); d->layout->setSpacing(0); d->documentListMenu = new QMenu(this); d->documentListButton = new QToolButton(this); d->documentListButton->setIcon(QIcon::fromTheme(QStringLiteral("format-list-unordered"))); d->documentListButton->setMenu(d->documentListMenu); #ifdef Q_OS_MACOS // for maintaining the Dock menu: setFocusPolicy(Qt::StrongFocus); #endif d->documentListButton->setPopupMode(QToolButton::InstantPopup); d->documentListButton->setAutoRaise(true); d->documentListButton->setToolTip(i18n("Show sorted list of opened documents")); d->documentListButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); d->layout->addWidget(d->documentListButton); d->tabBar = new ContainerTabBar(this); d->tabBar->setContextMenuPolicy(Qt::CustomContextMenu); d->layout->addWidget(d->tabBar); d->fileStatus = new QLabel( this ); d->fileStatus->setFixedSize( QSize( 16, 16 ) ); d->layout->addWidget(d->fileStatus); d->fileNameCorner = new UnderlinedLabel(d->tabBar, this); d->fileNameCorner->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); d->layout->addWidget(d->fileNameCorner); d->shortcutHelpLabel = new QLabel(i18n("(Press Ctrl+Tab to switch)"), this); auto font = d->shortcutHelpLabel->font(); font.setPointSize(font.pointSize() - 2); font.setItalic(true); d->shortcutHelpLabel->setFont(font); d->layout->addSpacerItem(new QSpacerItem(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0, QSizePolicy::Fixed, QSizePolicy::Fixed)); d->shortcutHelpLabel->setAlignment(Qt::AlignCenter); d->layout->addWidget(d->shortcutHelpLabel); d->layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); d->statusCorner = new StatusLabel(d->tabBar, this); d->layout->addWidget(d->statusCorner); l->addLayout(d->layout); d->stack = new QStackedWidget(this); l->addWidget(d->stack); connect(d->tabBar, &ContainerTabBar::currentChanged, this, &Container::widgetActivated); connect(d->tabBar, &ContainerTabBar::tabCloseRequested, this, static_cast(&Container::requestClose)); connect(d->tabBar, &ContainerTabBar::newTabRequested, this, &Container::newTabRequested); connect(d->tabBar, &ContainerTabBar::tabMoved, this, &Container::tabMoved); connect(d->tabBar, &ContainerTabBar::customContextMenuRequested, this, &Container::contextMenu); connect(d->tabBar, &ContainerTabBar::tabBarDoubleClicked, this, &Container::doubleClickTriggered); connect(d->documentListMenu, &QMenu::triggered, this, &Container::documentListActionTriggered); setTabBarHidden(!configTabBarVisible()); d->tabBar->setTabsClosable(true); d->tabBar->setMovable(true); d->tabBar->setExpanding(false); d->tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); } Container::~Container() = default; bool Container::configTabBarVisible() { KConfigGroup group = KSharedConfig::openConfig()->group("UiSettings"); return group.readEntry("TabBarVisibility", 1); } void Container::setLeftCornerWidget(QWidget* widget) { if(d->leftCornerWidget.data() == widget) { if(d->leftCornerWidget) d->leftCornerWidget.data()->setParent(nullptr); }else{ delete d->leftCornerWidget.data(); d->leftCornerWidget.clear(); } d->leftCornerWidget = widget; if(!widget) return; widget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); d->layout->insertWidget(0, widget); widget->show(); } QList Container::views() const { return d->viewForWidget.values(); } void Container::requestClose(int idx) { emit requestClose(widget(idx)); } void Container::widgetActivated(int idx) { if (idx < 0) return; if (QWidget* w = d->stack->widget(idx)) { Sublime::View* view = d->viewForWidget.value(w); if(view) emit activateView(view); } } void Container::addWidget(View *view, int position) { QWidget *w = view->widget(this); int idx = 0; if (position != -1) { idx = d->stack->insertWidget(position, w); } else idx = d->stack->addWidget(w); d->tabBar->insertTab(idx, view->document()->statusIcon(), view->document()->title()); Q_ASSERT(view); d->viewForWidget[w] = view; // Update document list context menu. This has to be called before // setCurrentWidget, because we call the status icon and title update slots // already, which in turn need the document list menu to be setup. d->updateDocumentListPopupMenu(); setCurrentWidget(d->stack->currentWidget()); // This fixes a strange layouting bug, that could be reproduced like this: Open a few files in KDevelop, activate the rightmost tab. // Then temporarily switch to another area, and then switch back. After that, the tab-bar was gone. // The problem could only be fixed by closing/opening another view. d->tabBar->setMinimumHeight(d->tabBar->sizeHint().height()); connect(view, &View::statusChanged, this, &Container::statusChanged); connect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); connect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); } void Container::statusChanged(Sublime::View* view) { const auto statusText = view->viewStatus(); d->statusCorner->setText(statusText); d->statusCorner->setVisible(!statusText.isEmpty()); } void Container::statusIconChanged(Document* doc) { QHashIterator it = d->viewForWidget; while (it.hasNext()) { if (it.next().value()->document() == doc) { d->fileStatus->setPixmap( doc->statusIcon().pixmap( QSize( 16,16 ) ) ); int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabIcon(tabIndex, doc->statusIcon()); } // Update the document title's menu associated action // using the View* index map Q_ASSERT(d->documentListActionForView.contains(it.value())); d->documentListActionForView[it.value()]->setIcon(doc->icon()); break; } } } void Container::documentTitleChanged(Sublime::Document* doc) { QHashIterator it = d->viewForWidget; while (it.hasNext()) { Sublime::View* view = it.next().value(); if (view->document() == doc) { if (currentView() == view) { d->fileNameCorner->setText( doc->title(Document::Extended) ); // TODO KF6: remove this as soon as it is included upstream and we reqire // that version // see https://phabricator.kde.org/D7010 d->fileNameCorner->updateGeometry(); } int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabText(tabIndex, doc->title()); } break; } } // Update document list popup title d->updateDocumentListPopupMenu(); } int Container::count() const { return d->stack->count(); } QWidget* Container::currentWidget() const { return d->stack->currentWidget(); } void Container::setCurrentWidget(QWidget* w) { if (d->stack->currentWidget() == w) { return; } d->stack->setCurrentWidget(w); d->tabBar->setCurrentIndex(d->stack->indexOf(w)); if (View* view = viewForWidget(w)) { statusChanged(view); if (!d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us statusIconChanged( view->document() ); documentTitleChanged( view->document() ); } } } QWidget* Container::widget(int i) const { return d->stack->widget(i); } int Container::indexOf(QWidget* w) const { return d->stack->indexOf(w); } void Container::removeWidget(QWidget *w) { if (w) { int widgetIdx = d->stack->indexOf(w); d->stack->removeWidget(w); d->tabBar->removeTab(widgetIdx); if (d->tabBar->currentIndex() != -1 && !d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us View* view = currentView(); if( view ) { statusIconChanged( view->document() ); documentTitleChanged( view->document() ); } } View* view = d->viewForWidget.take(w); if (view) { disconnect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); disconnect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); disconnect(view, &View::statusChanged, this, &Container::statusChanged); // Update document list context menu Q_ASSERT(d->documentListActionForView.contains(view)); delete d->documentListActionForView.take(view); } } } bool Container::hasWidget(QWidget *w) { return d->stack->indexOf(w) != -1; } View *Container::viewForWidget(QWidget *w) const { return d->viewForWidget.value(w); } void Container::setTabBarHidden(bool hide) { if (hide) { d->tabBar->hide(); d->fileStatus->show(); d->shortcutHelpLabel->show(); d->fileNameCorner->show(); } else { d->fileNameCorner->hide(); d->fileStatus->hide(); d->tabBar->show(); d->shortcutHelpLabel->hide(); } View* v = currentView(); if (v) { documentTitleChanged(v->document()); } } void Container::resetTabColors(const QColor& color) { for (int i = 0; i < count(); i++){ d->tabBar->setTabTextColor(i, color); } } void Container::setTabColor(const View* view, const QColor& color) { for (int i = 0; i < count(); i++){ if (view == viewForWidget(widget(i))) { d->tabBar->setTabTextColor(i, color); } } } void Container::setTabColors(const QHash& colors) { for (int i = 0; i < count(); i++) { auto view = viewForWidget(widget(i)); auto color = colors[view]; if (color.isValid()) { d->tabBar->setTabTextColor(i, color); } } } void Container::tabMoved(int from, int to) { QWidget *w = d->stack->widget(from); d->stack->removeWidget(w); d->stack->insertWidget(to, w); d->viewForWidget[w]->notifyPositionChanged(to); } void Container::contextMenu( const QPoint& pos ) { QWidget* senderWidget = qobject_cast(sender()); Q_ASSERT(senderWidget); int currentTab = d->tabBar->tabAt(pos); QMenu menu; // At least for positioning on Wayland the window the menu belongs to // needs to be set. We cannot set senderWidget as parent because some actions (e.g. split view) // result in sync destruction of the senderWidget, which then would also prematurely // destruct the menu object // Workaround (best known currently, check again API of Qt >5.9): menu.winId(); // trigger being a native widget already, to ensure windowHandle created auto parentWindowHandle = senderWidget->windowHandle(); if (!parentWindowHandle) { parentWindowHandle = senderWidget->nativeParentWidget()->windowHandle(); } menu.windowHandle()->setTransientParent(parentWindowHandle); Sublime::View* view = viewForWidget(widget(currentTab)); emit tabContextMenuRequested(view, &menu); menu.addSeparator(); QAction* copyPathAction = nullptr; QAction* closeTabAction = nullptr; QAction* closeOtherTabsAction = nullptr; if (view) { copyPathAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Filename")); menu.addSeparator(); closeTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close")); closeOtherTabsAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close All Other")); } QAction* closeAllTabsAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close All")); QAction* triggered = menu.exec(senderWidget->mapToGlobal(pos)); if (triggered) { if ( triggered == closeTabAction ) { requestClose(currentTab); } else if ( triggered == closeOtherTabsAction ) { // activate the remaining tab widgetActivated(currentTab); // first get the widgets to be closed since otherwise the indices will be wrong QList otherTabs; for ( int i = 0; i < count(); ++i ) { if ( i != currentTab ) { otherTabs << widget(i); } } // finally close other tabs foreach( QWidget* tab, otherTabs ) { emit requestClose(tab); } } else if ( triggered == closeAllTabsAction ) { // activate last tab widgetActivated(count() - 1); // close all for ( int i = 0; i < count(); ++i ) { emit requestClose(widget(i)); } } else if( triggered == copyPathAction ) { auto view = viewForWidget( widget( currentTab ) ); auto urlDocument = qobject_cast( view->document() ); if( urlDocument ) { QString toCopy = urlDocument->url().toDisplayString(QUrl::PreferLocalFile); if (urlDocument->url().isLocalFile()) { toCopy = QDir::toNativeSeparators(toCopy); } QApplication::clipboard()->setText(toCopy); } } // else the action was handled by someone else } } void Container::showTooltipForTab(int tab) { emit tabToolTipRequested(viewForWidget(widget(tab)), this, tab); } bool Container::isCurrentTab(int tab) const { return d->tabBar->currentIndex() == tab; } QRect Container::tabRect(int tab) const { return d->tabBar->tabRect(tab).translated(d->tabBar->mapToGlobal(QPoint(0, 0))); } void Container::doubleClickTriggered(int tab) { if (tab == -1) { emit newTabRequested(); } else { emit tabDoubleClicked(viewForWidget(widget(tab))); } } void Container::documentListActionTriggered(QAction* action) { Sublime::View* view = action->data().value< Sublime::View* >(); Q_ASSERT(view); QWidget* widget = d->viewForWidget.key(view); Q_ASSERT(widget); setCurrentWidget(widget); } Sublime::View* Container::currentView() const { return d->viewForWidget.value(widget( d->tabBar->currentIndex() )); } void Container::focusInEvent(QFocusEvent* event) { d->setAsDockMenu(); QWidget::focusInEvent(event); } } #include "container.moc" diff --git a/kdevplatform/sublime/examples/example1main.h b/kdevplatform/sublime/examples/example1main.h index 07a237efeb..2a65941982 100644 --- a/kdevplatform/sublime/examples/example1main.h +++ b/kdevplatform/sublime/examples/example1main.h @@ -1,50 +1,50 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_EXAMPLE1MAIN_H #define KDEVPLATFORM_EXAMPLE1MAIN_H -#include +#include namespace Sublime { class Area; class Controller; } class Example1Main: public KXmlGuiWindow { Q_OBJECT public: Example1Main(); public Q_SLOTS: void selectArea1(); void selectArea2(); private Q_SLOTS: void updateTitle(Sublime::Area *area); private: Sublime::Controller *m_controller; Sublime::Area *m_area1; Sublime::Area *m_area2; }; #endif diff --git a/kdevplatform/sublime/urldocument.cpp b/kdevplatform/sublime/urldocument.cpp index 02485f0278..6ec4547b5a 100644 --- a/kdevplatform/sublime/urldocument.cpp +++ b/kdevplatform/sublime/urldocument.cpp @@ -1,98 +1,98 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * 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 "urldocument.h" #include #include #include #include -#include +#include namespace Sublime { // class UrlDocumentPrivate class UrlDocumentPrivate { public: QUrl url; }; // class UrlDocument UrlDocument::UrlDocument(Controller *controller, const QUrl &url) :Document(url.fileName(), controller), d( new UrlDocumentPrivate() ) { setUrl(url); } UrlDocument::~UrlDocument() = default; QUrl UrlDocument::url() const { return d->url; } void UrlDocument::setUrl(const QUrl& newUrl) { Q_ASSERT(newUrl.adjusted(QUrl::NormalizePathSegments) == newUrl); d->url = newUrl; // remote URLs might not have a file name Q_ASSERT(!newUrl.fileName().isEmpty() || !newUrl.isLocalFile()); auto title = newUrl.fileName(); if (title.isEmpty()) { title = i18n("Untitled"); } setTitle(title); setToolTip(newUrl.toDisplayString(QUrl::PreferLocalFile)); } QWidget *UrlDocument::createViewWidget(QWidget *parent) { ///@todo adymo: load file contents here return new KTextEdit(parent); } QString UrlDocument::documentType() const { return QStringLiteral("Url"); } QString UrlDocument::documentSpecifier() const { return d->url.url(); } QIcon UrlDocument::defaultIcon() const { return QIcon::fromTheme(KIO::iconNameForUrl(d->url)); } QString UrlDocument::title(TitleType type) const { if (type == Extended) return Document::title() + QLatin1String(" (") + url().toDisplayString(QUrl::PreferLocalFile) + QLatin1Char(')'); else return Document::title(); } } diff --git a/kdevplatform/tests/kdevsignalspy.cpp b/kdevplatform/tests/kdevsignalspy.cpp index 7fdc74434a..16fe568dbd 100644 --- a/kdevplatform/tests/kdevsignalspy.cpp +++ b/kdevplatform/tests/kdevsignalspy.cpp @@ -1,62 +1,63 @@ /* * This file is part of KDevelop * Copyright 2008 Manuel Breugelmans * * 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 "kdevsignalspy.h" -#include -#include + +#include +#include namespace KDevelop { KDevSignalSpy::KDevSignalSpy(QObject *obj, const char *signal, Qt::ConnectionType ct ) : QObject(nullptr), m_obj(obj), m_emitted(false) { m_timer = new QTimer(this); m_loop = new QEventLoop(this); connect(obj, signal, this, SLOT(signalEmitted()), ct); } bool KDevSignalSpy::wait(int timeout) { Q_ASSERT(!m_loop->isRunning()); Q_ASSERT(!m_timer->isActive()); m_emitted = false; if (timeout > 0) { connect(m_timer, &QTimer::timeout, m_loop, &QEventLoop::quit); m_timer->setSingleShot(true); m_timer->start(timeout); } m_loop->exec(); return m_emitted; } void KDevSignalSpy::signalEmitted() { m_emitted = true; disconnect(m_obj, nullptr, this, nullptr); m_timer->stop(); m_loop->quit(); } } // KDevelop diff --git a/kdevplatform/util/commandexecutor.cpp b/kdevplatform/util/commandexecutor.cpp index cc3f7c3733..3a7e247c4b 100644 --- a/kdevplatform/util/commandexecutor.cpp +++ b/kdevplatform/util/commandexecutor.cpp @@ -1,166 +1,168 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat 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 "commandexecutor.h" #include "processlinemaker.h" + +#include +#include + #include #include #include -#include -#include namespace KDevelop { class CommandExecutorPrivate { public: explicit CommandExecutorPrivate( CommandExecutor* cmd ) : m_exec(cmd), m_useShell(false) { } CommandExecutor* m_exec; KProcess* m_process; ProcessLineMaker* m_lineMaker; QString m_command; QStringList m_args; QString m_workDir; QMap m_env; bool m_useShell; void procError( QProcess::ProcessError error ) { Q_UNUSED(error) m_lineMaker->flushBuffers(); emit m_exec->failed( error ); } void procFinished( int code, QProcess::ExitStatus status ) { m_lineMaker->flushBuffers(); if( status == QProcess::NormalExit ) emit m_exec->completed(code); } }; CommandExecutor::CommandExecutor( const QString& command, QObject* parent ) : QObject(parent), d(new CommandExecutorPrivate(this)) { d->m_process = new KProcess(this); d->m_process->setOutputChannelMode( KProcess::SeparateChannels ); d->m_lineMaker = new ProcessLineMaker( d->m_process ); d->m_command = command; connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &CommandExecutor::receivedStandardOutput ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &CommandExecutor::receivedStandardError ); connect( d->m_process, static_cast(&KProcess::error), this, [&] (QProcess::ProcessError error) { d->procError(error); } ); connect( d->m_process, static_cast(&KProcess::finished), this, [&] (int code, QProcess::ExitStatus status) { d->procFinished(code, status); } ); } CommandExecutor::~CommandExecutor() { delete d->m_process; delete d->m_lineMaker; } void CommandExecutor::setEnvironment( const QMap& env ) { d->m_env = env; } void CommandExecutor::setEnvironment( const QStringList& env ) { QMap envmap; for (const QString& var : env) { int sep = var.indexOf(QLatin1Char('=')); envmap.insert( var.left( sep ), var.mid( sep + 1 ) ); } d->m_env = envmap; } void CommandExecutor::setArguments( const QStringList& args ) { d->m_args = args; } void CommandExecutor::setWorkingDirectory( const QString& dir ) { d->m_workDir = dir; } bool CommandExecutor::useShell() const { return d->m_useShell; } void CommandExecutor::setUseShell( bool shell ) { d->m_useShell = shell; } void CommandExecutor::start() { for(auto it = d->m_env.constBegin(), itEnd = d->m_env.constEnd(); it!=itEnd; ++it) { d->m_process->setEnv( it.key(), it.value() ); } d->m_process->setWorkingDirectory( d->m_workDir ); if( !d->m_useShell ) { d->m_process->setProgram( d->m_command, d->m_args ); } else { QStringList arguments; arguments.reserve(d->m_args.size()); Q_FOREACH( const QString &a, d->m_args ) arguments << KShell::quoteArg( a ); d->m_process->setShellCommand(d->m_command + QLatin1Char(' ') + arguments.join(QLatin1Char(' '))); } d->m_process->start(); } void CommandExecutor::setCommand( const QString& command ) { d->m_command = command; } void CommandExecutor::kill() { d->m_process->kill(); } QString CommandExecutor::command() const { return d->m_command; } QStringList CommandExecutor::arguments() const { return d->m_args; } QString CommandExecutor::workingDirectory() const { return d->m_workDir; } } #include "moc_commandexecutor.cpp" diff --git a/kdevplatform/util/shellutils.cpp b/kdevplatform/util/shellutils.cpp index b2c6f47ee8..d281451318 100644 --- a/kdevplatform/util/shellutils.cpp +++ b/kdevplatform/util/shellutils.cpp @@ -1,117 +1,118 @@ /* * This file is part of KDevelop * * Copyright 2012 Ivan Shapovalov * * 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 "shellutils.h" #include #include #include #include #include -#include -#include #include #include -#include + +#include +#include +#include namespace KDevelop { bool askUser( const QString& mainText, const QString& ttyPrompt, const QString& mboxTitle, const QString& mboxAdditionalText, const QString& confirmText, const QString& rejectText, bool ttyDefaultToYes ) { if( !qobject_cast(qApp) ) { // no ui-mode e.g. for duchainify and other tools QTextStream out( stdout ); out << mainText << endl; QTextStream in( stdin ); QString input; forever { if( ttyDefaultToYes ) { out << QStringLiteral( "%1: [Y/n] " ).arg( ttyPrompt ) << flush; } else { out << QStringLiteral( "%1: [y/N] ").arg( ttyPrompt ) << flush; } input = in.readLine().trimmed(); if( input.isEmpty() ) { return ttyDefaultToYes; } else if( input.toLower() == QLatin1String("y") ) { return true; } else if( input.toLower() == QLatin1String("n") ) { return false; } } } else { auto okButton = KStandardGuiItem::ok(); okButton.setText(confirmText); auto rejectButton = KStandardGuiItem::cancel(); rejectButton.setText(rejectText); int userAnswer = KMessageBox::questionYesNo( ICore::self()->uiController()->activeMainWindow(), mainText + QLatin1String("\n\n") + mboxAdditionalText, mboxTitle, okButton, rejectButton ); return userAnswer == KMessageBox::Yes; } } bool ensureWritable( const QList &urls ) { QStringList notWritable; for (const QUrl& url : urls) { if (url.isLocalFile()) { QFile file(url.toLocalFile()); if (file.exists() && !(file.permissions() & QFileDevice::WriteOwner) && !(file.permissions() & QFileDevice::WriteGroup)) { notWritable << url.toLocalFile(); } } } if (!notWritable.isEmpty()) { int answer = KMessageBox::questionYesNoCancel(ICore::self()->uiController()->activeMainWindow(), i18n("You don't have write permissions for the following files; add write permissions for owner before saving?") + QLatin1String("\n\n") + notWritable.join(QLatin1Char('\n')), i18n("Some files are write-protected"), KStandardGuiItem::yes(), KStandardGuiItem::no(), KStandardGuiItem::cancel()); if (answer == KMessageBox::Yes) { bool success = true; foreach (const QString& filename, notWritable) { QFile file(filename); QFileDevice::Permissions permissions = file.permissions(); permissions |= QFileDevice::WriteOwner; success &= file.setPermissions(permissions); } if (!success) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Failed adding write permissions for some files."), i18n("Failed setting permissions")); return false; } } return answer != KMessageBox::Cancel; } return true; } } diff --git a/kdevplatform/vcs/interfaces/ipatchsource.h b/kdevplatform/vcs/interfaces/ipatchsource.h index 78597ffad5..e9b8f104c9 100644 --- a/kdevplatform/vcs/interfaces/ipatchsource.h +++ b/kdevplatform/vcs/interfaces/ipatchsource.h @@ -1,113 +1,113 @@ /* Copyright 2006 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. */ #ifndef KDEVPLATFORM_IPATCHSOURCE_H #define KDEVPLATFORM_IPATCHSOURCE_H -#include -#include - -#include #include #include +#include +#include +#include + namespace KDevelop { ///Any entity may delete an IPatchSource based object at will, so it must always be referenced through a QPointer (Just use IPatchSource::Ptr). class KDEVPLATFORMVCS_EXPORT IPatchSource : public QObject { Q_OBJECT public: typedef QPointer Ptr; ///Name of the patch, that will be shown in a combo box. Should ///describe the patch in a useful way, for example "Difference to base in kdevplatform/language" virtual QString name() const = 0; ///Icon that will be shown with the patch virtual QIcon icon() const; ///Should tell if the patch is already applied on the local version. virtual bool isAlreadyApplied() const = 0; ///Explicit updating of the patch: If it is a dynamic patch, it ///should re-compare the files or whatever needs to be done ///If the patch has changed, patchChanged needs to be emitted virtual void update() = 0; ///Name of the patch file virtual QUrl file() const = 0; ///Should return the base-dir of the patch virtual QUrl baseDir() const = 0; ///Can return a custom widget that should be shown to the user with this patch ///The ownership of the widget is shared between the caller and the patch-source (both may delete it at will) ///The default implementation returns zero virtual QWidget* customWidget() const; ///May return a custom text for the "Finish Review" action. ///The default implementation returns QString(), which means that the default is used virtual QString finishReviewCustomText() const; ///Called when the user has reviewed and accepted this patch ///If canSelectFiles() returned true, @p selection will contain the list of selected files ///If this returns false, the review is not finished. virtual bool finishReview(const QList& selection); ///Called when the user has rejected this patch virtual void cancelReview(); ///Should return whether the user may cancel this review (cancelReview will be called when he does) ///The default implementation returns false virtual bool canCancel() const; ///Should return whether the user should be able to select files of the patch ///The files available for selection will be all files affected by the patch, and the files ///return by additionalSelectableFiles() The default implementation returns false virtual bool canSelectFiles() const; ///May return an additional list of selectable files together with short description strings for this patch ///The default implementation returns an empty list virtual QMap additionalSelectableFiles() const; /// Depth - number of directories to left-strip from paths in the patch - see "patch -p" /// Defaults to 0 virtual uint depth() const; Q_SIGNALS: ///Should be emitted whenever the patch has changed. void patchChanged(); }; class KDEVPLATFORMVCS_EXPORT IPatchReview { public: virtual ~IPatchReview(); enum ReviewMode { OpenAndRaise ///< Opens the related files in the review area, switches to that area, and raises the patch-review tool view }; ///Starts a review on the patch: Opens the patch and the files within the review area virtual void startReview(IPatchSource* patch, ReviewMode mode = OpenAndRaise) = 0; }; } Q_DECLARE_INTERFACE(KDevelop::IPatchReview, "org.kdevelop.IPatchReview") #endif // KDEVPLATFORM_IPATCHSOURCE_H diff --git a/kdevplatform/vcs/models/vcsannotationmodel.h b/kdevplatform/vcs/models/vcsannotationmodel.h index 72cad83add..26758d0cc9 100644 --- a/kdevplatform/vcs/models/vcsannotationmodel.h +++ b/kdevplatform/vcs/models/vcsannotationmodel.h @@ -1,57 +1,58 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_VCSANNOTATIONMODEL_H #define KDEVPLATFORM_VCSANNOTATIONMODEL_H -#include #include #include "../vcsrevision.h" +#include + #include class QUrl; template class QList; namespace KDevelop { class VcsJob; class KDEVPLATFORMVCS_EXPORT VcsAnnotationModel : public KTextEditor::AnnotationModel { Q_OBJECT public: VcsAnnotationModel( VcsJob* job, const QUrl&, QObject*, const QColor& foreground = QColor(Qt::black), const QColor& background = QColor(Qt::white) ); ~VcsAnnotationModel() override; VcsRevision revisionForLine(int line) const; QVariant data( int line, Qt::ItemDataRole role = Qt::DisplayRole ) const override; private: const QScopedPointer d; friend class VcsAnnotationModelPrivate; }; } #endif diff --git a/plugins/appwizard/appwizardplugin.cpp b/plugins/appwizard/appwizardplugin.cpp index 366e3dc208..3bca2c9f34 100644 --- a/plugins/appwizard/appwizardplugin.cpp +++ b/plugins/appwizard/appwizardplugin.cpp @@ -1,567 +1,567 @@ /*************************************************************************** * Copyright 2001 Bernd Gehrmann * * Copyright 2004-2005 Sascha Cunz * * Copyright 2005 Ian Reinhart Geiser * * Copyright 2007 Alexander Dymo * * Copyright 2008 Evgeniy Ivanov * * * * 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 "appwizardplugin.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 #include #include "appwizarddialog.h" #include "projectselectionpage.h" #include "projectvcspage.h" #include "projecttemplatesmodel.h" #include "debug.h" using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(AppWizardFactory, "kdevappwizard.json", registerPlugin();) AppWizardPlugin::AppWizardPlugin(QObject *parent, const QVariantList &) : KDevelop::IPlugin(QStringLiteral("kdevappwizard"), parent) , m_templatesModel(nullptr) { setXMLFile(QStringLiteral("kdevappwizard.rc")); m_newFromTemplate = actionCollection()->addAction(QStringLiteral("project_new")); m_newFromTemplate->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template"))); m_newFromTemplate->setText(i18n("New From Template...")); connect(m_newFromTemplate, &QAction::triggered, this, &AppWizardPlugin::slotNewProject); m_newFromTemplate->setToolTip( i18n("Generate a new project from a template") ); m_newFromTemplate->setWhatsThis( i18n("This starts KDevelop's application wizard. " "It helps you to generate a skeleton for your " "application from a set of templates.") ); } AppWizardPlugin::~AppWizardPlugin() { } void AppWizardPlugin::slotNewProject() { model()->refresh(); ScopedDialog dlg(core()->pluginController(), m_templatesModel); if (dlg->exec() == QDialog::Accepted) { QString project = createProject( dlg->appInfo() ); if (!project.isEmpty()) { core()->projectController()->openProject(QUrl::fromLocalFile(project)); KConfig templateConfig(dlg->appInfo().appTemplate); KConfigGroup general(&templateConfig, "General"); const QStringList fileArgs = general.readEntry("ShowFilesAfterGeneration").split(QLatin1Char(','), QString::SkipEmptyParts); for (const auto& fileArg : fileArgs) { QString file = KMacroExpander::expandMacros(fileArg.trimmed(), m_variables); if (QDir::isRelativePath(file)) { file = m_variables[QStringLiteral("PROJECTDIR")] + QLatin1Char('/') + file; } core()->documentController()->openDocument(QUrl::fromUserInput(file)); } } else { KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n("Could not create project from template\n"), i18n("Failed to create project") ); } } } namespace { IDistributedVersionControl* toDVCS(IPlugin* plugin) { Q_ASSERT(plugin); return plugin->extension(); } ICentralizedVersionControl* toCVCS(IPlugin* plugin) { Q_ASSERT(plugin); return plugin->extension(); } /*! Trouble while initializing version control. Show failure message to user. */ void vcsError(const QString &errorMsg, QTemporaryDir &tmpdir, const QUrl &dest, const QString &details = QString()) { QString displayDetails = details; if (displayDetails.isEmpty()) { displayDetails = i18n("Please see the Version Control tool view."); } KMessageBox::detailedError(nullptr, errorMsg, displayDetails, i18n("Version Control System Error")); KIO::del(dest, KIO::HideProgressInfo)->exec(); tmpdir.remove(); } /*! Setup distributed version control for a new project defined by @p info. Use @p scratchArea for temporary files */ bool initializeDVCS(IDistributedVersionControl* dvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea) { Q_ASSERT(dvcs); qCDebug(PLUGIN_APPWIZARD) << "DVCS system is used, just initializing DVCS"; const QUrl& dest = info.location; //TODO: check if we want to handle KDevelop project files (like now) or only SRC dir VcsJob* job = dvcs->init(dest); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not initialize DVCS repository"), scratchArea, dest); return false; } qCDebug(PLUGIN_APPWIZARD) << "Initializing DVCS repository:" << dest; job = dvcs->add({dest}, KDevelop::IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not add files to the DVCS repository"), scratchArea, dest); return false; } job = dvcs->commit(info.importCommitMessage, {dest}, KDevelop::IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded) { vcsError(i18n("Could not import project into %1.", dvcs->name()), scratchArea, dest, job ? job->errorString() : QString()); return false; } return true; // We're good } /*! Setup version control for a new project defined by @p info. Use @p scratchArea for temporary files */ bool initializeCVCS(ICentralizedVersionControl* cvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea) { Q_ASSERT(cvcs); qCDebug(PLUGIN_APPWIZARD) << "Importing" << info.sourceLocation << "to" << info.repository.repositoryServer(); VcsJob* job = cvcs->import( info.importCommitMessage, QUrl::fromLocalFile(scratchArea.path()), info.repository); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded ) { vcsError(i18n("Could not import project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer())); return false; } qCDebug(PLUGIN_APPWIZARD) << "Checking out"; job = cvcs->createWorkingCopy( info.repository, info.location, IBasicVersionControl::Recursive); if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded ) { vcsError(i18n("Could not checkout imported project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer())); return false; } return true; // initialization phase complete } QString generateIdentifier( const QString& appname ) { QString tmp = appname; QRegExp re("[^a-zA-Z0-9_]"); return tmp.replace(re, QStringLiteral("_")); } } // end anonymous namespace QString AppWizardPlugin::createProject(const ApplicationInfo& info) { QFileInfo templateInfo(info.appTemplate); if (!templateInfo.exists()) { qCWarning(PLUGIN_APPWIZARD) << "Project app template does not exist:" << info.appTemplate; return QString(); } QString templateName = templateInfo.baseName(); qCDebug(PLUGIN_APPWIZARD) << "Searching archive for template name:" << templateName; QString templateArchive; const QStringList filters = {templateName + QStringLiteral(".*")}; const QStringList matchesPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevappwizard/templates/"), QStandardPaths::LocateDirectory); for (const QString& matchesPath : matchesPaths) { const QStringList files = QDir(matchesPath).entryList(filters); if(!files.isEmpty()) { templateArchive = matchesPath + files.first(); } } if(templateArchive.isEmpty()) { qCWarning(PLUGIN_APPWIZARD) << "Template name does not exist in the template list"; return QString(); } qCDebug(PLUGIN_APPWIZARD) << "Using template archive:" << templateArchive; QUrl dest = info.location; //prepare variable substitution hash m_variables.clear(); m_variables[QStringLiteral("APPNAME")] = info.name; m_variables[QStringLiteral("APPNAMEUC")] = info.name.toUpper(); m_variables[QStringLiteral("APPNAMELC")] = info.name.toLower(); m_variables[QStringLiteral("APPNAMEID")] = generateIdentifier(info.name); m_variables[QStringLiteral("PROJECTDIR")] = dest.toLocalFile(); // backwards compatibility m_variables[QStringLiteral("dest")] = m_variables[QStringLiteral("PROJECTDIR")]; m_variables[QStringLiteral("PROJECTDIRNAME")] = dest.fileName(); m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = info.vcsPluginName; KArchive* arch = nullptr; if( templateArchive.endsWith(QLatin1String(".zip")) ) { arch = new KZip(templateArchive); } else { arch = new KTar(templateArchive, QStringLiteral("application/x-bzip")); } if (arch->open(QIODevice::ReadOnly)) { QTemporaryDir tmpdir; QString unpackDir = tmpdir.path(); //the default value for all Centralized VCS IPlugin* plugin = core()->pluginController()->loadPlugin( info.vcsPluginName ); if( info.vcsPluginName.isEmpty() || ( plugin && plugin->extension() ) ) { if( !QFileInfo::exists( dest.toLocalFile() ) ) { QDir::root().mkpath( dest.toLocalFile() ); } unpackDir = dest.toLocalFile(); //in DVCS we unpack template directly to the project's directory } else { QUrl url = KIO::upUrl(dest); if(!QFileInfo::exists(url.toLocalFile())) { QDir::root().mkpath(url.toLocalFile()); } } // estimate metadata files which should not be copied QStringList metaDataFileNames; // try by same name const KArchiveEntry *templateEntry = arch->directory()->entry(templateName + QLatin1String(".kdevtemplate")); // but could be different name, if e.g. downloaded, so make a guess if (!templateEntry || !templateEntry->isFile()) { const auto& entries = arch->directory()->entries(); for (const auto& entryName : entries) { if (entryName.endsWith(QLatin1String(".kdevtemplate"))) { templateEntry = arch->directory()->entry(entryName); break; } } } if (templateEntry && templateEntry->isFile()) { metaDataFileNames << templateEntry->name(); // check if a preview file is to be ignored const KArchiveFile *templateFile = static_cast(templateEntry); QTemporaryDir temporaryDir; templateFile->copyTo(temporaryDir.path()); KConfig config(temporaryDir.path() + QLatin1Char('/') + templateEntry->name()); KConfigGroup group(&config, "General"); if (group.hasKey("Icon")) { const KArchiveEntry* iconEntry = arch->directory()->entry(group.readEntry("Icon")); if (iconEntry && iconEntry->isFile()) { metaDataFileNames << iconEntry->name(); } } } if (!unpackArchive(arch->directory(), unpackDir, metaDataFileNames)) { QString errorMsg = i18n("Could not create new project"); vcsError(errorMsg, tmpdir, QUrl::fromLocalFile(unpackDir)); return QString(); } if( !info.vcsPluginName.isEmpty() ) { if (!plugin) { // Red Alert, serious program corruption. // This should never happen, the vcs dialog presented a list of vcs // systems and now the chosen system doesn't exist anymore?? tmpdir.remove(); return QString(); } IDistributedVersionControl* dvcs = toDVCS(plugin); ICentralizedVersionControl* cvcs = toCVCS(plugin); bool success = false; if (dvcs) { success = initializeDVCS(dvcs, info, tmpdir); } else if (cvcs) { success = initializeCVCS(cvcs, info, tmpdir); } else { if (KMessageBox::Continue == KMessageBox::warningContinueCancel(nullptr, QStringLiteral("Failed to initialize version control system, " "plugin is neither VCS nor DVCS."))) success = true; } if (!success) return QString(); } tmpdir.remove(); }else { qCDebug(PLUGIN_APPWIZARD) << "failed to open template archive"; return QString(); } QString projectFileName = QDir::cleanPath( dest.toLocalFile() + '/' + info.name + ".kdev4" ); // Loop through the new project directory and try to detect the first .kdev4 file. // If one is found this file will be used. So .kdev4 file can be stored in any subdirectory and the // project templates can be more complex. QDirIterator it(QDir::cleanPath( dest.toLocalFile()), QStringList() << QStringLiteral("*.kdev4"), QDir::NoFilter, QDirIterator::Subdirectories); if(it.hasNext() == true) { projectFileName = it.next(); } qCDebug(PLUGIN_APPWIZARD) << "Returning" << projectFileName << QFileInfo::exists( projectFileName ) ; const QFileInfo projectFileInfo(projectFileName); if (!projectFileInfo.exists()) { qCDebug(PLUGIN_APPWIZARD) << "creating .kdev4 file"; KSharedConfigPtr cfg = KSharedConfig::openConfig( projectFileName, KConfig::SimpleConfig ); KConfigGroup project = cfg->group( "Project" ); project.writeEntry( "Name", info.name ); QString manager = QStringLiteral("KDevGenericManager"); QDir d( dest.toLocalFile() ); const auto data = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); for (const KPluginMetaData& info : data) { QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); if (!filter.isEmpty()) { if (!d.entryList(filter).isEmpty()) { manager = info.pluginId(); break; } } } project.writeEntry( "Manager", manager ); project.sync(); cfg->sync(); KConfigGroup project2 = cfg->group( "Project" ); qCDebug(PLUGIN_APPWIZARD) << "kdev4 file contents:" << project2.readEntry("Name", "") << project2.readEntry("Manager", "" ); } // create developer .kde4 file const QString developerProjectFileName = projectFileInfo.canonicalPath() + QLatin1String("/.kdev4/") + projectFileInfo.fileName(); qCDebug(PLUGIN_APPWIZARD) << "creating developer .kdev4 file:" << developerProjectFileName; KSharedConfigPtr developerCfg = KSharedConfig::openConfig(developerProjectFileName, KConfig::SimpleConfig); KConfigGroup developerProjectGroup = developerCfg->group("Project"); developerProjectGroup.writeEntry("VersionControlSupport", info.vcsPluginName); developerProjectGroup.sync(); developerCfg->sync(); return projectFileName; } bool AppWizardPlugin::unpackArchive(const KArchiveDirectory* dir, const QString& dest, const QStringList& skipList) { qCDebug(PLUGIN_APPWIZARD) << "unpacking dir:" << dir->name() << "to" << dest; const QStringList entries = dir->entries(); qCDebug(PLUGIN_APPWIZARD) << "entries:" << entries.join(QLatin1Char(',')); //This extra tempdir is needed just for the files files have special names, //which may contain macros also files contain content with macros. So the //easiest way to extract the files from the archive and then rename them //and replace the macros is to use a tempdir and copy the file (and //replacing while copying). This also allows one to easily remove all files, //by just unlinking the tempdir QTemporaryDir tdir; bool ret = true; for (const auto& entryName : entries) { if (skipList.contains(entryName)) { continue; } const auto entry = dir->entry(entryName); if (entry->isDirectory()) { const KArchiveDirectory* subdir = static_cast(entry); QString newdest = dest + '/' + KMacroExpander::expandMacros(subdir->name(), m_variables); if( !QFileInfo::exists( newdest ) ) { QDir::root().mkdir( newdest ); } ret |= unpackArchive(subdir, newdest); } else if (entry->isFile()) { const KArchiveFile* file = static_cast(entry); file->copyTo(tdir.path()); QString destName = dest + '/' + file->name(); if (!copyFileAndExpandMacros(QDir::cleanPath(tdir.path()+'/'+file->name()), KMacroExpander::expandMacros(destName, m_variables))) { KMessageBox::sorry(nullptr, i18n("The file %1 cannot be created.", dest)); return false; } } } tdir.remove(); return ret; } bool AppWizardPlugin::copyFileAndExpandMacros(const QString &source, const QString &dest) { qCDebug(PLUGIN_APPWIZARD) << "copy:" << source << "to" << dest; QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(source); if( !mime.inherits(QStringLiteral("text/plain")) ) { KIO::CopyJob* job = KIO::copy( QUrl::fromUserInput(source), QUrl::fromUserInput(dest), KIO::HideProgressInfo ); if( !job->exec() ) { return false; } return true; } else { QFile inputFile(source); QFile outputFile(dest); if (inputFile.open(QFile::ReadOnly) && outputFile.open(QFile::WriteOnly)) { QTextStream input(&inputFile); input.setCodec(QTextCodec::codecForName("UTF-8")); QTextStream output(&outputFile); output.setCodec(QTextCodec::codecForName("UTF-8")); while(!input.atEnd()) { QString line = input.readLine(); output << KMacroExpander::expandMacros(line, m_variables) << "\n"; } #ifndef Q_OS_WIN // Preserve file mode... QT_STATBUF statBuf; QT_FSTAT(inputFile.handle(), &statBuf); // Unix only, won't work in Windows, maybe KIO::chmod could be used ::fchmod(outputFile.handle(), statBuf.st_mode); #endif return true; } else { inputFile.close(); outputFile.close(); return false; } } } KDevelop::ContextMenuExtension AppWizardPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { Q_UNUSED(parent); KDevelop::ContextMenuExtension ext; if ( context->type() != KDevelop::Context::ProjectItemContext || !static_cast(context)->items().isEmpty() ) { return ext; } ext.addAction(KDevelop::ContextMenuExtension::ProjectGroup, m_newFromTemplate); return ext; } ProjectTemplatesModel* AppWizardPlugin::model() { if(!m_templatesModel) m_templatesModel = new ProjectTemplatesModel(this); return m_templatesModel; } QAbstractItemModel* AppWizardPlugin::templatesModel() { return model(); } QString AppWizardPlugin::knsConfigurationFile() const { return QStringLiteral("kdevappwizard.knsrc"); } QStringList AppWizardPlugin::supportedMimeTypes() const { const QStringList types{ QStringLiteral("application/x-desktop"), QStringLiteral("application/x-bzip-compressed-tar"), QStringLiteral("application/zip"), }; return types; } QIcon AppWizardPlugin::icon() const { return QIcon::fromTheme(QStringLiteral("project-development-new-template")); } QString AppWizardPlugin::name() const { return i18n("Project Templates"); } void AppWizardPlugin::loadTemplate(const QString& fileName) { model()->loadTemplateFile(fileName); } void AppWizardPlugin::reload() { model()->refresh(); } #include "appwizardplugin.moc" diff --git a/plugins/bazaar/bazaarpluginmetadata.cpp b/plugins/bazaar/bazaarpluginmetadata.cpp index 51f2761b9a..229cc72285 100644 --- a/plugins/bazaar/bazaarpluginmetadata.cpp +++ b/plugins/bazaar/bazaarpluginmetadata.cpp @@ -1,35 +1,35 @@ /*************************************************************************** * Copyright 2014 Alex Richardson * * * * 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 "bazaarplugin.h" -#include +#include // This file only exists so that the tests can be built: // test_bazaar builds bazaar.cpp again but in a different directory. // This means that the kdevbazaar.json file is no longer found. // Since the JSON metadata is not needed in the test, we simply move // the K_PLUGIN_FACTORY_WITH_JSON to a separate file. // TODO: use object or static library? K_PLUGIN_FACTORY_WITH_JSON(KDevBazaarFactory, "kdevbazaar.json", registerPlugin();) #include "bazaarpluginmetadata.moc" diff --git a/plugins/clang/clangsettings/sessionsettings/sessionconfigskeleton.h b/plugins/clang/clangsettings/sessionsettings/sessionconfigskeleton.h index c1b6b17ded..9d91ef35e7 100644 --- a/plugins/clang/clangsettings/sessionsettings/sessionconfigskeleton.h +++ b/plugins/clang/clangsettings/sessionsettings/sessionconfigskeleton.h @@ -1,43 +1,43 @@ /* * 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 SESSIONCONFIGSKELETON_H #define SESSIONCONFIGSKELETON_H -#include - #include #include +#include + using namespace KDevelop; class SessionConfigSkeleton : public KConfigSkeleton { public: explicit SessionConfigSkeleton( const QString& ) : KConfigSkeleton( ICore::self()->activeSession()->config() ) { } }; #endif diff --git a/plugins/clang/codegen/codegenexport.h b/plugins/clang/codegen/codegenexport.h deleted file mode 100644 index 7bbbc5a866..0000000000 --- a/plugins/clang/codegen/codegenexport.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - This file is part of KDevelop - - Copyright 2014 Michael Ferris - - 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 CODEGENEXPORT_H -#define CODEGENEXPORT_H - -/* needed for KDE_EXPORT macros */ -#include - - -#ifndef KDEVCLANGCODEGEN_EXPORT -# ifdef MAKE_KDEV4CLANGCODEGEN_LIB -# define KDEVCLANGCODEGEN_EXPORT KDE_EXPORT -# else -# define KDEVCLANGCODEGEN_EXPORT KDE_IMPORT -# endif -#endif - -#endif diff --git a/plugins/clang/tests/test_assistants.cpp b/plugins/clang/tests/test_assistants.cpp index daca9483d7..3beccabbb7 100644 --- a/plugins/clang/tests/test_assistants.cpp +++ b/plugins/clang/tests/test_assistants.cpp @@ -1,800 +1,800 @@ /* 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 #include #include #include using namespace KDevelop; using namespace KTextEditor; QTEST_MAIN(TestAssistants) 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_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 = nullptr; } static QUrl createFile(const QString& fileContents, const 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,QStringLiteral(".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,QStringLiteral(".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(QLatin1String("\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), QStringLiteral("d"), QStringLiteral("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), QStringLiteral("u"), QStringLiteral("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), QStringLiteral("abcdefg"), QStringLiteral("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), QStringLiteral("cdef"), QStringLiteral("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), QStringLiteral("a"), QStringLiteral("ai")) << StateChange(Testbed::CppDoc, Range(0,13,0,13), QStringLiteral("b"), QStringLiteral("abi")) << StateChange(Testbed::CppDoc, Range(0,14,0,14), QStringLiteral("c"), QStringLiteral("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), QStringLiteral("c"), QStringLiteral("abcg")) << StateChange(Testbed::CppDoc, Range(0,15,0,15), QStringLiteral("d"), QStringLiteral("abcdg")) << StateChange(Testbed::CppDoc, Range(0,16,0,16), QStringLiteral("e"), QStringLiteral("abcdeg")) << StateChange(Testbed::CppDoc, Range(0,17,0,17), QStringLiteral("f"), QStringLiteral("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(QString(), 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(QString(), QStringLiteral("int foo(int i)\n { i = 0; return i; }")); testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,13), QStringLiteral("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), QString()); // there should be no assistant anymore QVERIFY(!assistant || assistant->actions().isEmpty()); } const QString SHOULD_ASSIST = QStringLiteral("SHOULD_ASSIST"); //An assistant will be visible const QString NO_ASSIST = QStringLiteral("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), QStringLiteral("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), QStringLiteral("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), QStringLiteral("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), QString(), 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), QStringLiteral("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), QStringLiteral("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), QString(), 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), QStringLiteral(", 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), QStringLiteral(", 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), QStringLiteral("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), QStringLiteral(", 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), QStringLiteral("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), QStringLiteral(", 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), QStringLiteral("char c"), SHOULD_ASSIST)) << "class Foo { Foo(char c); };" << "Foo::Foo(char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=365420 QTest::newRow("no_retval_on_ctor_while_editing_definition") << "class Foo {\nFoo(int a); \n};" << "Foo::Foo(int a)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0,13,0,14), QStringLiteral("b"), SHOULD_ASSIST)) << "class Foo {\nFoo(int b); \n};" << "Foo::Foo(int b)\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), QStringLiteral("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), QStringLiteral("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(); QTest::newRow("not a class type") << "void test();" << "" << "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(i18n("Forward declare as 'struct'")) || actionDescriptions.contains(i18n("Forward declare as 'class'")); QCOMPARE(hasForwardDecls, static_cast(actions & ForwardDecls)); } { auto fileName = testbed.includeFileName(); fileName.remove(0, fileName.lastIndexOf('/') + 1); const auto directive = QStringLiteral("#include \"%1\"").arg(fileName); const auto description = i18n("Insert \'%1\'", directive); 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, QStringLiteral("h")); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" + origImpl, QStringLiteral("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(QStringLiteral("foo")); QTest::newRow("globalfunction") << QStringLiteral("int foo()\n{\n int i = 0;\n return 0;\n}\n") << QString() << QStringLiteral("int foo();\n") << QStringLiteral("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("staticfunction") << QStringLiteral("static int foo()\n{\n int i = 0;\n return 0;\n}\n") << QString() << QStringLiteral("static int foo();\n") << QStringLiteral("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("funcsameline") << QStringLiteral("int foo() {\n int i = 0;\n return 0;\n}\n") << QString() << QStringLiteral("int foo();\n") << QStringLiteral("\nint foo() {\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("func-comment") << QStringLiteral("int foo()\n/* foobar */ {\n int i = 0;\n return 0;\n}\n") << QString() << QStringLiteral("int foo()\n/* foobar */;\n") << QStringLiteral("\nint foo() {\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("func-comment2") << QStringLiteral("int foo()\n/*asdf*/\n{\n int i = 0;\n return 0;\n}\n") << QString() << QStringLiteral("int foo()\n/*asdf*/;\n") << QStringLiteral("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; const QualifiedIdentifier aFooId(QStringLiteral("a::foo")); QTest::newRow("class-method") << QStringLiteral("class a {\n int foo(){\n return 0;\n }\n};\n") << QString() << QStringLiteral("class a {\n int foo();\n};\n") << QStringLiteral("\nint a::foo() {\n return 0;\n }\n") << aFooId; QTest::newRow("class-method-const") << QStringLiteral("class a {\n int foo() const\n {\n return 0;\n }\n};\n") << QString() << QStringLiteral("class a {\n int foo() const;\n};\n") << QStringLiteral("\nint a::foo() const\n {\n return 0;\n }\n") << aFooId; QTest::newRow("class-method-const-sameline") << QStringLiteral("class a {\n int foo() const{\n return 0;\n }\n};\n") << QString() << QStringLiteral("class a {\n int foo() const;\n};\n") << QStringLiteral("\nint a::foo() const {\n return 0;\n }\n") << aFooId; QTest::newRow("elaborated-type") << QStringLiteral("namespace NS{class C{};} class a {\nint foo(const NS::C c) const{\nreturn 0;\n}\n};\n") << QString() << QStringLiteral("namespace NS{class C{};} class a {\nint foo(const NS::C c) const;\n};\n") << QStringLiteral("\nint a::foo(const NS::C c) const {\nreturn 0;\n}\n") << aFooId; QTest::newRow("add-into-namespace") << QStringLiteral("namespace NS{class a {\nint foo() const {\nreturn 0;\n}\n};\n}") << QStringLiteral("namespace NS{\n}") << QStringLiteral("namespace NS{class a {\nint foo() const;\n};\n}") << QStringLiteral("namespace NS{\n\nint a::foo() const {\nreturn 0;\n}\n}") << QualifiedIdentifier(QStringLiteral("NS::a::foo")); QTest::newRow("class-template-parameter") << QStringLiteral(R"( namespace first { template class Test{}; namespace second { template class List; } class MoveIntoSource { public: void f(const second::List>*>& param){} };} )") << QString() << QStringLiteral(R"( namespace first { template class Test{}; namespace second { template class List; } class MoveIntoSource { public: void f(const second::List>*>& param); };} )") << QStringLiteral("namespace first {\nvoid MoveIntoSource::f(const first::second::List< const volatile first::Test< first::second::List< int* > >* >& param) {}}\n\n") << QualifiedIdentifier(QStringLiteral("first::MoveIntoSource::f")); QTest::newRow("move-unexposed-type") << QStringLiteral("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i){}") << QString() << QStringLiteral("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i);") << QStringLiteral("void move(std::string i) {}\n") << QualifiedIdentifier(QStringLiteral("move")); QTest::newRow("move-constructor") << QStringLiteral("class Class{Class(){}\n};") << QString() << QStringLiteral("class Class{Class();\n};") << QStringLiteral("Class::Class() {}\n") << QualifiedIdentifier(QStringLiteral("Class::Class")); } diff --git a/plugins/cmake/cmakecodecompletionmodel.cpp b/plugins/cmake/cmakecodecompletionmodel.cpp index 94fa86be72..a40ccd9fc4 100644 --- a/plugins/cmake/cmakecodecompletionmodel.cpp +++ b/plugins/cmake/cmakecodecompletionmodel.cpp @@ -1,315 +1,317 @@ /* 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 "cmakecodecompletionmodel.h" #include #include #include #include #include #include #include #include #include #include -#include -#include -#include -#include + #include #include "cmakeutils.h" #include "icmakedocumentation.h" +#include +#include +#include +#include + using namespace KTextEditor; using namespace KDevelop; QVector CMakeCodeCompletionModel::s_commands; CMakeCodeCompletionModel::CMakeCodeCompletionModel(QObject* parent) : CodeCompletionModel(parent) , m_inside(false) {} bool isFunction(const Declaration* decl) { return decl->abstractType().cast(); } bool isPathChar(const QChar& c) { return c.isLetterOrNumber() || c=='/' || c=='.'; } QString escapePath(QString path) { // see https://cmake.org/Wiki/CMake/Language_Syntax#Escapes static const QString toBeEscaped = QStringLiteral("\"()#$^"); for(const QChar &ch : toBeEscaped) { path.replace(ch, QLatin1Char('\\') + ch); } return path; } void CMakeCodeCompletionModel::completionInvoked(View* view, const Range& range, InvocationType invocationType) { beginResetModel(); if(s_commands.isEmpty()) { ICMakeDocumentation* cmakedoc=CMake::cmakeDocumentation(); if(cmakedoc) s_commands=cmakedoc->names(ICMakeDocumentation::Command); } Q_UNUSED(invocationType); m_declarations.clear(); DUChainReadLocker lock(DUChain::lock()); KTextEditor::Document* d=view->document(); TopDUContext* ctx = DUChain::self()->chainForDocument( d->url() ); QString line=d->line(range.end().line()); // m_inside=line.lastIndexOf('(', range.end().column())>=0; m_inside=line.lastIndexOf('(', range.end().column()-line.size()-1)>=0; for(int l=range.end().line(); l>=0 && !m_inside; --l) { QString cline=d->line(l); QString line=cline.left(cline.indexOf('#')); int close=line.lastIndexOf(')'), open=line.indexOf('('); if(close>=0 && open>=0) { m_inside=open>close; break; } else if(open>=0) { m_inside=true; break; } else if(close>=0) { m_inside=false; break; } } int numRows = 0; if(m_inside) { Cursor start=range.start(); for(; isPathChar(d->characterAt(start)); start-=Cursor(0,1)) {} start+=Cursor(0,1); QString tocomplete=d->text(Range(start, range.end()-Cursor(0,1))); int lastdir=tocomplete.lastIndexOf('/'); QString path = KIO::upUrl(QUrl(d->url())).adjusted(QUrl::StripTrailingSlash).toLocalFile()+'/'; QString basePath; if(lastdir>=0) { basePath=tocomplete.mid(0, lastdir); path+=basePath; } QDir dir(path); const QFileInfoList paths = dir.entryInfoList(QStringList() << tocomplete.mid(lastdir+1)+'*', QDir::AllEntries | QDir::NoDotAndDotDot); m_paths.clear(); m_paths.reserve(paths.size()); for (const QFileInfo& f : paths) { QString currentPath = f.fileName(); if(f.isDir()) currentPath+='/'; m_paths += currentPath; } numRows += m_paths.count(); } else numRows += s_commands.count(); if(ctx) { const auto list = ctx->allDeclarations( ctx->transformToLocalRevision(KTextEditor::Cursor(range.start())), ctx ); for (const auto& pair : list) { bool func=isFunction(pair.first); if((func && !m_inside) || (!func && m_inside)) m_declarations.append(pair.first); } numRows+=m_declarations.count(); } setRowCount(numRows); endResetModel(); } CMakeCodeCompletionModel::Type CMakeCodeCompletionModel::indexType(int row) const { if(m_inside) { if(row < m_declarations.count()) { KDevelop::DUChainReadLocker lock; Declaration* dec = m_declarations.at(row).declaration(); if (dec && dec->type()) return Target; else return Variable; } else return Path; } else { if(rowidentifier().toString() : i18n("INVALID"); } } } else if(role==Qt::DisplayRole && index.column()==CodeCompletionModel::Prefix) { switch(type) { case Command: return i18n("Command"); case Variable: return i18n("Variable"); case Macro: return i18n("Macro"); case Path: return i18n("Path"); case Target: return i18n("Target"); } } else if(role==Qt::DecorationRole && index.column()==CodeCompletionModel::Icon) { switch(type) { case Command: return QIcon::fromTheme(QStringLiteral("code-block")); case Variable: return QIcon::fromTheme(QStringLiteral("code-variable")); case Macro: return QIcon::fromTheme(QStringLiteral("code-function")); case Target: return QIcon::fromTheme(QStringLiteral("system-run")); case Path: { QUrl url = QUrl::fromUserInput(m_paths[index.row()-m_declarations.size()]); QString iconName; if (url.isLocalFile()) { // don't read contents even if it is a local file iconName = QMimeDatabase().mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchExtension).iconName(); } else { // remote always only looks at the extension iconName = QMimeDatabase().mimeTypeForUrl(url).iconName(); } return QIcon::fromTheme(iconName); } } } else if(role==Qt::DisplayRole && index.column()==CodeCompletionModel::Arguments) { switch(type) { case Variable: case Command: case Path: case Target: break; case Macro: { DUChainReadLocker lock(DUChain::lock()); int pos=index.row(); FunctionType::Ptr func; if(m_declarations[pos].data()) func = m_declarations[pos].data()->abstractType().cast(); if(!func) return QVariant(); QStringList args; const auto arguments = func->arguments(); args.reserve(arguments.size()); for (const AbstractType::Ptr& t : arguments) { DelayedType::Ptr delay = t.cast(); args.append(delay ? delay->identifier().toString() : i18n("wrong")); } return QString('('+args.join(QStringLiteral(", "))+')'); } } } return QVariant(); } void CMakeCodeCompletionModel::executeCompletionItem(View* view, const Range& word, const QModelIndex& idx) const { Document* document = view->document(); const int row = idx.row(); switch(indexType(row)) { case Path: { Range r=word; for(QChar c=document->characterAt(r.end()); c.isLetterOrNumber() || c=='.'; c=document->characterAt(r.end())) { r.setEnd(KTextEditor::Cursor(r.end().line(), r.end().column()+1)); } QString path = data(index(row, Name, QModelIndex())).toString(); document->replaceText(r, escapePath(path)); } break; case Macro: case Command: { QString code=data(index(row, Name, QModelIndex())).toString(); if(!document->line(word.start().line()).contains('(')) code.append('('); document->replaceText(word, code); } break; case Variable: { Range r=word, prefix(word.start()-Cursor(0,2), word.start()); QString bef=document->text(prefix); if(r.start().column()>=2 && bef==QLatin1String("${")) r.setStart(KTextEditor::Cursor(r.start().line(), r.start().column()-2)); QString code="${"+data(index(row, Name, QModelIndex())).toString(); if(document->characterAt(word.end())!='}') code+='}'; document->replaceText(r, code); } break; case Target: document->replaceText(word, data(index(row, Name, QModelIndex())).toString()); break; } } diff --git a/plugins/cmake/cmakecodecompletionmodel.h b/plugins/cmake/cmakecodecompletionmodel.h index 8260f5889a..e013133b07 100644 --- a/plugins/cmake/cmakecodecompletionmodel.h +++ b/plugins/cmake/cmakecodecompletionmodel.h @@ -1,49 +1,51 @@ /* 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. */ #ifndef CMAKECODECOMPLETION_H #define CMAKECODECOMPLETION_H -#include #include -#include #include +#include + +#include + class CMakeDocumentation; namespace KTextEditor { class Document; class Range; } class CMakeCodeCompletionModel : public KTextEditor::CodeCompletionModel { public: explicit CMakeCodeCompletionModel(QObject *parent); void completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType) override; QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const override; void executeCompletionItem(KTextEditor::View* view, const KTextEditor::Range& word, const QModelIndex& index) const override; private: enum Type { Command, Variable, Macro, Path, Target }; Type indexType(int row) const; static QVector s_commands; QList< KDevelop::IndexedDeclaration > m_declarations; bool m_inside; QStringList m_paths; }; #endif diff --git a/plugins/cmake/cmakemanager.cpp b/plugins/cmake/cmakemanager.cpp index 3e07ab9de9..4e6821142c 100644 --- a/plugins/cmake/cmakemanager.cpp +++ b/plugins/cmake/cmakemanager.cpp @@ -1,987 +1,987 @@ /* 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 "cmakecodecompletionmodel.h" #include "cmakenavigationwidget.h" #include "icmakedocumentation.h" #include "cmakemodelitems.h" #include "testing/ctestutils.h" #include "cmakeserverimportjob.h" #include "cmakeserver.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 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( QStringLiteral("kdevcmakemanager"), parent ) , m_filter( new ProjectFilterManager( this ) ) { if (CMake::findExecutable().isEmpty()) { setErrorDescription(i18n("Unable to find a CMake executable. Is one 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(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &CMakeManager::reloadProjects); 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()].compilationData.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); } class ChooseCMakeInterfaceJob : public ExecuteCompositeJob { Q_OBJECT public: ChooseCMakeInterfaceJob(IProject* project, CMakeManager* manager) : ExecuteCompositeJob(manager, {}) , project(project) , manager(manager) { } void start() override { server = new CMakeServer(project); connect(server, &CMakeServer::connected, this, &ChooseCMakeInterfaceJob::successfulConnection); connect(server, &CMakeServer::finished, this, &ChooseCMakeInterfaceJob::failedConnection); } private: void successfulConnection() { auto job = new CMakeServerImportJob(project, server, this); connect(job, &CMakeServerImportJob::result, this, [this, job](){ if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } void failedConnection(int code) { Q_ASSERT(code > 0); Q_ASSERT(!server->isServerAvailable()); server->deleteLater(); server = nullptr; // parse the JSON file CMakeImportJsonJob* job = new CMakeImportJsonJob(project, this); // 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"; addSubjob(manager->builder()->configure(project)); } connect(job, &CMakeImportJsonJob::result, this, [this, job]() { if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } CMakeServer* server = nullptr; IProject* const project; CMakeManager* const manager; }; KJob* CMakeManager::createImportJob(ProjectFolderItem* item) { auto project = item->project(); auto job = new ChooseCMakeInterfaceJob(project, this); connect(job, &KJob::result, this, [this, job, project](){ if (job->error() != 0) { qCWarning(CMAKE) << "couldn't load project successfully" << project->name(); m_projects.remove(project); } }); const QList jobs = { job, KDevelop::AbstractFileManagerPlugin::createImportJob(item) // generate the file system listing }; 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 auto & data = m_projects[item->project()].compilationData; 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; } QString CMakeManager::extraArguments(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).compileFlags; } KDevelop::IProjectBuilder * CMakeManager::builder() const { IPlugin* i = core()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("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 ); if (folder == project->projectItem()) { connect(job, &KJob::finished, this, [project](KJob* job) { if (job->error()) return; KDevelop::ICore::self()->projectController()->reparseProject(project, true); }); } return true; } static void populateTargets(ProjectFolderItem* folder, const QHash>& targets) { 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") }; QList dirTargets = kFilter>(targets[folder->path()], [](const CMakeTarget& target) -> bool { return target.type != CMakeTarget::Custom || (!target.name.endsWith(QLatin1String("_automoc")) && !target.name.endsWith(QLatin1String("_autogen")) && !standardTargets.contains(target.name) && !target.name.startsWith(QLatin1String("install/")) ); }); const auto tl = folder->targetList(); for (ProjectTargetItem* item : tl) { const auto idx = kIndexOf(dirTargets, [item](const CMakeTarget& target) { return target.name == item->text(); }); if (idx < 0) { delete item; } else { dirTargets.removeAt(idx); } } foreach (const auto& target, dirTargets) { switch(target.type) { case CMakeTarget::Executable: new CMakeTargetItem(folder, target.name, target.artifacts.value(0)); break; case CMakeTarget::Library: new ProjectLibraryTargetItem(folder->project(), target.name, folder); break; case CMakeTarget::Custom: new ProjectTargetItem(folder->project(), target.name, folder); break; } } foreach (ProjectFolderItem* children, folder->folderList()) { populateTargets(children, targets); } } void CMakeManager::integrateData(const CMakeProjectData &data, KDevelop::IProject* project) { if (data.m_server) { connect(data.m_server.data(), &CMakeServer::response, project, [this, project](const QJsonObject& response) { serverResponse(project, response); }); } else { connect(data.watcher.data(), &QFileSystemWatcher::fileChanged, this, &CMakeManager::dirtyFile); connect(data.watcher.data(), &QFileSystemWatcher::directoryChanged, this, &CMakeManager::dirtyFile); } m_projects[project] = data; populateTargets(project->projectItem(), data.targets); CTestUtils::createTestSuites(data.m_testSuites, data.targets, project); } void CMakeManager::serverResponse(KDevelop::IProject* project, const QJsonObject& response) { if (response[QStringLiteral("type")] == QLatin1String("signal")) { if (response[QStringLiteral("name")] == QLatin1String("dirty")) { m_projects[project].m_server->configure({}); } else qCDebug(CMAKE) << "unhandled signal response..." << project << response; } else if (response[QStringLiteral("type")] == QLatin1String("reply")) { const auto inReplyTo = response[QStringLiteral("inReplyTo")]; if (inReplyTo == QLatin1String("configure")) { m_projects[project].m_server->compute(); } else if (inReplyTo == QLatin1String("compute")) { m_projects[project].m_server->codemodel(); } else if(inReplyTo == QLatin1String("codemodel")) { auto &data = m_projects[project]; CMakeServerImportJob::processCodeModel(response, data); populateTargets(project->projectItem(), data.targets); } else { qCDebug(CMAKE) << "unhandled reply response..." << project << response; } } else { qCDebug(CMAKE) << "unhandled response..." << project << response; } } // 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=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=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 { qCWarning(CMAKE) << "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; } void CMakeManager::reloadProjects() { const auto& projects = m_projects.keys(); for (IProject* project : projects) { CMake::checkForNeedingConfigure(project); reload(project->projectItem()); } } #include "cmakemanager.moc" diff --git a/plugins/cmake/cmakeutils.cpp b/plugins/cmake/cmakeutils.cpp index 4626bea6e4..75319dbf66 100644 --- a/plugins/cmake/cmakeutils.cpp +++ b/plugins/cmake/cmakeutils.cpp @@ -1,731 +1,731 @@ /* KDevelop CMake Support * * 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 "cmakeutils.h" #include "cmakeprojectdata.h" #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include #include "icmakedocumentation.h" #include "cmakebuilddirchooser.h" #include "settings/cmakecachemodel.h" #include "debug.h" #include "cmakebuilderconfig.h" #include #include "parser/cmakelistsparser.h" using namespace KDevelop; namespace Config { namespace Old { static const QString currentBuildDirKey = QStringLiteral("CurrentBuildDir"); static const QString oldcmakeExecutableKey = QStringLiteral("CMake Binary"); // Todo: Remove at some point static const QString currentBuildTypeKey = QStringLiteral("CurrentBuildType"); static const QString currentInstallDirKey = QStringLiteral("CurrentInstallDir"); static const QString currentEnvironmentKey = QStringLiteral("CurrentEnvironment"); static const QString currentExtraArgumentsKey = QStringLiteral("Extra Arguments"); static const QString currentCMakeExecutableKey = QStringLiteral("Current CMake Binary"); static const QString projectRootRelativeKey = QStringLiteral("ProjectRootRelative"); static const QString projectBuildDirs = QStringLiteral("BuildDirs"); } static const QString buildDirIndexKey_ = QStringLiteral("Current Build Directory Index"); static const QString buildDirOverrideIndexKey = QStringLiteral("Temporary Build Directory Index"); static const QString buildDirCountKey = QStringLiteral("Build Directory Count"); //the used builddir will change for every runtime static QString buildDirIndexKey() { const QString currentRuntime = ICore::self()->runtimeController()->currentRuntime()->name(); return buildDirIndexKey_ + '-' + currentRuntime; } namespace Specific { static const QString buildDirPathKey = QStringLiteral("Build Directory Path"); // TODO: migrate to more generic & consistent key term "CMake Executable" // Support the old "CMake Binary" key too for backwards compatibility during // a reasonable transition period. Both keys are saved at least until 5.2.0 // is released. Import support for the old key will need to remain for a // considably longer period, ideally. static const QString cmakeBinaryKey = QStringLiteral("CMake Binary"); static const QString cmakeExecutableKey = QStringLiteral("CMake Executable"); static const QString cmakeBuildTypeKey = QStringLiteral("Build Type"); static const QString cmakeInstallDirKey = QStringLiteral("Install Directory"); static const QString cmakeEnvironmentKey = QStringLiteral("Environment Profile"); static const QString cmakeArgumentsKey = QStringLiteral("Extra Arguments"); static const QString buildDirRuntime = QStringLiteral("Runtime"); } static const QString groupNameBuildDir = QStringLiteral("CMake Build Directory %1"); static const QString groupName = QStringLiteral("CMake"); } // namespace Config namespace { KConfigGroup baseGroup( KDevelop::IProject* project ) { if (!project) return KConfigGroup(); return project->projectConfiguration()->group( Config::groupName ); } KConfigGroup buildDirGroup( KDevelop::IProject* project, int buildDirIndex ) { return baseGroup(project).group( Config::groupNameBuildDir.arg(buildDirIndex) ); } bool buildDirGroupExists( KDevelop::IProject* project, int buildDirIndex ) { return baseGroup(project).hasGroup( Config::groupNameBuildDir.arg(buildDirIndex) ); } QString readBuildDirParameter( KDevelop::IProject* project, const QString& key, const QString& aDefault, int buildDirectory ) { const int buildDirIndex = buildDirectory<0 ? CMake::currentBuildDirIndex(project) : buildDirectory; if (buildDirIndex >= 0) return buildDirGroup( project, buildDirIndex ).readEntry( key, aDefault ); else return aDefault; } void writeBuildDirParameter( KDevelop::IProject* project, const QString& key, const QString& value ) { int buildDirIndex = CMake::currentBuildDirIndex(project); if (buildDirIndex >= 0) { KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex ); buildDirGrp.writeEntry( key, value ); } else { qCWarning(CMAKE) << "cannot write key" << key << "(" << value << ")" << "when no builddir is set!"; } } void writeProjectBaseParameter( KDevelop::IProject* project, const QString& key, const QString& value ) { KConfigGroup baseGrp = baseGroup(project); baseGrp.writeEntry( key, value ); } void setBuildDirRuntime( KDevelop::IProject* project, const QString& name) { writeBuildDirParameter(project, Config::Specific::buildDirRuntime, name); } QString buildDirRuntime( KDevelop::IProject* project, int builddir) { return readBuildDirParameter(project, Config::Specific::buildDirRuntime, QString(), builddir); } } // namespace namespace CMake { KDevelop::Path::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs) { const KDevelop::Path buildDir(CMake::currentBuildDir(project)); const KDevelop::Path installDir(CMake::currentInstallDir(project)); KDevelop::Path::List newList; newList.reserve(dirs.size()); for (const QString& s : dirs) { KDevelop::Path dir; if(s.startsWith(QLatin1String("#[bin_dir]"))) { dir = KDevelop::Path(buildDir, s); } else if(s.startsWith(QLatin1String("#[install_dir]"))) { dir = KDevelop::Path(installDir, s); } else { dir = KDevelop::Path(s); } // qCDebug(CMAKE) << "resolved" << s << "to" << d; if (!newList.contains(dir)) { newList.append(dir); } } return newList; } ///NOTE: when you change this, update @c defaultConfigure in cmakemanagertest.cpp bool checkForNeedingConfigure( KDevelop::IProject* project ) { const QString currentRuntime = ICore::self()->runtimeController()->currentRuntime()->name(); const KDevelop::Path builddir = currentBuildDir(project); const bool isValid = (buildDirRuntime(project, -1) == currentRuntime || buildDirRuntime(project, -1).isEmpty()) && builddir.isValid(); if( !isValid ) { CMakeBuildDirChooser bd; bd.setProject( project ); const auto builddirs = CMake::allBuildDirs(project); bd.setAlreadyUsed( builddirs ); bd.setShowAvailableBuildDirs(!builddirs.isEmpty()); bd.setCMakeExecutable(currentCMakeExecutable(project)); if( !bd.exec() ) { return false; } if (bd.reuseBuilddir()) { CMake::setCurrentBuildDirIndex( project, bd.alreadyUsedIndex() ); } else { QString newbuilddir = bd.buildFolder().toLocalFile(); int addedBuildDirIndex = buildDirCount( project ); // old count is the new index // 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 " << bd.buildFolder(); qCDebug(CMAKE) << "adding to cmake config: installdir " << bd.installPrefix(); qCDebug(CMAKE) << "adding to cmake config: extra args" << bd.extraArguments(); qCDebug(CMAKE) << "adding to cmake config: build type " << bd.buildType(); qCDebug(CMAKE) << "adding to cmake config: cmake executable " << bd.cmakeExecutable(); qCDebug(CMAKE) << "adding to cmake config: environment "; CMake::setBuildDirCount( project, addedBuildDirIndex + 1 ); CMake::setCurrentBuildDirIndex( project, addedBuildDirIndex ); CMake::setCurrentBuildDir( project, bd.buildFolder() ); CMake::setCurrentInstallDir( project, bd.installPrefix() ); CMake::setCurrentExtraArguments( project, bd.extraArguments() ); CMake::setCurrentBuildType( project, bd.buildType() ); CMake::setCurrentCMakeExecutable(project, bd.cmakeExecutable()); CMake::setCurrentEnvironment( project, QString() ); } setBuildDirRuntime( project, currentRuntime ); return true; } else if( !QFile::exists( KDevelop::Path(builddir, QStringLiteral("CMakeCache.txt")).toLocalFile() ) || //TODO: maybe we could use the builder for that? !(QFile::exists( KDevelop::Path(builddir, QStringLiteral("Makefile")).toLocalFile() ) || QFile::exists( KDevelop::Path(builddir, QStringLiteral("build.ninja")).toLocalFile() ) ) ) { // User entered information already, but cmake hasn't actually been run yet. setBuildDirRuntime( project, currentRuntime ); return true; } setBuildDirRuntime( project, currentRuntime ); return false; } QHash enumerateTargets(const KDevelop::Path& targetsFilePath, const QString& sourceDir, const KDevelop::Path &buildDir) { const QString buildPath = buildDir.toLocalFile(); QHash targets; QFile targetsFile(targetsFilePath.toLocalFile()); if (!targetsFile.open(QIODevice::ReadOnly)) { qCDebug(CMAKE) << "Couldn't find the Targets file in" << targetsFile.fileName(); } QTextStream targetsFileStream(&targetsFile); const QRegularExpression rx(QStringLiteral("^(.*)/CMakeFiles/(.*).dir$")); while (!targetsFileStream.atEnd()) { const QString line = targetsFileStream.readLine(); auto match = rx.match(line); if (!match.isValid()) qCDebug(CMAKE) << "invalid match for" << line; const QString sourcePath = match.captured(1).replace(buildPath, sourceDir); targets[KDevelop::Path(sourcePath)].append(match.captured(2)); } return targets; } KDevelop::Path projectRoot(KDevelop::IProject* project) { if (!project) { return {}; } return project->path().cd(CMake::projectRootRelative(project)); } KDevelop::Path currentBuildDir( KDevelop::IProject* project, int builddir ) { return KDevelop::Path(readBuildDirParameter( project, Config::Specific::buildDirPathKey, QString(), builddir )); } KDevelop::Path commandsFile(KDevelop::IProject* project) { auto currentBuildDir = CMake::currentBuildDir(project); if (currentBuildDir.isEmpty()) { return {}; } return KDevelop::Path(currentBuildDir, QStringLiteral("compile_commands.json")); } KDevelop::Path targetDirectoriesFile(KDevelop::IProject* project) { auto currentBuildDir = CMake::currentBuildDir(project); if (currentBuildDir.isEmpty()) { return {}; } return KDevelop::Path(currentBuildDir, QStringLiteral("CMakeFiles/TargetDirectories.txt")); } QString currentBuildType( KDevelop::IProject* project, int builddir ) { return readBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, QStringLiteral("Release"), builddir ); } QString findExecutable() { auto cmake = QStandardPaths::findExecutable(QStringLiteral("cmake")); #ifdef Q_OS_WIN if (cmake.isEmpty()) cmake = QStandardPaths::findExecutable("cmake",{ "C:\\Program Files (x86)\\CMake\\bin", "C:\\Program Files\\CMake\\bin", "C:\\Program Files (x86)\\CMake 2.8\\bin", "C:\\Program Files\\CMake 2.8\\bin"}); #endif return cmake; } KDevelop::Path currentCMakeExecutable(KDevelop::IProject* project, int builddir) { auto defaultCMakeExecutable = CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile(); if (!QFileInfo::exists(ICore::self()->runtimeController()->currentRuntime()->pathInHost(KDevelop::Path(defaultCMakeExecutable)).toLocalFile())) defaultCMakeExecutable = CMake::findExecutable(); if (project) { // check for "CMake Executable" but for now also "CMake Binary", falling back to the default. auto projectCMakeExecutable = readBuildDirParameter( project, Config::Specific::cmakeExecutableKey, readBuildDirParameter( project, Config::Specific::cmakeBinaryKey, defaultCMakeExecutable, builddir), builddir ); if (projectCMakeExecutable != defaultCMakeExecutable) { QFileInfo info(projectCMakeExecutable); if (!info.isExecutable()) { projectCMakeExecutable = defaultCMakeExecutable; } } return KDevelop::Path(projectCMakeExecutable); } return KDevelop::Path(defaultCMakeExecutable); } KDevelop::Path currentInstallDir( KDevelop::IProject* project, int builddir ) { const QString defaultInstallDir = #ifdef Q_OS_WIN QStringLiteral("C:\\Program Files"); #else QStringLiteral("/usr/local"); #endif return KDevelop::Path(readBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, defaultInstallDir, builddir )); } QString projectRootRelative( KDevelop::IProject* project ) { return baseGroup(project).readEntry( Config::Old::projectRootRelativeKey, "." ); } bool hasProjectRootRelative(KDevelop::IProject* project) { return baseGroup(project).hasKey( Config::Old::projectRootRelativeKey ); } QString currentExtraArguments( KDevelop::IProject* project, int builddir ) { return readBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, QString(), builddir ); } void setCurrentInstallDir( KDevelop::IProject* project, const KDevelop::Path& path ) { writeBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, path.toLocalFile() ); } void setCurrentBuildType( KDevelop::IProject* project, const QString& type ) { writeBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, type ); } void setCurrentCMakeExecutable(KDevelop::IProject* project, const KDevelop::Path& path) { // maintain compatibility with older versions for now writeBuildDirParameter(project, Config::Specific::cmakeBinaryKey, path.toLocalFile()); writeBuildDirParameter(project, Config::Specific::cmakeExecutableKey, path.toLocalFile()); } void setCurrentBuildDir( KDevelop::IProject* project, const KDevelop::Path& path ) { writeBuildDirParameter( project, Config::Specific::buildDirPathKey, path.toLocalFile() ); } void setProjectRootRelative( KDevelop::IProject* project, const QString& relative) { writeProjectBaseParameter( project, Config::Old::projectRootRelativeKey, relative ); } void setCurrentExtraArguments( KDevelop::IProject* project, const QString& string) { writeBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, string ); } QString currentEnvironment(KDevelop::IProject* project, int builddir) { return readBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, QString(), builddir ); } int currentBuildDirIndex( KDevelop::IProject* project ) { KConfigGroup baseGrp = baseGroup(project); if ( baseGrp.hasKey( Config::buildDirOverrideIndexKey ) ) return baseGrp.readEntry( Config::buildDirOverrideIndexKey, -1 ); else if (baseGrp.hasKey(Config::buildDirIndexKey())) return baseGrp.readEntry( Config::buildDirIndexKey(), -1 ); else return baseGrp.readEntry( Config::buildDirIndexKey_, -1 ); // backwards compatibility } void setCurrentBuildDirIndex( KDevelop::IProject* project, int buildDirIndex ) { writeProjectBaseParameter( project, Config::buildDirIndexKey(), QString::number (buildDirIndex) ); } void setCurrentEnvironment( KDevelop::IProject* project, const QString& environment ) { writeBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, environment ); } void initBuildDirConfig( KDevelop::IProject* project ) { int buildDirIndex = currentBuildDirIndex( project ); if (buildDirCount(project) <= buildDirIndex ) setBuildDirCount( project, buildDirIndex + 1 ); } int buildDirCount( KDevelop::IProject* project ) { return baseGroup(project).readEntry( Config::buildDirCountKey, 0 ); } void setBuildDirCount( KDevelop::IProject* project, int count ) { writeProjectBaseParameter( project, Config::buildDirCountKey, QString::number(count) ); } void removeBuildDirConfig( KDevelop::IProject* project ) { int buildDirIndex = currentBuildDirIndex( project ); if ( !buildDirGroupExists( project, buildDirIndex ) ) { qCWarning(CMAKE) << "build directory config" << buildDirIndex << "to be removed but does not exist"; return; } int bdCount = buildDirCount(project); setBuildDirCount( project, bdCount - 1 ); removeOverrideBuildDirIndex( project ); setCurrentBuildDirIndex( project, -1 ); // move (rename) the upper config groups to keep the numbering // if there's nothing to move, just delete the group physically if (buildDirIndex + 1 == bdCount) buildDirGroup( project, buildDirIndex ).deleteGroup(); else for (int i = buildDirIndex + 1; i < bdCount; ++i) { KConfigGroup src = buildDirGroup( project, i ); KConfigGroup dest = buildDirGroup( project, i - 1 ); dest.deleteGroup(); src.copyTo(&dest); src.deleteGroup(); } } QHash readCacheValues(const KDevelop::Path& cmakeCachePath, QSet variables) { QHash ret; QFile file(cmakeCachePath.toLocalFile()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qCWarning(CMAKE) << "couldn't open CMakeCache.txt" << cmakeCachePath; return ret; } QTextStream in(&file); while (!in.atEnd() && !variables.isEmpty()) { QString line = in.readLine().trimmed(); if(!line.isEmpty() && line[0].isLetter()) { CacheLine c; c.readLine(line); if(!c.isCorrect()) continue; if (variables.remove(c.name())) { ret[c.name()] = c.value(); } } } return ret; } void updateConfig( KDevelop::IProject* project, int buildDirIndex) { if (buildDirIndex < 0) return; KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex ); const KDevelop::Path builddir(buildDirGrp.readEntry( Config::Specific::buildDirPathKey, QString() )); const KDevelop::Path cacheFilePath( builddir, QStringLiteral("CMakeCache.txt")); const QMap keys = { { QStringLiteral("CMAKE_COMMAND"), Config::Specific::cmakeExecutableKey }, { QStringLiteral("CMAKE_INSTALL_PREFIX"), Config::Specific::cmakeInstallDirKey }, { QStringLiteral("CMAKE_BUILD_TYPE"), Config::Specific::cmakeBuildTypeKey } }; const QHash cacheValues = readCacheValues(cacheFilePath, keys.keys().toSet()); for(auto it = cacheValues.constBegin(), itEnd = cacheValues.constEnd(); it!=itEnd; ++it) { const QString key = keys.value(it.key()); Q_ASSERT(!key.isEmpty()); // Use cache only when the config value is not set. Without this check we will always // overwrite values provided by the user in config dialog. if (buildDirGrp.readEntry(key).isEmpty() && !it.value().isEmpty()) { buildDirGrp.writeEntry( key, it.value() ); } } } void attemptMigrate( KDevelop::IProject* project ) { if ( !baseGroup(project).hasKey( Config::Old::projectBuildDirs ) ) { qCDebug(CMAKE) << "CMake settings migration: already done, exiting"; return; } KConfigGroup baseGrp = baseGroup(project); KDevelop::Path buildDir( baseGrp.readEntry( Config::Old::currentBuildDirKey, QString() ) ); int buildDirIndex = -1; const QStringList existingBuildDirs = baseGrp.readEntry( Config::Old::projectBuildDirs, QStringList() ); { // also, find current build directory in this list (we need an index, not path) QString currentBuildDirCanonicalPath = QDir( buildDir.toLocalFile() ).canonicalPath(); for( int i = 0; i < existingBuildDirs.count(); ++i ) { const QString& nextBuildDir = existingBuildDirs.at(i); if( QDir(nextBuildDir).canonicalPath() == currentBuildDirCanonicalPath ) { buildDirIndex = i; } } } int buildDirsCount = existingBuildDirs.count(); qCDebug(CMAKE) << "CMake settings migration: existing build directories" << existingBuildDirs; qCDebug(CMAKE) << "CMake settings migration: build directory count" << buildDirsCount; qCDebug(CMAKE) << "CMake settings migration: current build directory" << buildDir << "(index" << buildDirIndex << ")"; baseGrp.writeEntry( Config::buildDirCountKey, buildDirsCount ); baseGrp.writeEntry( Config::buildDirIndexKey(), buildDirIndex ); for (int i = 0; i < buildDirsCount; ++i) { qCDebug(CMAKE) << "CMake settings migration: writing group" << i << ": path" << existingBuildDirs.at(i); KConfigGroup buildDirGrp = buildDirGroup( project, i ); buildDirGrp.writeEntry( Config::Specific::buildDirPathKey, existingBuildDirs.at(i) ); } baseGrp.deleteEntry( Config::Old::currentBuildDirKey ); baseGrp.deleteEntry( Config::Old::currentCMakeExecutableKey ); baseGrp.deleteEntry( Config::Old::currentBuildTypeKey ); baseGrp.deleteEntry( Config::Old::currentInstallDirKey ); baseGrp.deleteEntry( Config::Old::currentEnvironmentKey ); baseGrp.deleteEntry( Config::Old::currentExtraArgumentsKey ); baseGrp.deleteEntry( Config::Old::projectBuildDirs ); } void setOverrideBuildDirIndex( KDevelop::IProject* project, int overrideBuildDirIndex ) { writeProjectBaseParameter( project, Config::buildDirOverrideIndexKey, QString::number(overrideBuildDirIndex) ); } void removeOverrideBuildDirIndex( KDevelop::IProject* project, bool writeToMainIndex ) { KConfigGroup baseGrp = baseGroup(project); if( !baseGrp.hasKey(Config::buildDirOverrideIndexKey) ) return; if( writeToMainIndex ) baseGrp.writeEntry( Config::buildDirIndexKey(), baseGrp.readEntry(Config::buildDirOverrideIndexKey) ); baseGrp.deleteEntry(Config::buildDirOverrideIndexKey); } ICMakeDocumentation* cmakeDocumentation() { return KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.ICMakeDocumentation")); } QStringList allBuildDirs(KDevelop::IProject* project) { QStringList result; int bdCount = buildDirCount(project); result.reserve(bdCount); for (int i = 0; i < bdCount; ++i) result += buildDirGroup( project, i ).readEntry( Config::Specific::buildDirPathKey ); return result; } QString executeProcess(const QString& execName, const QStringList& args) { Q_ASSERT(!execName.isEmpty()); qCDebug(CMAKE) << "Executing:" << execName << "::" << args; QProcess p; QTemporaryDir tmp(QStringLiteral("kdevcmakemanager")); p.setWorkingDirectory( tmp.path() ); p.start(execName, args, QIODevice::ReadOnly); if(!p.waitForFinished()) { qCDebug(CMAKE) << "failed to execute:" << execName << args << p.exitStatus() << p.readAllStandardError(); } QByteArray b = p.readAllStandardOutput(); QString t; t.prepend(b.trimmed()); return t; } QStringList supportedGenerators() { QStringList generatorNames; bool hasNinja = ICore::self() && ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevNinjaBuilder")); if (hasNinja) generatorNames << QStringLiteral("Ninja"); #ifdef Q_OS_WIN // Visual Studio solution is the standard generator under windows, but we don't want to use // the VS IDE, so we need nmake makefiles generatorNames << QStringLiteral("NMake Makefiles") << QStringLiteral("MinGW Makefiles"); #endif generatorNames << QStringLiteral("Unix Makefiles"); return generatorNames; } QString defaultGenerator() { const QStringList generatorNames = supportedGenerators(); QString defGen = generatorNames.value(CMakeBuilderSettings::self()->generator()); if (defGen.isEmpty()) { qCWarning(CMAKE) << "Couldn't find builder with index " << CMakeBuilderSettings::self()->generator() << ", defaulting to 0"; CMakeBuilderSettings::self()->setGenerator(0); defGen = generatorNames.at(0); } return defGen; } QVector importTestSuites(const Path &buildDir) { const auto contents = CMakeListsParser::readCMakeFile(buildDir.toLocalFile() + "/CTestTestfile.cmake"); QVector tests; for (const auto& entry: contents) { if (entry.name == QLatin1String("add_test")) { auto args = entry.arguments; Test test; test.name = args.takeFirst().value; test.executable = args.takeFirst().value; test.arguments = kTransform(args, [](const CMakeFunctionArgument& arg) { return arg.value; }); tests += test; } else if (entry.name == QLatin1String("subdirs")) { tests += importTestSuites(Path(buildDir, entry.arguments.first().value)); } else if (entry.name == QLatin1String("set_tests_properties")) { if(entry.arguments.count() < 4 || entry.arguments.count() % 2) { qCWarning(CMAKE) << "found set_tests_properties() with unexpected number of arguments:" << entry.arguments.count(); continue; } if (tests.isEmpty() || entry.arguments.first().value != tests.last().name) { qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.first().value << " ...), but expected test " << tests.last().name; continue; } if (entry.arguments[1].value != QLatin1String("PROPERTIES")) { qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.first().value << entry.arguments.at(1).value << "...), but expected PROPERTIES as second argument"; continue; } Test &test = tests.last(); for (int i = 2; i < entry.arguments.count(); i += 2) test.properties[entry.arguments[i].value] = entry.arguments[i + 1].value; } } return tests; } } diff --git a/plugins/cmake/duchain/cmakeparsejob.cpp b/plugins/cmake/duchain/cmakeparsejob.cpp index 31a3109a18..8fe761f6db 100644 --- a/plugins/cmake/duchain/cmakeparsejob.cpp +++ b/plugins/cmake/duchain/cmakeparsejob.cpp @@ -1,149 +1,151 @@ /* 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 "cmakeparsejob.h" #include "declarationbuilder.h" #include "usebuilder.h" #include #include #include #include #include #include #include #include #include -#include + +#include + #include using namespace KDevelop; CMakeParseJob::CMakeParseJob(const KDevelop::IndexedString& url, KDevelop::ILanguageSupport* languageSupport) : ParseJob(url, languageSupport) { } IndexedString parentCMakeFile(const IndexedString& doc) { return IndexedString(QUrl(KIO::upUrl(doc.toUrl().adjusted(QUrl::RemoveFilename)).toString()+"CMakeLists.txt")); } void CMakeParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { const IndexedString languageName(CMakeManager::languageName()); UrlParseLock urlLock(document()); if (abortRequested() || !isUpdateRequired(languageName)) { return; } ProblemPointer p = readContents(); if (p) { //TODO: associate problem with topducontext return; } ReferencedTopDUContext parentCtx; if (document().str().endsWith(QStringLiteral("CMakeLists.txt"))) { IndexedString parentFile = parentCMakeFile(document()); if (QFile::exists(parentFile.toUrl().toLocalFile())) { // TODO: figure out includes import { DUChainReadLocker lock; parentCtx = DUChain::self()->chainForDocument(parentFile); } if (!parentCtx) { qCDebug(CMAKE) << "waiting for..." << parentFile << document(); //FIXME: Currently dead-locks //parentCtx = DUChain::self()->waitForUpdate(parentFile, TopDUContext::AllDeclarationsAndContexts); } //Q_ASSERT(parentCtx); } } ReferencedTopDUContext context; { DUChainReadLocker lock; context = DUChainUtils::standardContextForUrl(document().toUrl()); } if (context) { translateDUChainToRevision(context); DUChainWriteLocker lock; context->setRange(RangeInRevision(0, 0, INT_MAX, INT_MAX)); context->addImportedParentContext(parentCtx); } CMakeFileContent package = CMakeListsParser::readCMakeFile(document().toUrl().toLocalFile()); if (!package.isEmpty()) { if (abortRequested()) { abortJob(); return; } QReadLocker parseLock(languageSupport()->parseLock()); CMakeContentIterator it(package); DeclarationBuilder builder; context = builder.build(document(), &it, context); if (abortRequested()) { abortJob(); return; } // session.reparseImporters(context); if ( context && minimumFeatures() & TopDUContext::AllDeclarationsContextsAndUses ) { UseBuilder useBuilder(context); CMakeContentIterator it(package); useBuilder.startVisiting(&it); } } if (abortRequested()) { abortJob(); return; } if (!context) { DUChainWriteLocker lock; ParsingEnvironmentFile *file = new ParsingEnvironmentFile(document()); file->setLanguage(languageName); context = new TopDUContext(document(), RangeInRevision(0, 0, INT_MAX, INT_MAX), file); DUChain::self()->addDocumentChain(context); } setDuChain(context); { DUChainWriteLocker lock; // context->setProblems(session.problems()); context->setFeatures(minimumFeatures()); ParsingEnvironmentFilePointer file = context->parsingEnvironmentFile(); Q_ASSERT(file); file->setModificationRevision(contents().modification); DUChain::self()->updateContextEnvironment( context->topContext(), file.data() ); } highlightDUChain(); DUChain::self()->emitUpdateReady(document(), duChain()); } diff --git a/plugins/cmake/parser/cmStandardLexer.h b/plugins/cmake/parser/cmStandardLexer.h index 088452a8dd..c489c0303e 100644 --- a/plugins/cmake/parser/cmStandardLexer.h +++ b/plugins/cmake/parser/cmStandardLexer.h @@ -1,60 +1,60 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmStandardLexer_h #define cmStandardLexer_h -#include +#include /* Disable some warnings. */ #if defined(_MSC_VER) #pragma warning(disable : 4018) #pragma warning(disable : 4127) #pragma warning(disable : 4131) #pragma warning(disable : 4244) #pragma warning(disable : 4251) #pragma warning(disable : 4267) #pragma warning(disable : 4305) #pragma warning(disable : 4309) #pragma warning(disable : 4706) #pragma warning(disable : 4786) #endif #if defined(__GNUC__) && !defined(__INTEL_COMPILER) #if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402 #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-compare" #endif #if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 403 #pragma GCC diagnostic ignored "-Wsign-conversion" #endif #endif /* Make sure isatty is available. */ #if defined(_WIN32) && !defined(__CYGWIN__) #include #if defined(_MSC_VER) #define isatty _isatty #endif #else #include /* IWYU pragma: export */ #endif /* Make sure malloc and free are available on QNX. */ #ifdef __QNX__ #include #endif /* Disable features we do not need. */ #define YY_NEVER_INTERACTIVE 1 #define YY_NO_INPUT 1 #define YY_NO_UNPUT 1 #define ECHO typedef qint8 flex_int8_t; typedef quint8 flex_uint8_t; typedef qint16 flex_int16_t; typedef quint16 flex_uint16_t; typedef qint32 flex_int32_t; typedef quint32 flex_uint32_t; #endif diff --git a/plugins/cmake/settings/cmakepreferences.cpp b/plugins/cmake/settings/cmakepreferences.cpp index dd170ec34c..ef4848ca07 100644 --- a/plugins/cmake/settings/cmakepreferences.cpp +++ b/plugins/cmake/settings/cmakepreferences.cpp @@ -1,405 +1,405 @@ /* 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 #include #include #include "ui_cmakebuildsettings.h" #include "cmakecachedelegate.h" #include "cmakebuilddirchooser.h" #include "cmakebuilderconfig.h" #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(nullptr) { m_prefsUi = new Ui::CMakeBuildSettings; m_prefsUi->setupUi(this); 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(); // configure the extraArguments widget to span the advanced box width but not // expand the dialog to the width of the longest element in the argument history. // static_cast needed because KComboBox::minimumSizeHint() override mistakingly made it protected m_prefsUi->extraArguments->setMinimumWidth(static_cast(m_prefsUi->extraArguments)->minimumSizeHint().width()); m_extraArgumentsHistory = new CMakeExtraArgumentsHistory(m_prefsUi->extraArguments); 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); connect(m_prefsUi->installationPrefix, &KUrlRequester::textChanged, this, &CMakePreferences::changed); connect(m_prefsUi->buildType, static_cast(&QComboBox::currentIndexChanged), this, &CMakePreferences::changed); connect(m_prefsUi->buildType, &QComboBox::currentTextChanged, this, &CMakePreferences::changed); connect(m_prefsUi->extraArguments, &KComboBox::currentTextChanged, this, &CMakePreferences::changed); connect(m_prefsUi->extraArguments, &KComboBox::editTextChanged, this, &CMakePreferences::changed); connect(m_prefsUi->cMakeExecutable, &KUrlRequester::textChanged, 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_extraArgumentsHistory; delete m_prefsUi; } void CMakePreferences::initAdvanced() { m_prefsUi->environment->setCurrentProfile( CMake::currentEnvironment(m_project) ); m_prefsUi->installationPrefix->setText(CMake::currentInstallDir(m_project).toLocalFile()); m_prefsUi->installationPrefix->setMode(KFile::Directory); const QString buildType = CMake::currentBuildType(m_project); if (m_prefsUi->buildType->findText(buildType) == -1) { m_prefsUi->buildType->addItem(buildType); } m_prefsUi->buildType->setCurrentIndex(m_prefsUi->buildType->findText(buildType)); m_prefsUi->extraArguments->setEditText(CMake::currentExtraArguments(m_project)); m_prefsUi->cMakeExecutable->setText(CMake::currentCMakeExecutable(m_project).toLocalFile()); } 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) ); initAdvanced(); 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() ); CMake::setCurrentInstallDir( m_project, Path(m_prefsUi->installationPrefix->text()) ); const QString buildType = m_prefsUi->buildType->currentText(); if (m_prefsUi->buildType->findText(buildType) == -1) { m_prefsUi->buildType->addItem(buildType); } CMake::setCurrentBuildType( m_project, buildType ); CMake::setCurrentExtraArguments( m_project, m_prefsUi->extraArguments->currentText() ); CMake::setCurrentCMakeExecutable( m_project, Path(m_prefsUi->cMakeExecutable->text()) ); 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 executable " << CMake::currentCMakeExecutable(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->hideColumn(5); 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, QStringLiteral("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, nullptr); if (m_currentModel) { m_currentModel->deleteLater(); 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=QStringLiteral("%1. %2") .arg(m_currentModel->itemFromIndex(idxType)->text(), 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); initAdvanced(); 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.setProject( m_project ); // 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 paths? QStringList used = CMake::allBuildDirs( m_project ); bdCreator.setAlreadyUsed(used); bdCreator.setCMakeExecutable(Path(CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile())); if(bdCreator.exec()) { int addedBuildDirIndex = m_prefsUi->buildDirs->count(); // 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 executable " << bdCreator.cmakeExecutable(); qCDebug(CMAKE) << "adding to cmake config: environment empty"; CMake::setOverrideBuildDirIndex( m_project, addedBuildDirIndex ); CMake::setBuildDirCount( m_project, addedBuildDirIndex + 1 ); 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::setCurrentCMakeExecutable(m_project, bdCreator.cmakeExecutable()); CMake::setCurrentEnvironment( m_project, QString() ); QString newbuilddir = bdCreator.buildFolder().toLocalFile(); m_prefsUi->buildDirs->addItem( newbuilddir ); m_prefsUi->buildDirs->setCurrentIndex( addedBuildDirIndex ); m_prefsUi->removeBuildDir->setEnabled( true ); 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) { auto deleteJob = KIO::del(removedPath.toUrl()); KJobWidgets::setWindow(deleteJob, this); if (!deleteJob->exec()) KMessageBox::error(this, i18n("Could not remove: %1", 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 executable " << CMake::currentCMakeExecutable(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/plugins/cmakebuilder/cmakebuilder.cpp b/plugins/cmakebuilder/cmakebuilder.cpp index 1994b3dfb0..17371954cf 100644 --- a/plugins/cmakebuilder/cmakebuilder.cpp +++ b/plugins/cmakebuilder/cmakebuilder.cpp @@ -1,268 +1,269 @@ /* 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 "cmakejob.h" #include "prunejob.h" #include "cmakebuilderpreferences.h" #include "cmakeutils.h" #include +#include +#include +#include + +#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(QStringLiteral("kdevcmakebuilder"), parent) { addBuilder(QStringLiteral("Makefile"), QStringList{QStringLiteral("Unix Makefiles"), QStringLiteral("NMake Makefiles"), QStringLiteral("MinGW Makefiles")}, core()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IMakeBuilder"))); addBuilder(QStringLiteral("build.ninja"), QStringList(QStringLiteral("Ninja")), core()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("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; for (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(KDEV_CMAKEBUILDER) << "Added builder " << i->metaObject()->className() << "for" << neededfile; } else qCWarning(KDEV_CMAKEBUILDER) << "Couldn't add" << i->metaObject()->className(); } } 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 = nullptr; if(dom->file()) { IMakeBuilder* makeBuilder = dynamic_cast(builder); if (!makeBuilder) { return new ErrorJob(this, i18n("Could not 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(KDEV_CMAKEBUILDER) << "create build job for target" << build << dom << target; } qCDebug(KDEV_CMAKEBUILDER) << "Building with" << builder; if (!build) { build = builder->build(dom); } if( configure ) { qCDebug(KDEV_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("Could not 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(KDEV_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("Could not 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(KDEV_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("Could not 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[CMake::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; } KDevelop::ConfigPage* CMakeBuilder::configPage(int number, QWidget* parent) { if (number == 0) { return new CMakeBuilderPreferences(this, parent); } return nullptr; } #include "cmakebuilder.moc" diff --git a/plugins/cmakebuilder/cmakejob.cpp b/plugins/cmakebuilder/cmakejob.cpp index 5396ffce69..aecf2d2991 100644 --- a/plugins/cmakebuilder/cmakejob.cpp +++ b/plugins/cmakebuilder/cmakejob.cpp @@ -1,142 +1,142 @@ /* 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 - #include "cmakeutils.h" #include "debug.h" +#include +#include + using namespace KDevelop; CMakeJob::CMakeJob(QObject* parent) : OutputExecuteJob(parent) { 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(KDEV_CMAKEBUILDER) << "Configuring cmake" << workingDirectory(); if( !m_project ) { setError(NoProjectError); setErrorText(QStringLiteral("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(KDEV_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::currentCMakeExecutable( m_project ).toLocalFile(); args << QStringLiteral("-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(), it.value().toString()); } auto rt = ICore::self()->runtimeController()->currentRuntime(); //if we are creating a new build directory, we'll want to specify the generator QDir builddir(rt->pathInRuntime(CMake::currentBuildDir( m_project )).toLocalFile()); if(!builddir.exists() || !builddir.exists(QStringLiteral("CMakeCache.txt"))) { CMakeBuilderSettings::self()->load(); args << QStringLiteral("-G") << CMake::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 { qCWarning(KDEV_CMAKEBUILDER) << "Ignoring cmake Extra arguments"; if( err == KShell::BadQuoting ) { qCWarning(KDEV_CMAKEBUILDER) << "CMake arguments badly quoted:" << cmakeargs; } else { qCWarning(KDEV_CMAKEBUILDER) << "CMake arguments had meta character:" << cmakeargs; } } } args << rt->pathInRuntime(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/plugins/cmakebuilder/prunejob.cpp b/plugins/cmakebuilder/prunejob.cpp index 748207f9ee..febfe99685 100644 --- a/plugins/cmakebuilder/prunejob.cpp +++ b/plugins/cmakebuilder/prunejob.cpp @@ -1,89 +1,91 @@ /* 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 + #include using namespace KDevelop; PruneJob::PruneJob(KDevelop::IProject* project) : OutputJob(project, Verbose) , m_project(project) , 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 the build directory")); emitResult(); return; } else if (!builddir.isLocalFile() || QDir(builddir.toLocalFile()).exists(QStringLiteral("CMakeLists.txt"))) { output->appendLine(i18n("Wrong build directory, cannot clear the build directory")); emitResult(); return; } QDir d( builddir.toLocalFile() ); QList urls; const auto entries = d.entryList( QDir::NoDotAndDotDot | QDir::AllEntries ); urls.reserve(entries.size()); for (const auto& entry : entries) { 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 = nullptr; } diff --git a/plugins/contextbrowser/browsemanager.cpp b/plugins/contextbrowser/browsemanager.cpp index 1b7a82fc06..aa08ddfe99 100644 --- a/plugins/contextbrowser/browsemanager.cpp +++ b/plugins/contextbrowser/browsemanager.cpp @@ -1,346 +1,347 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * 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 "browsemanager.h" #include #include #include #include -#include -#include -#include #include #include #include "contextbrowserview.h" #include #include #include #include #include #include #include #include -#include #include #include #include "contextbrowser.h" #include "debug.h" +#include +#include +#include +#include + using namespace KDevelop; using namespace KTextEditor; EditorViewWatcher::EditorViewWatcher(QObject* parent) : QObject(parent) { connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &EditorViewWatcher::documentCreated); foreach(KDevelop::IDocument* document, ICore::self()->documentController()->openDocuments()) documentCreated(document); } void EditorViewWatcher::documentCreated( KDevelop::IDocument* document ) { KTextEditor::Document* textDocument = document->textDocument(); if(textDocument) { connect(textDocument, &Document::viewCreated, this, &EditorViewWatcher::viewCreated); foreach(KTextEditor::View* view, textDocument->views()) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } } } void EditorViewWatcher::addViewInternal(KTextEditor::View* view) { m_views << view; viewAdded(view); connect(view, &View::destroyed, this, &EditorViewWatcher::viewDestroyed); } void EditorViewWatcher::viewAdded(KTextEditor::View*) { } void EditorViewWatcher::viewDestroyed(QObject* view) { m_views.removeAll(static_cast(view)); } void EditorViewWatcher::viewCreated(KTextEditor::Document* /*doc*/, KTextEditor::View* view) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } QList EditorViewWatcher::allViews() { return m_views; } void BrowseManager::eventuallyStartDelayedBrowsing() { avoidMenuAltFocus(); if(m_browsingByKey == Qt::Key_Alt && m_browsingStartedInView) emit startDelayedBrowsing(m_browsingStartedInView); } BrowseManager::BrowseManager(ContextBrowserPlugin* controller) : QObject(controller) , m_plugin(controller) , m_browsing(false) , m_browsingByKey(0) , m_watcher(this) { m_delayedBrowsingTimer = new QTimer(this); m_delayedBrowsingTimer->setSingleShot(true); connect(m_delayedBrowsingTimer, &QTimer::timeout, this, &BrowseManager::eventuallyStartDelayedBrowsing); foreach(KTextEditor::View* view, m_watcher.allViews()) viewAdded(view); } KTextEditor::View* viewFromWidget(QWidget* widget) { if(!widget) return nullptr; KTextEditor::View* view = qobject_cast(widget); if(view) return view; else return viewFromWidget(widget->parentWidget()); } BrowseManager::JumpLocation BrowseManager::determineJumpLoc(KTextEditor::Cursor textCursor, const QUrl& viewUrl) const { // @todo find out why this is needed, fix the code in kate if (textCursor.column() > 0) { textCursor.setColumn(textCursor.column() - 1); } // Step 1: Look for a special language object(Macro, included header, etc.) foreach (const auto& language, ICore::self()->languageController()->languagesForUrl(viewUrl)) { auto jumpTo = language->specialLanguageObjectJumpCursor(viewUrl, textCursor); if (jumpTo.first.isValid() && jumpTo.second.isValid()) { return {jumpTo.first, jumpTo.second}; } } // Step 2: Look for a declaration/use DUChainReadLocker lock; // Jump to definition by default, unless a definition itself was selected, // in which case jump to declaration. if (auto selectedDeclaration = DUChainUtils::itemUnderCursor(viewUrl, textCursor).declaration) { auto jumpDestination = selectedDeclaration; if (selectedDeclaration->isDefinition()) { // A definition was clicked directly - jump to declaration instead. if (auto declaration = DUChainUtils::declarationForDefinition(selectedDeclaration)) { jumpDestination = declaration; } } else if (selectedDeclaration == DUChainUtils::declarationForDefinition(selectedDeclaration)) { // Clicked the declaration - jump to definition if (auto definition = FunctionDefinition::definition(selectedDeclaration)) { jumpDestination = definition; } } return {jumpDestination->url().toUrl(), jumpDestination->rangeInCurrentRevision().start()}; } return {}; } bool BrowseManager::eventFilter(QObject * watched, QEvent * event) { QWidget* widget = qobject_cast(watched); Q_ASSERT(widget); QKeyEvent* keyEvent = dynamic_cast(event); const int browseKey = Qt::Key_Control; const int magicModifier = Qt::Key_Alt; KTextEditor::View* view = viewFromWidget(widget); //Eventually start key-browsing if(keyEvent && (keyEvent->key() == browseKey || keyEvent->key() == magicModifier) && !m_browsingByKey && keyEvent->type() == QEvent::KeyPress) { m_delayedBrowsingTimer->start(300); // always start the timer, to get consistent behavior regarding the ALT key and the menu activation m_browsingByKey = keyEvent->key(); if(!view) { return false; } if(keyEvent->key() == magicModifier) { if(dynamic_cast(view) && dynamic_cast(view)->isCompletionActive()) { //Completion is active. avoidMenuAltFocus(); m_delayedBrowsingTimer->stop(); }else{ m_browsingStartedInView = view; } } } if (keyEvent && m_browsingByKey && m_browsingStartedInView && keyEvent->type() == QEvent::KeyPress) { if (keyEvent->key() >= Qt::Key_1 && keyEvent->key() <= Qt::Key_9) { // user wants to trigger an action in the code browser const int index = keyEvent->key() - Qt::Key_1; emit invokeAction(index); emit stopDelayedBrowsing(); return true; } } if(!view) { return false; } QFocusEvent* focusEvent = dynamic_cast(event); //Eventually stop key-browsing if((keyEvent && m_browsingByKey && keyEvent->key() == m_browsingByKey && keyEvent->type() == QEvent::KeyRelease) || (focusEvent && focusEvent->lostFocus()) || event->type() == QEvent::WindowDeactivate) { m_browsingByKey = 0; emit stopDelayedBrowsing(); } QMouseEvent* mouseEvent = dynamic_cast(event); if(mouseEvent) { if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton1) { m_plugin->historyPrevious(); return true; } if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton2) { m_plugin->historyNext(); return true; } } if(!m_browsing && !m_browsingByKey) { resetChangedCursor(); return false; } if(mouseEvent) { QPoint coordinatesInView = widget->mapTo(view, mouseEvent->pos()); KTextEditor::Cursor textCursor = view->coordinatesToCursor(coordinatesInView); if (textCursor.isValid()) { JumpLocation jumpTo = determineJumpLoc(textCursor, view->document()->url()); if (jumpTo.isValid()) { if(mouseEvent->button() == Qt::LeftButton) { if(mouseEvent->type() == QEvent::MouseButtonPress) { m_buttonPressPosition = textCursor; // view->setCursorPosition(textCursor); // return false; }else if(mouseEvent->type() == QEvent::MouseButtonRelease && textCursor == m_buttonPressPosition) { ICore::self()->documentController()->openDocument(jumpTo.url, jumpTo.cursor); // event->accept(); // return true; } }else if(mouseEvent->type() == QEvent::MouseMove) { //Make the cursor a "hand" setHandCursor(widget); return false; } } } resetChangedCursor(); } return false; } void BrowseManager::resetChangedCursor() { QMap, QCursor> cursors = m_oldCursors; m_oldCursors.clear(); for(QMap, QCursor>::iterator it = cursors.begin(); it != cursors.end(); ++it) if(it.key()) it.key()->setCursor(QCursor(Qt::IBeamCursor)); } void BrowseManager::setHandCursor(QWidget* widget) { if(m_oldCursors.contains(widget)) return; //Nothing to do m_oldCursors[widget] = widget->cursor(); widget->setCursor(QCursor(Qt::PointingHandCursor)); } void BrowseManager::avoidMenuAltFocus() { auto mainWindow = ICore::self()->uiController()->activeMainWindow(); if (!mainWindow) return; // send an invalid key event to the main menu bar. The menu bar will // stop listening when observing another key than ALT between the press // and the release. QKeyEvent event1(QEvent::KeyPress, 0, Qt::NoModifier); QApplication::sendEvent(mainWindow->menuBar(), &event1); QKeyEvent event2(QEvent::KeyRelease, 0, Qt::NoModifier); QApplication::sendEvent(mainWindow->menuBar(), &event2); } void BrowseManager::applyEventFilter(QWidget* object, bool install) { if(install) object->installEventFilter(this); else object->removeEventFilter(this); foreach(QObject* child, object->children()) if(qobject_cast(child)) applyEventFilter(qobject_cast(child), install); } void BrowseManager::viewAdded(KTextEditor::View* view) { applyEventFilter(view, true); //We need to listen for cursorPositionChanged, to clear the shift-detector. The problem: Kate listens for the arrow-keys using shortcuts, //so those keys are not passed to the event-filter // can't use new signal/slot syntax here, these signals are only defined in KateView // TODO: should we really depend on kate internals here? connect(view, SIGNAL(navigateLeft()), m_plugin, SLOT(navigateLeft())); connect(view, SIGNAL(navigateRight()), m_plugin, SLOT(navigateRight())); connect(view, SIGNAL(navigateUp()), m_plugin, SLOT(navigateUp())); connect(view, SIGNAL(navigateDown()), m_plugin, SLOT(navigateDown())); connect(view, SIGNAL(navigateAccept()), m_plugin, SLOT(navigateAccept())); connect(view, SIGNAL(navigateBack()), m_plugin, SLOT(navigateBack())); } void Watcher::viewAdded(KTextEditor::View* view) { m_manager->viewAdded(view); } void BrowseManager::setBrowsing(bool enabled) { if(enabled == m_browsing) return; m_browsing = enabled; //This collects all the views if(enabled) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Enabled browsing-mode"; }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "Disabled browsing-mode"; resetChangedCursor(); } } Watcher::Watcher(BrowseManager* manager) : EditorViewWatcher(manager), m_manager(manager) { foreach(KTextEditor::View* view, allViews()) m_manager->applyEventFilter(view, true); } diff --git a/plugins/cppcheck/job.cpp b/plugins/cppcheck/job.cpp index cdd0f27c04..5a7084c5c2 100644 --- a/plugins/cppcheck/job.cpp +++ b/plugins/cppcheck/job.cpp @@ -1,217 +1,218 @@ /* This file is part of KDevelop Copyright 2011 Mathieu Lornac Copyright 2011 Damien Coppel Copyright 2011 Lionel Duc Copyright 2011 Sebastien Rannou Copyright 2011 Lucas Sarie Copyright 2006-2008 Hamish Rodda Copyright 2002 Harald Fernengel Copyright 2013 Christoph Thielecke Copyright 2016-2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "job.h" #include "debug.h" #include "parser.h" #include "utils.h" -#include -#include #include +#include +#include + #include #include #include namespace cppcheck { Job::Job(const Parameters& params, QObject* parent) : KDevelop::OutputExecuteJob(parent) , m_timer(new QElapsedTimer) , m_parser(new CppcheckParser) , m_showXmlOutput(params.showXmlOutput) , m_projectRootPath(params.projectRootPath()) { setJobName(i18n("Cppcheck Analysis (%1)", prettyPathName(params.checkPath))); setCapabilities(KJob::Killable); setStandardToolView(KDevelop::IOutputView::TestView); setBehaviours(KDevelop::IOutputView::AutoScroll); setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStdout); setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStderr); setProperties(KDevelop::OutputExecuteJob::JobProperty::PostProcessOutput); *this << params.commandLine(); qCDebug(KDEV_CPPCHECK) << "checking path" << params.checkPath; } Job::~Job() { doKill(); } void Job::postProcessStdout(const QStringList& lines) { static const auto fileNameRegex = QRegularExpression(QStringLiteral("Checking ([^:]*)\\.{3}")); static const auto percentRegex = QRegularExpression(QStringLiteral("(\\d+)% done")); QRegularExpressionMatch match; for (const QString& line : lines) { match = fileNameRegex.match(line); if (match.hasMatch()) { emit infoMessage(this, match.captured(1)); continue; } match = percentRegex.match(line); if (match.hasMatch()) { setPercent(match.captured(1).toULong()); continue; } } m_standardOutput << lines; if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning) { KDevelop::OutputExecuteJob::postProcessStdout(lines); } } void Job::postProcessStderr(const QStringList& lines) { static const auto xmlStartRegex = QRegularExpression(QStringLiteral("\\s*<")); for (const QString & line : lines) { // unfortunately sometime cppcheck send non-XML messages to stderr. // For example, if we pass '-I /missing_include_dir' to the argument list, // then stderr output will contains such line (tested on cppcheck 1.72): // // (information) Couldn't find path given by -I '/missing_include_dir' // // Therefore we must 'move' such messages to m_standardOutput. if (line.indexOf(xmlStartRegex) != -1) { // the line contains XML m_xmlOutput << line; m_parser->addData(line); m_problems = m_parser->parse(); emitProblems(); } else { KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(i18n("Cppcheck"))); problem->setSeverity(KDevelop::IProblem::Error); problem->setDescription(line); problem->setExplanation(QStringLiteral("Check your cppcheck settings")); m_problems = {problem}; emitProblems(); if (m_showXmlOutput) { m_standardOutput << line; } else { postProcessStdout({line}); } } } if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning && m_showXmlOutput) { KDevelop::OutputExecuteJob::postProcessStderr(lines); } } void Job::start() { m_standardOutput.clear(); m_xmlOutput.clear(); qCDebug(KDEV_CPPCHECK) << "executing:" << commandLine().join(QLatin1Char(' ')); m_timer->restart(); KDevelop::OutputExecuteJob::start(); } void Job::childProcessError(QProcess::ProcessError e) { QString message; switch (e) { case QProcess::FailedToStart: message = i18n("Failed to start Cppcheck from \"%1\".", commandLine()[0]); break; case QProcess::Crashed: if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { message = i18n("Cppcheck crashed."); } break; case QProcess::Timedout: message = i18n("Cppcheck process timed out."); break; case QProcess::WriteError: message = i18n("Write to Cppcheck process failed."); break; case QProcess::ReadError: message = i18n("Read from Cppcheck process failed."); break; case QProcess::UnknownError: // current cppcheck errors will be displayed in the output view // don't notify the user break; } if (!message.isEmpty()) { KMessageBox::error(qApp->activeWindow(), message, i18n("Cppcheck Error")); } KDevelop::OutputExecuteJob::childProcessError(e); } void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(KDEV_CPPCHECK) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus; postProcessStdout({QStringLiteral("Elapsed time: %1 s.").arg(m_timer->elapsed()/1000.0)}); if (exitCode != 0) { qCDebug(KDEV_CPPCHECK) << "cppcheck failed, standard output: "; qCDebug(KDEV_CPPCHECK) << m_standardOutput.join(QLatin1Char('\n')); qCDebug(KDEV_CPPCHECK) << "cppcheck failed, XML output: "; qCDebug(KDEV_CPPCHECK) << m_xmlOutput.join(QLatin1Char('\n')); } KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus); } void Job::emitProblems() { if (!m_problems.isEmpty()) { emit problemsDetected(m_problems); } } } diff --git a/plugins/cppcheck/parser.cpp b/plugins/cppcheck/parser.cpp index 7d5ae95d3f..714a1b8b05 100644 --- a/plugins/cppcheck/parser.cpp +++ b/plugins/cppcheck/parser.cpp @@ -1,306 +1,307 @@ /* This file is part of KDevelop Copyright 2013 Christoph Thielecke Copyright 2016 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parser.h" #include "debug.h" -#include -#include #include #include +#include +#include + #include #include namespace cppcheck { /** * Convert the value of \ attribute of \ element from cppcheck's * XML-output to 'good-looking' HTML-version. This is necessary because the * displaying of the original message is performed without line breaks - such * tooltips are uncomfortable to read, and large messages will not fit into the * screen. * * This function put the original message into \ tag that automatically * provides line wrapping by builtin capabilities of Qt library. The source text * also can contain tokens '\012' (line break) - they are present in the case of * source code examples. In such cases, the entire text between the first and * last tokens (i.e. source code) is placed into \ tag. * * @param[in] input the original value of \ attribute * @return HTML version for displaying in problem's tooltip */ QString verboseMessageToHtml( const QString & input ) { QString output(QStringLiteral("%1").arg(input.toHtmlEscaped())); output.replace(QLatin1String("\\012"), QLatin1String("\n")); if (output.count(QLatin1Char('\n')) >= 2) { output.replace(output.indexOf(QLatin1Char('\n')), 1, QStringLiteral("
") );
         output.replace(output.lastIndexOf(QLatin1Char('\n')), 1, QStringLiteral("

") ); } return output; } CppcheckParser::CppcheckParser() { } CppcheckParser::~CppcheckParser() { } void CppcheckParser::clear() { m_stateStack.clear(); } bool CppcheckParser::startElement() { State newState = Unknown; qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: elem: " << qPrintable(name().toString()); if (name() == QLatin1String("results")) { newState = Results; } else if (name() == QLatin1String("cppcheck")) { newState = CppCheck; } else if (name() == QLatin1String("errors")) { newState = Errors; } else if (name() == QLatin1String("location")) { newState = Location; if (attributes().hasAttribute(QStringLiteral("file")) && attributes().hasAttribute(QStringLiteral("line"))) { QString errorFile = attributes().value(QStringLiteral("file")).toString(); // Usually when "file0" attribute exists it associated with source and // attribute "file" associated with header). // But sometimes cppcheck produces errors with "file" and "file0" attributes // both associated with same *source* file. In such cases attribute "file" contains // only file name, without full path. Therefore we should use "file0" instead "file". if (!QFile::exists(errorFile) && attributes().hasAttribute(QStringLiteral("file0"))) { errorFile = attributes().value(QStringLiteral("file0")).toString(); } m_errorFiles += errorFile; m_errorLines += attributes().value(QStringLiteral("line")).toString().toInt(); } } else if (name() == QLatin1String("error")) { newState = Error; m_errorSeverity = QStringLiteral("unknown"); m_errorInconclusive = false; m_errorFiles.clear(); m_errorLines.clear(); m_errorMessage.clear(); m_errorVerboseMessage.clear(); if (attributes().hasAttribute(QStringLiteral("msg"))) { m_errorMessage = attributes().value(QStringLiteral("msg")).toString(); } if (attributes().hasAttribute(QStringLiteral("verbose"))) { m_errorVerboseMessage = verboseMessageToHtml(attributes().value(QStringLiteral("verbose")).toString()); } if (attributes().hasAttribute(QStringLiteral("severity"))) { m_errorSeverity = attributes().value(QStringLiteral("severity")).toString(); } if (attributes().hasAttribute(QStringLiteral("inconclusive"))) { m_errorInconclusive = true; } } else { m_stateStack.push(m_stateStack.top()); return true; } m_stateStack.push(newState); return true; } bool CppcheckParser::endElement(QVector& problems) { qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: elem: " << qPrintable(name().toString()); State state = m_stateStack.pop(); switch (state) { case CppCheck: if (attributes().hasAttribute(QStringLiteral("version"))) { qCDebug(KDEV_CPPCHECK) << "Cppcheck report version: " << attributes().value(QStringLiteral("version")); } break; case Errors: // errors finished break; case Error: qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: new error elem: line: " << (m_errorLines.isEmpty() ? QStringLiteral("?") : QString::number(m_errorLines.first())) << " at " << (m_errorFiles.isEmpty() ? QStringLiteral("?") : m_errorFiles.first()) << ", msg: " << m_errorMessage; storeError(problems); break; case Results: // results finished break; case Location: break; default: break; } return true; } QVector CppcheckParser::parse() { QVector problems; qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse!"; while (!atEnd()) { int readNextVal = readNext(); switch (readNextVal) { case StartDocument: clear(); break; case StartElement: startElement(); break; case EndElement: endElement(problems); break; case Characters: break; default: qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: case: " << readNextVal; break; } } qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse: end"; if (hasError()) { switch (error()) { case CustomError: case UnexpectedElementError: case NotWellFormedError: KMessageBox::error( qApp->activeWindow(), i18n("Cppcheck XML Parsing: error at line %1, column %2: %3", lineNumber(), columnNumber(), errorString()), i18n("Cppcheck Error")); break; case NoError: case PrematureEndOfDocumentError: break; } } return problems; } void CppcheckParser::storeError(QVector& problems) { // Construct problem with using first location element KDevelop::IProblem::Ptr problem = getProblem(); // Adds other elements as diagnostics. // This allows the user to track the problem. for (int locationIdx = 1; locationIdx < m_errorFiles.size(); ++locationIdx) { problem->addDiagnostic(getProblem(locationIdx)); } problems.push_back(problem); } KDevelop::IProblem::Ptr CppcheckParser::getProblem(int locationIdx) const { KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(i18n("Cppcheck"))); QStringList messagePrefix; QString errorMessage(m_errorMessage); if (m_errorSeverity == QLatin1String("error")) { problem->setSeverity(KDevelop::IProblem::Error); } else if (m_errorSeverity == QLatin1String("warning")) { problem->setSeverity(KDevelop::IProblem::Warning); } else { problem->setSeverity(KDevelop::IProblem::Hint); messagePrefix.push_back(m_errorSeverity); } if (m_errorInconclusive) { messagePrefix.push_back(QStringLiteral("inconclusive")); } if (!messagePrefix.isEmpty()) { errorMessage = QStringLiteral("(%1) %2").arg(messagePrefix.join(QStringLiteral(", ")), m_errorMessage); } problem->setDescription(errorMessage); problem->setExplanation(m_errorVerboseMessage); KDevelop::DocumentRange range; if (locationIdx < 0 || locationIdx >= m_errorFiles.size()) { range = KDevelop::DocumentRange::invalid(); } else { range.document = KDevelop::IndexedString(m_errorFiles.at(locationIdx)); range.setBothLines(m_errorLines.at(locationIdx) - 1); range.setBothColumns(0); } problem->setFinalLocation(range); problem->setFinalLocationMode(KDevelop::IProblem::TrimmedLine); return problem; } } diff --git a/plugins/cppcheck/plugin.cpp b/plugins/cppcheck/plugin.cpp index 721959982b..fdec194957 100644 --- a/plugins/cppcheck/plugin.cpp +++ b/plugins/cppcheck/plugin.cpp @@ -1,295 +1,296 @@ /* This file is part of KDevelop Copyright 2013 Christoph Thielecke Copyright 2016-2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugin.h" #include "config/globalconfigpage.h" #include "config/projectconfigpage.h" #include "globalsettings.h" #include "debug.h" #include "job.h" #include "problemmodel.h" #include #include #include #include #include #include -#include -#include #include #include #include #include +#include +#include + #include #include K_PLUGIN_FACTORY_WITH_JSON(CppcheckFactory, "kdevcppcheck.json", registerPlugin();) namespace cppcheck { Plugin::Plugin(QObject* parent, const QVariantList&) : IPlugin(QStringLiteral("kdevcppcheck"), parent) , m_job(nullptr) , m_currentProject(nullptr) , m_model(new ProblemModel(this)) { qCDebug(KDEV_CPPCHECK) << "setting cppcheck rc file"; setXMLFile(QStringLiteral("kdevcppcheck.rc")); QIcon cppcheckIcon = QIcon::fromTheme(QStringLiteral("cppcheck")); m_menuActionFile = new QAction(cppcheckIcon, i18n("Analyze Current File with Cppcheck"), this); connect(m_menuActionFile, &QAction::triggered, this, [this](){ runCppcheck(false); }); actionCollection()->addAction(QStringLiteral("cppcheck_file"), m_menuActionFile); m_contextActionFile = new QAction(cppcheckIcon, i18n("Cppcheck"), this); connect(m_contextActionFile, &QAction::triggered, this, [this]() { runCppcheck(false); }); m_menuActionProject = new QAction(cppcheckIcon, i18n("Analyze Current Project with Cppcheck"), this); connect(m_menuActionProject, &QAction::triggered, this, [this](){ runCppcheck(true); }); actionCollection()->addAction(QStringLiteral("cppcheck_project"), m_menuActionProject); m_contextActionProject = new QAction(cppcheckIcon, i18n("Cppcheck"), this); connect(m_contextActionProject, &QAction::triggered, this, [this]() { runCppcheck(true); }); m_contextActionProjectItem = new QAction(cppcheckIcon, i18n("Cppcheck"), this); connect(core()->documentController(), &KDevelop::IDocumentController::documentClosed, this, &Plugin::updateActions); connect(core()->documentController(), &KDevelop::IDocumentController::documentActivated, this, &Plugin::updateActions); connect(core()->projectController(), &KDevelop::IProjectController::projectOpened, this, &Plugin::updateActions); connect(core()->projectController(), &KDevelop::IProjectController::projectClosed, this, &Plugin::projectClosed); updateActions(); } Plugin::~Plugin() { killCppcheck(); } bool Plugin::isRunning() { return m_job; } void Plugin::killCppcheck() { if (m_job) { m_job->kill(KJob::EmitResult); } } void Plugin::raiseProblemsView() { m_model->show(); } void Plugin::raiseOutputView() { core()->uiController()->findToolView( i18nc("@title:window", "Test"), nullptr, KDevelop::IUiController::FindFlags::Raise); } void Plugin::updateActions() { m_currentProject = nullptr; m_menuActionFile->setEnabled(false); m_menuActionProject->setEnabled(false); if (isRunning()) { return; } KDevelop::IDocument* activeDocument = core()->documentController()->activeDocument(); if (!activeDocument) { return; } QUrl url = activeDocument->url(); m_currentProject = core()->projectController()->findProjectForUrl(url); if (!m_currentProject) { return; } m_menuActionFile->setEnabled(true); m_menuActionProject->setEnabled(true); } void Plugin::projectClosed(KDevelop::IProject* project) { if (project != m_model->project()) { return; } killCppcheck(); m_model->reset(); } void Plugin::runCppcheck(bool checkProject) { KDevelop::IDocument* doc = core()->documentController()->activeDocument(); Q_ASSERT(doc); if (checkProject) { runCppcheck(m_currentProject, m_currentProject->path().toUrl().toLocalFile()); } else { runCppcheck(m_currentProject, doc->url().toLocalFile()); } } void Plugin::runCppcheck(KDevelop::IProject* project, const QString& path) { m_model->reset(project, path); Parameters params(project); params.checkPath = path; m_job = new Job(params); connect(m_job, &Job::problemsDetected, m_model.data(), &ProblemModel::addProblems); connect(m_job, &Job::finished, this, &Plugin::result); core()->uiController()->registerStatus(new KDevelop::JobStatus(m_job, QStringLiteral("Cppcheck"))); core()->runController()->registerJob(m_job); if (params.hideOutputView) { raiseProblemsView(); } else { raiseOutputView(); } updateActions(); } void Plugin::result(KJob*) { if (!core()->projectController()->projects().contains(m_model->project())) { m_model->reset(); } else { m_model->setProblems(); if (m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded || m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { raiseProblemsView(); } else { raiseOutputView(); } } m_job = nullptr; // job is automatically deleted later updateActions(); } static bool isSupportedMimeType(const QMimeType& mimeType) { const QString mimeName = mimeType.name(); return (mimeName == QLatin1String("text/x-c++src") || mimeName == QLatin1String("text/x-c++hdr") || mimeName == QLatin1String("text/x-chdr") || mimeName == QLatin1String("text/x-csrc")); } KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context, parent); if (context->hasType(KDevelop::Context::EditorContext) && m_currentProject && !isRunning()) { auto eContext = static_cast(context); QMimeDatabase db; const auto mime = db.mimeTypeForUrl(eContext->url()); if (isSupportedMimeType(mime)) { extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, m_contextActionFile); extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProject); } } if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) { auto pContext = static_cast(context); if (pContext->items().size() != 1) { return extension; } auto item = pContext->items().first(); switch (item->type()) { case KDevelop::ProjectBaseItem::File: { const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(item->path().toUrl()); if (!isSupportedMimeType(mimetype)) { return extension; } break; } case KDevelop::ProjectBaseItem::Folder: case KDevelop::ProjectBaseItem::BuildFolder: break; default: return extension; } m_contextActionProjectItem->disconnect(); connect(m_contextActionProjectItem, &QAction::triggered, this, [this, item](){ runCppcheck(item->project(), item->path().toLocalFile()); }); extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProjectItem); } return extension; } KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) { return number ? nullptr : new ProjectConfigPage(this, options.project, parent); } KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) { return number ? nullptr : new GlobalConfigPage(this, parent); } } #include "plugin.moc" diff --git a/plugins/cppcheck/problemmodel.cpp b/plugins/cppcheck/problemmodel.cpp index 775454b99d..bafd3b4b10 100644 --- a/plugins/cppcheck/problemmodel.cpp +++ b/plugins/cppcheck/problemmodel.cpp @@ -1,173 +1,173 @@ /* This file is part of KDevelop Copyright 2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemmodel.h" #include "plugin.h" #include "utils.h" #include #include #include #include #include #include -#include +#include namespace cppcheck { inline KDevelop::ProblemModelSet* problemModelSet() { return KDevelop::ICore::self()->languageController()->problemModelSet(); } namespace Strings { QString problemModelId() { return QStringLiteral("Cppcheck"); } } ProblemModel::ProblemModel(Plugin* plugin) : KDevelop::ProblemModel(plugin) , m_plugin(plugin) , m_project(nullptr) , m_pathLocation(KDevelop::DocumentRange::invalid()) { setFeatures(CanDoFullUpdate | ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter); reset(); problemModelSet()->addModel(Strings::problemModelId(), i18n("Cppcheck"), this); } ProblemModel::~ProblemModel() { problemModelSet()->removeModel(Strings::problemModelId()); } KDevelop::IProject* ProblemModel::project() const { return m_project; } void ProblemModel::fixProblemFinalLocation(KDevelop::IProblem::Ptr problem) { // Fix problems with incorrect range, which produced by cppcheck's errors // without element. In this case location automatically gets "/". // To avoid this we set current analysis path as problem location. if (problem->finalLocation().document.isEmpty()) { problem->setFinalLocation(m_pathLocation); } const auto& diagnostics = problem->diagnostics(); for (auto& diagnostic : diagnostics) { fixProblemFinalLocation(diagnostic); } } bool ProblemModel::problemExists(KDevelop::IProblem::Ptr newProblem) { for (auto problem : qAsConst(m_problems)) { if (newProblem->source() == problem->source() && newProblem->severity() == problem->severity() && newProblem->finalLocation() == problem->finalLocation() && newProblem->description() == problem->description() && newProblem->explanation() == problem->explanation()) return true; } return false; } void ProblemModel::setMessage(const QString& message) { setPlaceholderText(message, m_pathLocation, i18n("Cppcheck")); } void ProblemModel::addProblems(const QVector& problems) { static int maxLength = 0; if (m_problems.isEmpty()) { maxLength = 0; } for (auto problem : problems) { fixProblemFinalLocation(problem); if (problemExists(problem)) { continue; } m_problems.append(problem); addProblem(problem); // This performs adjusting of columns width in the ProblemsView if (maxLength < problem->description().length()) { maxLength = problem->description().length(); setProblems(m_problems); } } } void ProblemModel::setProblems() { setMessage(i18n("Analysis completed, no problems detected.")); setProblems(m_problems); } void ProblemModel::reset() { reset(nullptr, QString()); } void ProblemModel::reset(KDevelop::IProject* project, const QString& path) { m_project = project; m_path = path; m_pathLocation.document = KDevelop::IndexedString(m_path); clearProblems(); m_problems.clear(); QString tooltip; if (m_project) { setMessage(i18n("Analysis started...")); tooltip = i18nc("@info:tooltip %1 is the path of the file", "Re-run last Cppcheck analysis (%1)", prettyPathName(m_path)); } else { tooltip = i18nc("@info:tooltip", "Re-run last Cppcheck analysis"); } setFullUpdateTooltip(tooltip); } void ProblemModel::show() { problemModelSet()->showModel(Strings::problemModelId()); } void ProblemModel::forceFullUpdate() { if (m_project && !m_plugin->isRunning()) { m_plugin->runCppcheck(m_project, m_path); } } } diff --git a/plugins/custom-buildsystem/tests/kcmuitestmain.cpp b/plugins/custom-buildsystem/tests/kcmuitestmain.cpp index 776ad690ee..3a9fa4b40a 100644 --- a/plugins/custom-buildsystem/tests/kcmuitestmain.cpp +++ b/plugins/custom-buildsystem/tests/kcmuitestmain.cpp @@ -1,143 +1,143 @@ /************************************************************************ * KDevelop4 Custom Buildsystem Support * * * * Copyright 2012 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 #include #include #include #include -#include +#include #include #include #include #include #include #include #include #include "custombuildsystemconfigwidget.h" #include static const char description[] = I18N_NOOP("CustomBuildSystem Config Ui Test App"); static const char version[] = "0.1"; class State : public QObject { Q_OBJECT public: State( QDialogButtonBox* buttonBox, CustomBuildSystemConfigWidget* cfgWidget, KConfig* config, KDevelop::IProject* proj ) : buttonBox(buttonBox), configWidget(cfgWidget), cfg(config), project(proj) { connect(buttonBox, &QDialogButtonBox::clicked, this, &State::buttonClicked); connect(configWidget, &CustomBuildSystemConfigWidget::changed, this, &State::configChanged); } public Q_SLOTS: void buttonClicked(QAbstractButton* button) { if (button == buttonBox->button(QDialogButtonBox::Apply)) { apply(); } else if (button == buttonBox->button(QDialogButtonBox::Ok)) { ok(); } else if (button == buttonBox->button(QDialogButtonBox::Cancel)) { qApp->quit(); } } void apply() { configWidget->saveTo(cfg, project); buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } void ok() { apply(); qApp->quit(); } void configChanged() { buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } private: QDialogButtonBox* buttonBox; CustomBuildSystemConfigWidget* configWidget; KConfig* cfg; KDevelop::IProject* project; }; int main(int argc, char **argv) { QApplication app(argc, argv); KAboutData aboutData(QStringLiteral("kcm_uitest"), i18n("kcm_uitest"), version, i18n(description), KAboutLicense::GPL, i18n("(C) 2012 Andreas Pakulat")); aboutData.addAuthor( i18n("Andreas Pakulat"), QString(), QStringLiteral("apaku@gmx.de") ); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); QTemporaryDir tempdir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)+"/kdev-custom-uitest"); qCDebug(CUSTOMBUILDSYSTEM) << "created tempdir:" << tempdir.path(); KConfig projkcfg( tempdir.path() + "/kdev-custom-uitest.kdev4" ); QDir projdir(tempdir.path()); projdir.mkdir(QStringLiteral("includedir")); projdir.mkdir(QStringLiteral("subtree")); projdir.mkpath(QStringLiteral("subtree/includedir")); projdir.mkpath(QStringLiteral("subtree/deeptree")); projdir.mkpath(QStringLiteral("subtree/deeptree/includedir")); qCDebug(CUSTOMBUILDSYSTEM) << "project config:" << projkcfg.name(); QDialog dlg; QVBoxLayout mainLayout; dlg.setLayout(&mainLayout); KDevelop::TestProject proj; proj.setPath( KDevelop::Path(projkcfg.name())); CustomBuildSystemConfigWidget widget(nullptr); widget.loadFrom(&projkcfg); mainLayout.addWidget(&widget); dlg.setWindowTitle(QStringLiteral("Ui Test App for Config Widget")); QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel); buttonBox.button(QDialogButtonBox::Apply)->setEnabled(false); buttonBox.button(QDialogButtonBox::Ok)->setEnabled(false); State state(&buttonBox, &widget, &projkcfg, &proj ); dlg.resize(800, 600); dlg.show(); return app.exec(); } #include "kcmuitestmain.moc" diff --git a/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp b/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp index d6ca62934b..eb7ac30687 100644 --- a/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp +++ b/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp @@ -1,306 +1,307 @@ /************************************************************************ * * * 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 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 "projectpathswidget.h" -#include - #include #include #include #include -#include #include "../compilerprovider/compilerprovider.h" #include "../compilerprovider/settingsmanager.h" #include "ui_projectpathswidget.h" #include "ui_batchedit.h" #include "projectpathsmodel.h" #include +#include +#include + + using namespace KDevelop; namespace { enum PageType { IncludesPage, DefinesPage, ParserArgumentsPage }; } ProjectPathsWidget::ProjectPathsWidget( QWidget* parent ) : QWidget(parent), ui(new Ui::ProjectPathsWidget), pathsModel(new ProjectPathsModel(this)) { ui->setupUi( this ); // hack taken from kurlrequester, make the buttons a bit less in height so they better match the url-requester ui->addPath->setFixedHeight( ui->projectPaths->sizeHint().height() ); ui->removePath->setFixedHeight( ui->projectPaths->sizeHint().height() ); connect( ui->addPath, &QPushButton::clicked, this, &ProjectPathsWidget::addProjectPath ); connect( ui->removePath, &QPushButton::clicked, this, &ProjectPathsWidget::deleteProjectPath ); connect( ui->batchEdit, &QPushButton::clicked, this, &ProjectPathsWidget::batchEdit ); ui->projectPaths->setModel( pathsModel ); connect( ui->projectPaths, static_cast(&KComboBox::currentIndexChanged), this, &ProjectPathsWidget::projectPathSelected ); connect( pathsModel, &ProjectPathsModel::dataChanged, this, &ProjectPathsWidget::changed ); connect( pathsModel, &ProjectPathsModel::rowsInserted, this, &ProjectPathsWidget::changed ); connect( pathsModel, &ProjectPathsModel::rowsRemoved, this, &ProjectPathsWidget::changed ); connect( ui->compiler, static_cast(&QComboBox::activated), this, &ProjectPathsWidget::changed ); connect( ui->compiler, static_cast(&QComboBox::activated), this, &ProjectPathsWidget::changeCompilerForPath ); connect( ui->includesWidget, static_cast(&IncludesWidget::includesChanged), this, &ProjectPathsWidget::includesChanged ); connect( ui->definesWidget, static_cast(&DefinesWidget::definesChanged), this, &ProjectPathsWidget::definesChanged ); connect(ui->languageParameters, &QTabWidget::currentChanged, this, &ProjectPathsWidget::tabChanged); connect(ui->parserWidget, &ParserWidget::changed, this, &ProjectPathsWidget::parserArgumentsChanged); tabChanged(IncludesPage); } QVector ProjectPathsWidget::paths() const { return pathsModel->paths(); } void ProjectPathsWidget::setPaths( const QVector& paths ) { bool b = blockSignals( true ); clear(); pathsModel->setPaths( paths ); blockSignals( b ); ui->projectPaths->setCurrentIndex(0); // at least a project root item is present ui->languageParameters->setCurrentIndex(0); // Set compilers ui->compiler->clear(); auto settings = SettingsManager::globalInstance(); auto compilers = settings->provider()->compilers(); for (int i = 0 ; i < compilers.count(); ++i) { Q_ASSERT(compilers[i]); if (!compilers[i]) { continue; } ui->compiler->addItem(compilers[i]->name()); QVariant val; val.setValue(compilers[i]); ui->compiler->setItemData(i, val); } projectPathSelected(0); updateEnablements(); } void ProjectPathsWidget::definesChanged( const Defines& defines ) { qCDebug(DEFINESANDINCLUDES) << "defines changed"; updatePathsModel( QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole ); } void ProjectPathsWidget::includesChanged( const QStringList& includes ) { qCDebug(DEFINESANDINCLUDES) << "includes changed"; updatePathsModel( includes, ProjectPathsModel::IncludesDataRole ); } void ProjectPathsWidget::parserArgumentsChanged() { updatePathsModel(QVariant::fromValue(ui->parserWidget->parserArguments()), ProjectPathsModel::ParserArgumentsRole); } void ProjectPathsWidget::updatePathsModel(const QVariant& newData, int role) { QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0, QModelIndex() ); if( idx.isValid() ) { bool b = pathsModel->setData( idx, newData, role ); if( b ) { emit changed(); } } } void ProjectPathsWidget::projectPathSelected( int index ) { if( index < 0 && pathsModel->rowCount() > 0 ) { index = 0; } Q_ASSERT(index >= 0); const QModelIndex midx = pathsModel->index( index, 0 ); ui->includesWidget->setIncludes( pathsModel->data( midx, ProjectPathsModel::IncludesDataRole ).toStringList() ); ui->definesWidget->setDefines( pathsModel->data( midx, ProjectPathsModel::DefinesDataRole ).value() ); Q_ASSERT(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value()); ui->compiler->setCurrentText(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value()->name()); ui->parserWidget->setParserArguments(pathsModel->data(midx, ProjectPathsModel::ParserArgumentsRole).value()); updateEnablements(); } void ProjectPathsWidget::clear() { bool sigDisabled = ui->projectPaths->blockSignals( true ); pathsModel->setPaths({}); ui->includesWidget->clear(); ui->definesWidget->clear(); updateEnablements(); ui->projectPaths->blockSignals( sigDisabled ); } void ProjectPathsWidget::addProjectPath() { const QUrl directory = pathsModel->data(pathsModel->index(0, 0), ProjectPathsModel::FullUrlDataRole).toUrl(); QPointer dlg = new QFileDialog(this, i18n("Select Project Path"), directory.toLocalFile()); dlg->setFileMode(QFileDialog::Directory); dlg->setOption(QFileDialog::ShowDirsOnly); if (dlg->exec()) { pathsModel->addPath(dlg->selectedUrls().value(0)); ui->projectPaths->setCurrentIndex(pathsModel->rowCount() - 1); updateEnablements(); } delete dlg; } void ProjectPathsWidget::deleteProjectPath() { const QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0 ); if( KMessageBox::questionYesNo( this, i18n("Are you sure you want to remove the configuration for the path '%1'?", pathsModel->data( idx, Qt::DisplayRole ).toString() ), QStringLiteral("Remove Path Configuration") ) == KMessageBox::Yes ) { pathsModel->removeRows( ui->projectPaths->currentIndex(), 1 ); } updateEnablements(); } void ProjectPathsWidget::setProject(KDevelop::IProject* w_project) { pathsModel->setProject( w_project ); ui->includesWidget->setProject( w_project ); } void ProjectPathsWidget::updateEnablements() { // Disable removal of the project root entry which is always first in the list ui->removePath->setEnabled( ui->projectPaths->currentIndex() > 0 ); } void ProjectPathsWidget::batchEdit() { Ui::BatchEdit be; QPointer dialog = new QDialog(this); be.setupUi(dialog); const int index = qMax(ui->projectPaths->currentIndex(), 0); const QModelIndex midx = pathsModel->index(index, 0); if (!midx.isValid()) { return; } bool includesTab = ui->languageParameters->currentIndex() == 0; if (includesTab) { auto includes = pathsModel->data(midx, ProjectPathsModel::IncludesDataRole).toStringList(); be.textEdit->setPlainText(includes.join(QLatin1Char('\n'))); dialog->setWindowTitle(i18n("Edit include directories/files")); } else { auto defines = pathsModel->data(midx, ProjectPathsModel::DefinesDataRole).value(); for (auto it = defines.constBegin(); it != defines.constEnd(); it++) { be.textEdit->appendPlainText(it.key() + QLatin1Char('=') + it.value()); } dialog->setWindowTitle(i18n("Edit defined macros")); } if (dialog->exec() != QDialog::Accepted) { delete dialog; return; } if (includesTab) { auto includes = be.textEdit->toPlainText().split('\n', QString::SkipEmptyParts); for (auto& s : includes) { s = s.trimmed(); } pathsModel->setData(midx, includes, ProjectPathsModel::IncludesDataRole); } else { auto list = be.textEdit->toPlainText().split('\n', QString::SkipEmptyParts); Defines defines; for (auto& d : list) { //This matches: a=b, a=, a QRegExp r("^([^=]+)(=(.*))?$"); if (!r.exactMatch(d)) { continue; } defines[r.cap(1).trimmed()] = r.cap(3).trimmed(); } pathsModel->setData(midx, QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole); } projectPathSelected(index); delete dialog; } void ProjectPathsWidget::setCurrentCompiler(const QString& name) { for (int i = 0 ; i < ui->compiler->count(); ++i) { if(ui->compiler->itemText(i) == name) { ui->compiler->setCurrentIndex(i); } } } CompilerPointer ProjectPathsWidget::currentCompiler() const { return ui->compiler->itemData(ui->compiler->currentIndex()).value(); } void ProjectPathsWidget::tabChanged(int idx) { if (idx == ParserArgumentsPage) { ui->batchEdit->setVisible(false); ui->compilerBox->setVisible(true); ui->configureLabel->setText(i18n("Configure C/C++ parser")); } else { ui->batchEdit->setVisible(true); ui->compilerBox->setVisible(false); ui->configureLabel->setText(i18n("Configure which macros and include directories/files will be added to the parser during project parsing:")); } } void ProjectPathsWidget::changeCompilerForPath() { for (int idx = 0; idx < pathsModel->rowCount(); idx++) { const QModelIndex midx = pathsModel->index(idx, 0); if (pathsModel->data(midx, Qt::DisplayRole) == ui->projectPaths->currentText()) { pathsModel->setData(midx, QVariant::fromValue(currentCompiler()), ProjectPathsModel::CompilerDataRole); break; } } } diff --git a/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp b/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp index bb51b7e4f5..5e07b07ea9 100644 --- a/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp +++ b/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp @@ -1,77 +1,78 @@ /* * 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 "noprojectcustomincludepaths.h" #include "ui_noprojectcustomincludepaths.h" +#include + #include #include -#include NoProjectCustomIncludePaths::NoProjectCustomIncludePaths(QWidget* parent) : QDialog(parent), m_ui(new Ui::CustomIncludePaths) { m_ui->setupUi(this); m_ui->storageDirectory->setMode(KFile::Directory); setWindowTitle(i18n("Setup Custom Include Paths")); connect(m_ui->directorySelector, &QPushButton::clicked, this, &NoProjectCustomIncludePaths::openAddIncludeDirectoryDialog); } void NoProjectCustomIncludePaths::setStorageDirectory(const QString& path) { m_ui->storageDirectory->setUrl(QUrl::fromLocalFile(path)); } QString NoProjectCustomIncludePaths::storageDirectory() const { return m_ui->storageDirectory->url().toLocalFile(); } void NoProjectCustomIncludePaths::appendCustomIncludePath(const QString& path) { m_ui->customIncludePaths->appendPlainText(path); } QStringList NoProjectCustomIncludePaths::customIncludePaths() const { const QString pathsText = m_ui->customIncludePaths->document()->toPlainText(); const QStringList paths = pathsText.split('\n', QString::SkipEmptyParts); return paths; } void NoProjectCustomIncludePaths::setCustomIncludePaths(const QStringList& paths) { m_ui->customIncludePaths->setPlainText(paths.join(QLatin1Char('\n'))); } void NoProjectCustomIncludePaths::openAddIncludeDirectoryDialog() { const QString dirName = QFileDialog::getExistingDirectory(this, i18n("Select directory to include")); if (dirName.isEmpty()) return; appendCustomIncludePath(dirName); } diff --git a/plugins/custommake/custommakemanager.cpp b/plugins/custommake/custommakemanager.cpp index a70e28e124..56bbfa3d47 100644 --- a/plugins/custommake/custommakemanager.cpp +++ b/plugins/custommake/custommakemanager.cpp @@ -1,327 +1,328 @@ /* 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 using namespace KDevelop; class CustomMakeProvider : public IDefinesAndIncludesManager::BackgroundProvider { public: explicit 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 {} K_PLUGIN_FACTORY_WITH_JSON(CustomMakeSupportFactory, "kdevcustommakemanager.json", registerPlugin(); ) CustomMakeManager::CustomMakeManager( QObject *parent, const QVariantList& args ) : KDevelop::AbstractFileManagerPlugin( QStringLiteral("kdevcustommakemanager"), parent ) , m_provider(new CustomMakeProvider(this)) { Q_UNUSED(args) setXMLFile( QStringLiteral("kdevcustommakemanager.rc") ); // TODO use CustomMakeBuilder IPlugin* i = core()->pluginController()->pluginForExtension( QStringLiteral("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(); } QString CustomMakeManager::extraArguments(KDevelop::ProjectBaseItem*) const { return {}; } ProjectTargetItem* CustomMakeManager::createTarget(const QString& target, KDevelop::ProjectFolderItem *parent) { Q_UNUSED(target) Q_UNUSED(parent) 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 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/plugins/custommake/makefileresolver/makefileresolver.cpp b/plugins/custommake/makefileresolver/makefileresolver.cpp index 0a79fcb9a1..5e8cdc1bcf 100644 --- a/plugins/custommake/makefileresolver/makefileresolver.cpp +++ b/plugins/custommake/makefileresolver/makefileresolver.cpp @@ -1,641 +1,641 @@ /* * KDevelop C++ Language Support * * Copyright 2007 David Nolden * Copyright 2014 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 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 "makefileresolver.h" #include "helper.h" #include #include #include #include #include #include #include -#include +#include #include #include #include #include // #define VERBOSE #if defined(VERBOSE) #define ifTest(x) x #else #define ifTest(x) #endif const int maximumInternalResolutionDepth = 3; using namespace std; using namespace KDevelop; namespace { ///After how many seconds should we retry? static const int CACHE_FAIL_FOR_SECONDS = 200; static const int processTimeoutSeconds = 30; struct CacheEntry { CacheEntry() { } ModificationRevisionSet modificationTime; Path::List paths; Path::List frameworkDirectories; QHash defines; QString errorMessage, longErrorMessage; bool failed = false; QMap failedFiles; QDateTime failTime; }; typedef QMap Cache; static Cache s_cache; static QMutex s_cacheMutex; } /** * Compatibility: * make/automake: Should work perfectly * cmake: Thanks to the path-recursion, this works with cmake(tested with version "2.4-patch 6" * with kdelibs out-of-source and with kdevelop4 in-source) **/ class SourcePathInformation { public: explicit SourcePathInformation(const QString& path) : m_path(path) { } QString createCommand(const QString& absoluteFile, const QString& workingDirectory, const QString& makeParameters) const { QString relativeFile = Path(workingDirectory).relativePath(Path(absoluteFile)); #ifndef Q_OS_FREEBSD // GNU make implicitly enables "-w" for sub-makes, we don't want that QLatin1String noPrintDirFlag = QLatin1String(" --no-print-directory"); #else QLatin1String noPrintDirFlag; #endif return "make -k" + noPrintDirFlag + " -W \'" + absoluteFile + "\' -W \'" + relativeFile + "\' -n " + makeParameters; } bool hasMakefile() const { QFileInfo makeFile(m_path, QStringLiteral("Makefile")); return makeFile.exists(); } QStringList possibleTargets(const QString& targetBaseName) const { const QStringList ret{ ///@todo open the make-file, and read the target-names from there. //It would be nice if all targets could be processed in one call, the problem is the exit-status of make, so for now make has to be called multiple times. targetBaseName + QLatin1String(".o"), targetBaseName + QLatin1String(".lo"), //ret << targetBaseName + ".lo " + targetBaseName + ".o"; targetBaseName + QLatin1String(".ko"), }; return ret; } private: QString m_path; }; static void mergePaths(KDevelop::Path::List& destList, const KDevelop::Path::List& srcList) { for (const Path& path : srcList) { if (!destList.contains(path)) destList.append(path); } } void PathResolutionResult::mergeWith(const PathResolutionResult& rhs) { mergePaths(paths, rhs.paths); mergePaths(frameworkDirectories, rhs.frameworkDirectories); includePathDependency += rhs.includePathDependency; defines.unite(rhs.defines); } PathResolutionResult::PathResolutionResult(bool success, const QString& errorMessage, const QString& longErrorMessage) : success(success) , errorMessage(errorMessage) , longErrorMessage(longErrorMessage) {} PathResolutionResult::operator bool() const { return success; } ModificationRevisionSet MakeFileResolver::findIncludePathDependency(const QString& file) { QString oldSourceDir = m_source; QString oldBuildDir = m_build; Path currentWd(mapToBuild(file)); ModificationRevisionSet rev; while (currentWd.hasParent()) { currentWd = currentWd.parent(); QString path = currentWd.toLocalFile(); QFileInfo makeFile(QDir(path), QStringLiteral("Makefile")); if (makeFile.exists()) { IndexedString makeFileStr(makeFile.filePath()); rev.addModificationRevision(makeFileStr, ModificationRevision::revisionForFile(makeFileStr)); break; } } setOutOfSourceBuildSystem(oldSourceDir, oldBuildDir); return rev; } bool MakeFileResolver::executeCommand(const QString& command, const QString& workingDirectory, QString& result) const { ifTest(cout << "executing " << command.toUtf8().constData() << endl); ifTest(cout << "in " << workingDirectory.toUtf8().constData() << endl); KProcess proc; proc.setWorkingDirectory(workingDirectory); proc.setOutputChannelMode(KProcess::MergedChannels); QStringList args(command.split(' ')); QString prog = args.takeFirst(); proc.setProgram(prog, args); int status = proc.execute(processTimeoutSeconds * 1000); result = proc.readAll(); return status == 0; } MakeFileResolver::MakeFileResolver() { } ///More efficient solution: Only do exactly one call for each directory. During that call, mark all source-files as changed, and make all targets for those files. PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file) { if (file.isEmpty()) { // for unit tests with temporary files return PathResolutionResult(); } QFileInfo fi(file); return resolveIncludePath(fi.fileName(), fi.absolutePath()); } QString MakeFileResolver::mapToBuild(const QString &path) const { QString wd = QDir::cleanPath(path); if (m_outOfSource) { if (wd.startsWith(m_source) && !wd.startsWith(m_build)) { //Move the current working-directory out of source, into the build-system wd = QDir::cleanPath(m_build + '/' + wd.midRef(m_source.length())); } } return wd; } void MakeFileResolver::clearCache() { QMutexLocker l(&s_cacheMutex); s_cache.clear(); } PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file, const QString& _workingDirectory, int maxStepsUp) { //Prefer this result when returning a "fail". The include-paths of this result will always be added. PathResolutionResult resultOnFail; if (m_isResolving) return PathResolutionResult(false, i18n("Tried include path resolution while another resolution process was still running")); //Make the working-directory absolute QString workingDirectory = _workingDirectory; if (QFileInfo(workingDirectory).isRelative()) { QUrl u = QUrl::fromLocalFile(QDir::currentPath()); if (workingDirectory == QLatin1String(".")) workingDirectory = QString(); else if (workingDirectory.startsWith(QLatin1String("./"))) workingDirectory.remove(0, 2); if (!workingDirectory.isEmpty()) { u = u.adjusted(QUrl::StripTrailingSlash); u.setPath(u.path() + '/' + workingDirectory); } workingDirectory = u.toLocalFile(); } else workingDirectory = _workingDirectory; ifTest(cout << "working-directory: " << workingDirectory.toLocal8Bit().data() << " file: " << file.toLocal8Bit().data() << std::endl;) QDir sourceDir(workingDirectory); QDir dir = QDir(mapToBuild(sourceDir.absolutePath())); QFileInfo makeFile(dir, QStringLiteral("Makefile")); if (!makeFile.exists()) { if (maxStepsUp > 0) { //If there is no makefile in this directory, go one up and re-try from there QFileInfo fileName(file); QString localName = sourceDir.dirName(); if (sourceDir.cdUp() && !fileName.isAbsolute()) { const QString checkFor = localName + QLatin1Char('/') + file; PathResolutionResult oneUp = resolveIncludePath(checkFor, sourceDir.path(), maxStepsUp-1); if (oneUp.success) { oneUp.mergeWith(resultOnFail); return oneUp; } } } if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Makefile is missing in folder \"%1\"", dir.absolutePath()), i18n("Problem while trying to resolve include paths for %1", file)); } PushValue e(m_isResolving, true); Path::List cachedPaths; //If the call doesn't succeed, use the cached not up-to-date version Path::List cachedFWDirs; QHash cachedDefines; ModificationRevisionSet dependency; dependency.addModificationRevision(IndexedString(makeFile.filePath()), ModificationRevision::revisionForFile(IndexedString(makeFile.filePath()))); dependency += resultOnFail.includePathDependency; Cache::iterator it; { QMutexLocker l(&s_cacheMutex); it = s_cache.find(dir.path()); if (it != s_cache.end()) { cachedPaths = it->paths; cachedFWDirs = it->frameworkDirectories; cachedDefines = it->defines; if (dependency == it->modificationTime) { if (!it->failed) { //We have a valid cached result PathResolutionResult ret(true); ret.paths = it->paths; ret.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(resultOnFail); return ret; } else { //We have a cached failed result. We should use that for some time but then try again. Return the failed result if: (there were too many tries within this folder OR this file was already tried) AND The last tries have not expired yet if (/*(it->failedFiles.size() > 3 || it->failedFiles.find(file) != it->failedFiles.end()) &&*/ it->failTime.secsTo(QDateTime::currentDateTime()) < CACHE_FAIL_FOR_SECONDS) { PathResolutionResult ret(false); //Fake that the result is ok ret.errorMessage = i18n("Cached: %1", it->errorMessage); ret.longErrorMessage = it->longErrorMessage; ret.paths = it->paths; ret.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(resultOnFail); return ret; } else { //Try getting a correct result again } } } } } ///STEP 1: Prepare paths QString targetName; QFileInfo fi(file); QString absoluteFile = file; if (fi.isRelative()) absoluteFile = workingDirectory + '/' + file; absoluteFile = QDir::cleanPath(absoluteFile); int dot; if ((dot = file.lastIndexOf('.')) == -1) { if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Filename %1 seems to be malformed", file)); } targetName = file.left(dot); QString wd = dir.path(); if (QFileInfo(wd).isRelative()) { wd = QDir::cleanPath(QDir::currentPath() + '/' + wd); } wd = mapToBuild(wd); SourcePathInformation source(wd); QStringList possibleTargets = source.possibleTargets(targetName); ///STEP 3: Try resolving the paths, by using once the absolute and once the relative file-path. Which kind is required differs from setup to setup. ///STEP 3.1: Try resolution using the absolute path PathResolutionResult res; //Try for each possible target res = resolveIncludePathInternal(absoluteFile, wd, possibleTargets.join(QLatin1Char(' ')), source, maximumInternalResolutionDepth); if (!res) { ifTest(cout << "Try for absolute file " << absoluteFile.toLocal8Bit().data() << " and targets " << possibleTargets.join(", ").toLocal8Bit().data() << " failed: " << res.longErrorMessage.toLocal8Bit().data() << endl;) } res.includePathDependency = dependency; if (res.paths.isEmpty()) { res.paths = cachedPaths; //We failed, maybe there is an old cached result, use that. res.defines = cachedDefines; } // a build command could contain only one or more -iframework or -F specifications. if (res.frameworkDirectories.isEmpty()) { res.frameworkDirectories = cachedFWDirs; } { QMutexLocker l(&s_cacheMutex); if (it == s_cache.end()) it = s_cache.insert(dir.path(), CacheEntry()); CacheEntry& ce(*it); ce.paths = res.paths; ce.frameworkDirectories = res.frameworkDirectories; ce.modificationTime = dependency; if (!res) { ce.failed = true; ce.errorMessage = res.errorMessage; ce.longErrorMessage = res.longErrorMessage; ce.failTime = QDateTime::currentDateTime(); ce.failedFiles[file] = true; } else { ce.failed = false; ce.failedFiles.clear(); } } if (!res && (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty())) return resultOnFail; return res; } static QRegularExpression includeRegularExpression() { static const QRegularExpression expression( "\\s(--include-dir=|-I\\s*|-isystem\\s+|-iframework\\s+|-F\\s*)(" "\\'.*\\'|\\\".*\\\"" //Matches "hello", 'hello', 'hello"hallo"', etc. "|" "((?:\\\\.)?([\\S^\\\\]?))+" //Matches /usr/I\ am\ a\ strange\ path/include ")(?=\\s)" ); Q_ASSERT(expression.isValid()); return expression; } PathResolutionResult MakeFileResolver::resolveIncludePathInternal(const QString& file, const QString& workingDirectory, const QString& makeParameters, const SourcePathInformation& source, int maxDepth) { --maxDepth; if (maxDepth < 0) return PathResolutionResult(false); QString fullOutput; executeCommand(source.createCommand(file, workingDirectory, makeParameters), workingDirectory, fullOutput); { QRegExp newLineRx("\\\\\\n"); fullOutput.remove(newLineRx); } ///@todo collect multiple outputs at the same time for performance-reasons QString firstLine = fullOutput; int lineEnd; if ((lineEnd = fullOutput.indexOf('\n')) != -1) firstLine.truncate(lineEnd); //Only look at the first line of output /** * There's two possible cases this can currently handle. * 1.: gcc is called, with the parameters we are searching for (so we parse the parameters) * 2.: A recursive make is called, within another directory(so we follow the recursion and try again) "cd /foo/bar && make -f pi/pa/build.make pi/pa/po.o * */ ///STEP 1: Test if it is a recursive make-call // Do not search for recursive make-calls if we already have include-paths available. Happens in kernel modules. if (!includeRegularExpression().match(fullOutput).hasMatch()) { QRegExp makeRx("\\bmake\\s"); int offset = 0; while ((offset = makeRx.indexIn(firstLine, offset)) != -1) { QString prefix = firstLine.left(offset).trimmed(); if (prefix.endsWith(QLatin1String("&&")) || prefix.endsWith(';') || prefix.isEmpty()) { QString newWorkingDirectory = workingDirectory; ///Extract the new working-directory if (!prefix.isEmpty()) { if (prefix.endsWith(QLatin1String("&&"))) prefix.truncate(prefix.length() - 2); else if (prefix.endsWith(';')) prefix.truncate(prefix.length() - 1); ///Now test if what we have as prefix is a simple "cd /foo/bar" call. //In cases like "cd /media/data/kdedev/4.0/build/kdevelop && cd /media/data/kdedev/4.0/build/kdevelop" //We use the second directory. For t hat reason we search for the last index of "cd " int cdIndex = prefix.lastIndexOf(QLatin1String("cd ")); if (cdIndex != -1) { newWorkingDirectory = prefix.mid(cdIndex + 3).trimmed(); if (QFileInfo(newWorkingDirectory).isRelative()) newWorkingDirectory = workingDirectory + '/' + newWorkingDirectory; newWorkingDirectory = QDir::cleanPath(newWorkingDirectory); } } if (newWorkingDirectory == workingDirectory) { return PathResolutionResult(false, i18n("Failed to extract new working directory"), i18n("Output was: %1", fullOutput)); } QFileInfo d(newWorkingDirectory); if (d.exists()) { ///The recursive working-directory exists. QString makeParams = firstLine.mid(offset+5); if (!makeParams.contains(';') && !makeParams.contains(QLatin1String("&&"))) { ///Looks like valid parameters ///Make the file-name absolute, so it can be referenced from any directory QString absoluteFile = file; if (QFileInfo(absoluteFile).isRelative()) absoluteFile = workingDirectory + '/' + file; Path absolutePath(absoluteFile); ///Try once with absolute, and if that fails with relative path of the file SourcePathInformation newSource(newWorkingDirectory); PathResolutionResult res = resolveIncludePathInternal(absolutePath.toLocalFile(), newWorkingDirectory, makeParams, newSource, maxDepth); if (res) return res; return resolveIncludePathInternal(Path(newWorkingDirectory).relativePath(absolutePath), newWorkingDirectory, makeParams , newSource, maxDepth); }else{ return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The parameter string \"%1\" does not seem to be valid. Output was: %2.", makeParams, fullOutput)); } } else { return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The directory \"%1\" does not exist. Output was: %2.", newWorkingDirectory, fullOutput)); } } else { return PathResolutionResult(false, i18n("Malformed recursive make call"), i18n("Output was: %1", fullOutput)); } ++offset; if (offset >= firstLine.length()) break; } } ///STEP 2: Search the output for include-paths PathResolutionResult ret = processOutput(fullOutput, workingDirectory); if (ret.paths.isEmpty() && ret.frameworkDirectories.isEmpty()) return PathResolutionResult(false, i18n("Could not extract include paths from make output"), i18n("Folder: \"%1\" Command: \"%2\" Output: \"%3\"", workingDirectory, source.createCommand(file, workingDirectory, makeParameters), fullOutput)); return ret; } QRegularExpression MakeFileResolver::defineRegularExpression() { static const QRegularExpression pattern( QStringLiteral("-D([^\\s=]+)(?:=(?:\"(.*?)(? 2)) { //probable a quoted path if (path.endsWith(path.leftRef(1))) { //Quotation is ok, remove it path = path.mid(1, path.length() - 2); } } if (QDir::isRelativePath(path)) path = workingDirectory + '/' + path; const auto& internedPath = internPath(path); const auto& type = match.captured(1); const auto isFramework = type.startsWith(QLatin1String("-iframework")) || type.startsWith(QLatin1String("-F")); if (isFramework) { ret.frameworkDirectories << internedPath; } else { ret.paths << internedPath; } } } { const auto& defineRx = defineRegularExpression(); auto it = defineRx.globalMatch(fullOutput); while (it.hasNext()) { const auto match = it.next(); QString value; if (match.lastCapturedIndex() > 1) { value = unescape(match.capturedRef(match.lastCapturedIndex())); } ret.defines[internString(match.captured(1))] = internString(value); } } return ret; } void MakeFileResolver::resetOutOfSourceBuild() { m_outOfSource = false; } void MakeFileResolver::setOutOfSourceBuildSystem(const QString& source, const QString& build) { if (source == build) { resetOutOfSourceBuild(); return; } m_outOfSource = true; m_source = QDir::cleanPath(source); m_build = QDir::cleanPath(m_build); } Path MakeFileResolver::internPath(const QString& path) const { Path& ret = m_pathCache[path]; if (ret.isEmpty() != path.isEmpty()) { ret = Path(path); } return ret; } QString MakeFileResolver::internString(const QString& path) const { auto it = m_stringCache.constFind(path); if (it != m_stringCache.constEnd()) { return *it; } m_stringCache.insert(path); return path; } // kate: indent-width 2; tab-width 2; diff --git a/plugins/cvs/cvsplugin.h b/plugins/cvs/cvsplugin.h index b7fd987deb..50f37c0b42 100644 --- a/plugins/cvs/cvsplugin.h +++ b/plugins/cvs/cvsplugin.h @@ -1,137 +1,138 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * 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 KDEVPLATFORM_PLUGIN_CVSPLUGIN_H #define KDEVPLATFORM_PLUGIN_CVSPLUGIN_H -#include -#include - #include #include #include -#include + +#include + +#include +#include class CvsProxy; /** * This is the main class of KDevelop's CVS plugin. * * It implements the IVersionControl interface. * * @author Robert Gruber */ class CvsPlugin : public KDevelop::IPlugin, public KDevelop::ICentralizedVersionControl { Q_OBJECT Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::ICentralizedVersionControl) friend class CvsProxy; public: explicit CvsPlugin(QObject *parent, const QVariantList & args = QVariantList()); ~CvsPlugin() override; void unload() override; QString name() const override; KDevelop::VcsImportMetadataWidget* createImportMetadataWidget(QWidget* parent) override; // From KDevelop::IPlugin KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; // Begin: KDevelop::IBasicVersionControl bool isValidRemoteRepositoryUrl(const QUrl& remoteLocation) override; bool isVersionControlled(const QUrl& localLocation) override; KDevelop::VcsJob* repositoryLocation(const QUrl& localLocation) override; KDevelop::VcsJob* add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) override; KDevelop::VcsJob* remove(const QList& localLocations) override; KDevelop::VcsJob* copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) override; KDevelop::VcsJob* move(const QUrl& localLocationSrc, const QUrl& localLocationDst) override; KDevelop::VcsJob* status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) override; KDevelop::VcsJob* revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) override; KDevelop::VcsJob* update(const QList& localLocations, const KDevelop::VcsRevision& rev, KDevelop::IBasicVersionControl::RecursionMode recursion) override; KDevelop::VcsJob* commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) override; KDevelop::VcsJob* diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::IBasicVersionControl::RecursionMode = KDevelop::IBasicVersionControl::Recursive) override; KDevelop::VcsJob* log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long limit) override; KDevelop::VcsJob* log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) override; KDevelop::VcsJob* annotate(const QUrl& localLocation, const KDevelop::VcsRevision& rev) override; KDevelop::VcsJob* resolve(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) override; KDevelop::VcsJob* createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl & destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override; // End: KDevelop::IBasicVersionControl // Begin: KDevelop::ICentralizedVersionControl KDevelop::VcsJob* edit(const QUrl& localLocation) override; KDevelop::VcsJob* unedit(const QUrl& localLocation) override; KDevelop::VcsJob* localRevision(const QUrl& localLocation, KDevelop::VcsRevision::RevisionType) override; KDevelop::VcsJob* import(const QString& commitMessage, const QUrl& sourceDirectory, const KDevelop::VcsLocation& destinationRepository) override; // End: KDevelop::ICentralizedVersionControl CvsProxy* proxy(); const QUrl urlFocusedDocument() const; KDevelop::VcsLocationWidget* vcsLocation(QWidget* parent) const override; public Q_SLOTS: // slots for context menu void ctxEdit(); void ctxUnEdit(); void ctxEditors(); // slots for menu void slotImport(); void slotCheckout(); void slotStatus(); Q_SIGNALS: /** * Some actions like commit, add, remove... will connect the job's * result() signal to this signal. Anybody, like for instance the * CvsMainView class, that is interested in getting notified about * jobs that finished can connect to this signal. * @see class CvsMainView */ void jobFinished(KJob* job); /** * Gets emmited when a job like log, editors... was created. * CvsPlugin will connect the newly created view to the result() signal * of a job. So the new view will show the output of that job as * soon as it has finished. */ void addNewTabToMainView(QWidget* tab, const QString& label); private: void setupActions(); QString findWorkingDir(const QUrl& location); private: const QScopedPointer d; }; #endif diff --git a/plugins/cvs/cvsproxy.cpp b/plugins/cvs/cvsproxy.cpp index b5d13f4ccc..5533e8c8a6 100644 --- a/plugins/cvs/cvsproxy.cpp +++ b/plugins/cvs/cvsproxy.cpp @@ -1,480 +1,480 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * 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 "cvsproxy.h" #include #include #include #include #include #include -#include +#include #include "cvsjob.h" #include "cvsannotatejob.h" #include "cvslogjob.h" #include "cvsstatusjob.h" #include "cvsdiffjob.h" #include #include CvsProxy::CvsProxy(KDevelop::IPlugin* parent) : QObject(parent), vcsplugin(parent) { } CvsProxy::~CvsProxy() { } bool CvsProxy::isValidDirectory(const QUrl& dirPath) const { const QFileInfo fsObject( dirPath.toLocalFile() ); QDir dir = fsObject.isDir() ? QDir(fsObject.filePath()) : fsObject.dir(); return dir.exists(QStringLiteral("CVS")); } bool CvsProxy::isVersionControlled(const QUrl& filePath) const { const QFileInfo fsObject( filePath.toLocalFile() ); QDir dir = fsObject.isDir() ? QDir(fsObject.filePath()) : fsObject.dir(); if( !dir.cd(QStringLiteral("CVS")) ) return false; if( fsObject.isDir() ) return true; QFile cvsEntries( dir.absoluteFilePath(QStringLiteral("Entries")) ); cvsEntries.open( QIODevice::ReadOnly ); QString cvsEntriesData = cvsEntries.readAll(); cvsEntries.close(); return ( cvsEntriesData.indexOf( fsObject.fileName() ) != -1 ); } bool CvsProxy::prepareJob(CvsJob* job, const QString& repository, enum RequestedOperation op) { // Only do this check if it's a normal operation like diff, log ... // For other operations like "cvs import" isValidDirectory() would fail as the // directory is not yet under CVS control if (op == CvsProxy::NormalOperation && !isValidDirectory(QUrl::fromLocalFile(repository))) { qCDebug(PLUGIN_CVS) << repository << " is not a valid CVS repository"; return false; } // clear commands and args from a possible previous run job->clear(); // setup the working directory for the new job job->setDirectory(repository); return true; } bool CvsProxy::addFileList(CvsJob* job, const QString& repository, const QList& urls) { QStringList args; QDir repoDir(repository); for (const QUrl& url : urls) { ///@todo this is ok for now, but what if some of the urls are not /// to the given repository const QString file = repoDir.relativeFilePath(url.toLocalFile()); args << KShell::quoteArg( file ); } *job << args; return true; } QString CvsProxy::convertVcsRevisionToString(const KDevelop::VcsRevision & rev) { QString str; switch (rev.revisionType()) { case KDevelop::VcsRevision::Special: break; case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) str = "-r"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::Date: if (rev.revisionValue().isValid()) str = "-D"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::GlobalNumber: // !! NOT SUPPORTED BY CVS !! default: break; } return str; } QString CvsProxy::convertRevisionToPrevious(const KDevelop::VcsRevision& rev) { QString str; // this only works if the revision is a real revisionnumber and not a date or special switch (rev.revisionType()) { case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) { QString orig = rev.revisionValue().toString(); // First we need to find the base (aka branch-part) of the revision number which will not change QString base(orig); base.truncate(orig.lastIndexOf(QLatin1Char('.'))); // next we need to cut off the last part of the revision number // this number is a count of revisions with a branch // so if we want to diff to the previous we just need to lower it by one int number = orig.midRef(orig.lastIndexOf(QLatin1Char('.'))+1).toInt(); if (number > 1) // of course this is only possible if our revision is not the first on the branch number--; str = QStringLiteral("-r") + base + '.' + QString::number(number); qCDebug(PLUGIN_CVS) << "Converted revision "<(); if (specialtype == KDevelop::VcsRevision::Previous) { rA = convertRevisionToPrevious(revB); } } else { rA = convertVcsRevisionToString(revA); } if (!rA.isEmpty()) *job << rA; QString rB = convertVcsRevisionToString(revB); if (!rB.isEmpty()) *job << rB; // in case the QUrl is a directory there is no filename if (!info.fileName().isEmpty()) *job << KShell::quoteArg(info.fileName()); return job; } delete job; return nullptr; } CvsJob * CvsProxy::annotate(const QUrl& url, const KDevelop::VcsRevision& rev) { QFileInfo info(url.toLocalFile()); CvsAnnotateJob* job = new CvsAnnotateJob(vcsplugin); if ( prepareJob(job, info.absolutePath()) ) { *job << "cvs"; *job << "annotate"; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; *job << KShell::quoteArg(info.fileName()); return job; } delete job; return nullptr; } CvsJob* CvsProxy::edit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "edit"; addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob* CvsProxy::unedit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "unedit"; addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob* CvsProxy::editors(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "editors"; addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob* CvsProxy::commit(const QString& repo, const QList& files, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "commit"; *job << "-m"; *job << KShell::quoteArg( message ); addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob* CvsProxy::add(const QString& repo, const QList& files, bool recursiv, bool binary) { Q_UNUSED(recursiv); // FIXME recursiv is not implemented yet CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "add"; if (binary) { *job << "-kb"; } addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob * CvsProxy::remove(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "remove"; *job << "-f"; //existing files will be deleted addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob * CvsProxy::update(const QString& repo, const QList& files, const KDevelop::VcsRevision & rev, const QString & updateOptions, bool recursive, bool pruneDirs, bool createDirs) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "update"; if (recursive) *job << "-R"; else *job << "-L"; if (pruneDirs) *job << "-P"; if (createDirs) *job << "-d"; if (!updateOptions.isEmpty()) *job << updateOptions; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; addFileList(job, repo, files); return job; } delete job; return nullptr; } CvsJob * CvsProxy::import(const QUrl& directory, const QString & server, const QString & repositoryName, const QString & vendortag, const QString & releasetag, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, directory.toLocalFile(), CvsProxy::Import) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d"; *job << server; *job << "import"; *job << "-m"; *job << KShell::quoteArg( message ); *job << repositoryName; *job << vendortag; *job << releasetag; return job; } delete job; return nullptr; } CvsJob * CvsProxy::checkout(const QUrl& targetDir, const QString & server, const QString & module, const QString & checkoutOptions, const QString & revision, bool recursive, bool pruneDirs) { CvsJob* job = new CvsJob(vcsplugin); ///@todo when doing a checkout we don't have the targetdir yet, /// for now it'll work to just run the command from the root if ( prepareJob(job, QStringLiteral("/"), CvsProxy::CheckOut) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d" << server; *job << "checkout"; if (!checkoutOptions.isEmpty()) *job << checkoutOptions; if (!revision.isEmpty()) { *job << "-r" << revision; } if (pruneDirs) *job << "-P"; if (!recursive) *job << "-l"; *job << "-d" << targetDir.toString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); *job << module; return job; } delete job; return nullptr; } CvsJob * CvsProxy::status(const QString & repo, const QList & files, bool recursive, bool taginfo) { CvsStatusJob* job = new CvsStatusJob(vcsplugin); job->setCommunicationMode( KProcess::MergedChannels ); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "status"; if (recursive) *job << "-R"; else *job << "-l"; if (taginfo) *job << "-v"; addFileList(job, repo, files); return job; } delete job; return nullptr; } diff --git a/plugins/executeplasmoid/executeplasmoidplugin.cpp b/plugins/executeplasmoid/executeplasmoidplugin.cpp index 96c40a5cff..52ab672004 100644 --- a/plugins/executeplasmoid/executeplasmoidplugin.cpp +++ b/plugins/executeplasmoid/executeplasmoidplugin.cpp @@ -1,96 +1,98 @@ /* * 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 +#include + +#include + using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(KDevExecutePlasmoidFactory,"kdevexecuteplasmoid.json", registerPlugin(); ) ExecutePlasmoidPlugin::ExecutePlasmoidPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevexecuteplasmoid"), parent) { 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 = 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::environmentProfileName(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/plugins/executeplasmoid/plasmoidexecutionjob.cpp b/plugins/executeplasmoid/plasmoidexecutionjob.cpp index 3415a83d94..ead0f4d3bb 100644 --- a/plugins/executeplasmoid/plasmoidexecutionjob.cpp +++ b/plugins/executeplasmoid/plasmoidexecutionjob.cpp @@ -1,148 +1,148 @@ /* 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 "plasmoidexecutionjob.h" #include "executeplasmoidplugin.h" #include #include #include -#include +#include #include #include #include #include #include #include #include #include using namespace KDevelop; PlasmoidExecutionJob::PlasmoidExecutionJob(ExecutePlasmoidPlugin* iface, ILaunchConfiguration* cfg) : OutputJob( iface ) { QString identifier = cfg->config().readEntry("PlasmoidIdentifier", ""); Q_ASSERT(!identifier.isEmpty()); setToolTitle(i18n("Plasmoid Viewer")); setCapabilities(Killable); setStandardToolView( IOutputView::RunView ); setBehaviours(IOutputView::AllowUserClose | IOutputView::AutoScroll ); setObjectName("plasmoidviewer "+identifier); setDelegate(new OutputDelegate); m_process = new CommandExecutor(executable(cfg), this); m_process->setArguments( arguments(cfg) ); m_process->setWorkingDirectory(workingDirectory(cfg)); OutputModel* model = new OutputModel(QUrl::fromLocalFile(m_process->workingDirectory()), this); model->setFilteringStrategy(OutputModel::CompilerFilter); setModel( model ); connect(m_process, &CommandExecutor::receivedStandardError, model, &OutputModel::appendLines ); connect(m_process, &CommandExecutor::receivedStandardOutput, model, &OutputModel::appendLines ); connect( m_process, &CommandExecutor::failed, this, &PlasmoidExecutionJob::slotFailed ); connect( m_process, &CommandExecutor::completed, this, &PlasmoidExecutionJob::slotCompleted ); } void PlasmoidExecutionJob::start() { startOutput(); model()->appendLine( m_process->workingDirectory() + "> " + m_process->command() + ' ' + m_process->arguments().join(QLatin1Char(' ')) ); m_process->start(); } bool PlasmoidExecutionJob::doKill() { m_process->kill(); model()->appendLine( i18n("** Killed **") ); return true; } OutputModel* PlasmoidExecutionJob::model() { return qobject_cast( OutputJob::model() ); } void PlasmoidExecutionJob::slotCompleted(int code) { if( code != 0 ) { setError( FailedShownError ); model()->appendLine( i18n("*** Failed ***") ); } else { model()->appendLine( i18n("*** Finished ***") ); } emitResult(); } void PlasmoidExecutionJob::slotFailed(QProcess::ProcessError error) { setError(error); // FIXME need more detail setErrorText(i18n("Plasmoid failed to execute on %1", m_process->workingDirectory())); model()->appendLine( i18n("*** Failed ***") ); emitResult(); } QString PlasmoidExecutionJob::executable(ILaunchConfiguration*) { return QStandardPaths::findExecutable(QStringLiteral("plasmoidviewer")); } QStringList PlasmoidExecutionJob::arguments(ILaunchConfiguration* cfg) { QStringList arguments = cfg->config().readEntry("Arguments", QStringList()); if(workingDirectory(cfg) == QDir::tempPath()) { QString identifier = cfg->config().readEntry("PlasmoidIdentifier", ""); arguments += QStringLiteral("-a"); arguments += identifier; } else { arguments += { "-a", "." }; } return arguments; } QString PlasmoidExecutionJob::workingDirectory(ILaunchConfiguration* cfg) { QString workingDirectory; IProject* p = cfg->project(); if(p) { QString identifier = cfg->config().readEntry("PlasmoidIdentifier", ""); QString possiblePath = Path(p->path(), identifier).toLocalFile(); if(QFileInfo(possiblePath).isDir()) { workingDirectory = possiblePath; } } if(workingDirectory.isEmpty()) { workingDirectory = QDir::tempPath(); } return workingDirectory; } diff --git a/plugins/filetemplates/outputpage.h b/plugins/filetemplates/outputpage.h index b195e7373c..facd94f03e 100644 --- a/plugins/filetemplates/outputpage.h +++ b/plugins/filetemplates/outputpage.h @@ -1,89 +1,90 @@ /* This file is part of KDevelop Copyright 2008 Hamish Rodda 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 KDEVPLATFORM_PLUGIN_OUTPUTPAGE_H #define KDEVPLATFORM_PLUGIN_OUTPUTPAGE_H +#include + #include #include -#include #include "ipagefocus.h" namespace KDevelop { class TemplateRenderer; class SourceFileTemplate; class CreateClassAssistant; /** * Assistant page for setting the output location of generated source code */ class OutputPage : public QWidget, public IPageFocus { Q_OBJECT public: explicit OutputPage(QWidget* parent); ~OutputPage() override; /** * Creates form widgets according to the number of output files of the template @p fileTemplate. * File identifiers and labels are read from @p fileTemplate, but not the actual URLs. * * This function is useful to prevent UI flickering that occurs when adding widgets while the page is visible. * It can be called immediately after the template is selected, before the user specified anything for the generated code. */ void prepareForm(const KDevelop::SourceFileTemplate& fileTemplate); /** * Loads file URLs from the template @p fileTemplate. * This function only sets URLs and file positions to widgets created by prepareForm(), * so be sure to call prepareForm() before calling this function. * * @param fileTemplate the template archive with the generated files * @param baseUrl the directory where the files are to be generated * @param renderer used to render any variables in output URLs */ void loadFileTemplate(const KDevelop::SourceFileTemplate& fileTemplate, const QUrl& baseUrl, KDevelop::TemplateRenderer* renderer); /** * Returns the file URLs, as specified by the user. */ QHash fileUrls() const; /** * Returns the positions within files where code is to be generated. */ QHash filePositions() const; void setFocusToFirstEditWidget() override; Q_SIGNALS: /** * @copydoc ClassIdentifierPage::isValid */ void isValid(bool valid); private: friend struct OutputPagePrivate; struct OutputPagePrivate* const d; }; } #endif // KDEVPLATFORM_PLUGIN_OUTPUTPAGE_H diff --git a/plugins/filetemplates/templatepreview.cpp b/plugins/filetemplates/templatepreview.cpp index 7cba5d39c5..3a5a1aee53 100644 --- a/plugins/filetemplates/templatepreview.cpp +++ b/plugins/filetemplates/templatepreview.cpp @@ -1,152 +1,152 @@ /* * This file is part of KDevelop * Copyright 2012 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) 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 "templatepreview.h" #include #include #include #include #include #include #include #include #include -#include +#include using namespace KDevelop; TemplatePreviewRenderer::TemplatePreviewRenderer() { QVariantHash vars; vars[QStringLiteral("name")] = "Example"; vars[QStringLiteral("license")] = "This file is licensed under the ExampleLicense 3.0"; // TODO: More variables, preferably the ones from TemplateClassGenerator VariableDescriptionList publicMembers; VariableDescriptionList protectedMembers; VariableDescriptionList privateMembers; publicMembers << VariableDescription(QStringLiteral("int"), QStringLiteral("number")); protectedMembers << VariableDescription(QStringLiteral("string"), QStringLiteral("name")); privateMembers << VariableDescription(QStringLiteral("float"), QStringLiteral("variable")); vars[QStringLiteral("members")] = CodeDescription::toVariantList(publicMembers + protectedMembers + privateMembers); vars[QStringLiteral("public_members")] = CodeDescription::toVariantList(publicMembers); vars[QStringLiteral("protected_members")] = CodeDescription::toVariantList(protectedMembers); vars[QStringLiteral("private_members")] = CodeDescription::toVariantList(privateMembers); FunctionDescriptionList publicFunctions; FunctionDescriptionList protectedFunctions; FunctionDescriptionList privateFunctions; FunctionDescription complexFunction(QStringLiteral("doBar"), VariableDescriptionList(), VariableDescriptionList()); complexFunction.arguments << VariableDescription(QStringLiteral("bool"), QStringLiteral("really")); complexFunction.arguments << VariableDescription(QStringLiteral("int"), QStringLiteral("howMuch")); complexFunction.returnArguments << VariableDescription(QStringLiteral("double"), QString()); publicFunctions << FunctionDescription(QStringLiteral("doFoo"), VariableDescriptionList(), VariableDescriptionList()); publicFunctions << complexFunction; protectedFunctions << FunctionDescription(QStringLiteral("onUpdate"), VariableDescriptionList(), VariableDescriptionList()); vars[QStringLiteral("functions")] = CodeDescription::toVariantList(publicFunctions + protectedFunctions + privateFunctions); vars[QStringLiteral("public_functions")] = CodeDescription::toVariantList(publicFunctions); vars[QStringLiteral("protected_functions")] = CodeDescription::toVariantList(protectedFunctions); vars[QStringLiteral("private_functions")] = CodeDescription::toVariantList(privateFunctions); vars[QStringLiteral("testCases")] = QStringList { QStringLiteral("doFoo"), QStringLiteral("doBar"), QStringLiteral("doMore") }; addVariables(vars); } TemplatePreviewRenderer::~TemplatePreviewRenderer() { } TemplatePreview::TemplatePreview(QWidget* parent) : QWidget(parent) { m_variables[QStringLiteral("APPNAME")] = QStringLiteral("Example"); m_variables[QStringLiteral("APPNAMELC")] = QStringLiteral("example"); m_variables[QStringLiteral("APPNAMEUC")] = QStringLiteral("EXAMPLE"); m_variables[QStringLiteral("APPNAMEID")] = QStringLiteral("Example"); m_variables[QStringLiteral("PROJECTDIR")] = QDir::homePath() + "/projects/ExampleProjectDir"; m_variables[QStringLiteral("PROJECTDIRNAME")] = QStringLiteral("ExampleProjectDir"); m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = QStringLiteral("kdevgit"); KTextEditor::Document* doc = KTextEditor::Editor::instance()->createDocument(this); m_preview.reset(doc); m_preview->setReadWrite(false); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); m_view = m_preview->createView(this); m_view->setStatusBarEnabled(false); if (KTextEditor::ConfigInterface* config = qobject_cast(m_view)) { config->setConfigValue(QStringLiteral("icon-bar"), false); config->setConfigValue(QStringLiteral("folding-bar"), false); config->setConfigValue(QStringLiteral("line-numbers"), false); config->setConfigValue(QStringLiteral("dynamic-word-wrap"), true); } layout->addWidget(m_view); } TemplatePreview::~TemplatePreview() { } QString TemplatePreview::setText(const QString& text, bool isProject, TemplateRenderer::EmptyLinesPolicy policy) { QString rendered; QString errorString; if (!text.isEmpty()) { if (isProject) { rendered = KMacroExpander::expandMacros(text, m_variables); } else { TemplatePreviewRenderer renderer; renderer.setEmptyLinesPolicy(policy); rendered = renderer.render(text); errorString = renderer.errorString(); } } m_preview->setReadWrite(true); m_preview->setText(rendered); m_view->setCursorPosition(KTextEditor::Cursor(0, 0)); m_preview->setReadWrite(false); return errorString; } KTextEditor::Document* TemplatePreview::document() const { return m_preview.data(); } diff --git a/plugins/gdb/debuggertracingdialog.cpp b/plugins/gdb/debuggertracingdialog.cpp index 654c2385bf..b714cc9d1d 100644 --- a/plugins/gdb/debuggertracingdialog.cpp +++ b/plugins/gdb/debuggertracingdialog.cpp @@ -1,124 +1,124 @@ /* * Dialog for configuring breakpoint tracing. * * Copyright 2006 Vladimir Prus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "debuggertracingdialog.h" #include "breakpoint.h" -#include -#include +#include +#include /* WARNING: this code was not yet ported to KDevelop4 and is unused, but is intended to be ported. */ using namespace KDevMI::GDB; DebuggerTracingDialog ::DebuggerTracingDialog(Breakpoint* bp, QWidget* parent) : QDialog(parent), bp_(bp) { setupUi(this); expressions->setButtons(KEditListBox::Add | KEditListBox::Remove); connect(enable, SIGNAL(stateChanged(int)), this, SLOT(enableOrDisable(int))); connect(enableCustomFormat, SIGNAL(stateChanged(int)), this, SLOT(enableOrDisableCustomFormat(int))); enable->setChecked(bp_->tracingEnabled()); expressions->setItems(bp_->tracedExpressions()); enableCustomFormat->setChecked(bp_->traceFormatStringEnabled()); customFormat->setText(bp_->traceFormatString()); enableOrDisable(enable->isChecked()); // Go away if the breakpoint does connect(bp_, SIGNAL(destroyed(QObject*)), this, SLOT(reject())); } void DebuggerTracingDialog::enableOrDisable(int state) { bool enable = (state == Qt::Checked); expressionsLabel->setEnabled(enable); expressions->setEnabled(enable); enableCustomFormat->setEnabled(enable); customFormat->setEnabled(enable && enableCustomFormat->isChecked()); } void DebuggerTracingDialog::enableOrDisableCustomFormat(int state) { customFormat->setEnabled(state == Qt::Checked); } void DebuggerTracingDialog::accept() { // Check that if we use format string, // the number of expression is not larget than the number of // format specifiers bool ok = true; if (enableCustomFormat->isChecked()) { QString s = customFormat->text(); int percent_count = 0; for (int i = 0; i < s.length(); ++i) if (s[i] == '%') { if (i+1 < s.length()) { if (s[i+1] != '%') { ++percent_count; } else { // Double % ++i; } } } if (percent_count < expressions->items().count()) { ok = false; KMessageBox::error( this, "Not enough format specifiers" "

The number of format specifiers in the custom format " "string is less than the number of expressions. Either remove " "some expressions or edit the format string.", "Not enough format specifiers"); } } if (ok) { bp_->setTracingEnabled(enable->isChecked()); bp_->setTracedExpressions(expressions->items()); bp_->setTraceFormatStringEnabled(enableCustomFormat->isChecked()); bp_->setTraceFormatString(customFormat->text()); QDialog::accept(); } } diff --git a/plugins/gdb/gdbconfigpage.cpp b/plugins/gdb/gdbconfigpage.cpp index 65de6cddc0..3aa6fb1efc 100644 --- a/plugins/gdb/gdbconfigpage.cpp +++ b/plugins/gdb/gdbconfigpage.cpp @@ -1,184 +1,184 @@ /* * 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 "dbgglobal.h" #include "debuggerplugin.h" #include "debuglog.h" #include "midebugjobs.h" #include "ui_gdbconfigpage.h" #include #include +#include +#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 QStringLiteral("gdb"); } QString GdbLauncher::name() const { return i18n("GDB"); } KJob* GdbLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return nullptr; } if( launchMode == QLatin1String("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 ); } qCWarning(DEBUGGERGDB) << "Unknown launch mode" << launchMode << "for config:" << cfg->name(); return nullptr; } QStringList GdbLauncher::supportedModes() const { return QStringList() << QStringLiteral("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/plugins/ghprovider/ghresource.cpp b/plugins/ghprovider/ghresource.cpp index 1fb6ecc551..c8bcc94bce 100644 --- a/plugins/ghprovider/ghresource.cpp +++ b/plugins/ghprovider/ghresource.cpp @@ -1,231 +1,231 @@ /* This file is part of KDevelop * * Copyright (C) 2012-2013 Miquel Sabaté * * 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 + +#include +#include + +#include +#include +#include #include -#include -#include -#include #include #include #include -#include -#include -#include - - namespace gh { /// Base url for the Github API v3. const static QUrl baseUrl(QStringLiteral("https://api.github.com")); KIO::StoredTransferJob* createHttpAuthJob(const QString &httpHeader) { QUrl url = baseUrl; url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1String("/authorizations")); // generate a unique token, see bug 372144 const QString tokenName = "KDevelop Github Provider : " + QHostInfo::localHostName() + " - " + QDateTime::currentDateTimeUtc().toString(); const QByteArray data = QByteArrayLiteral("{ \"scopes\": [\"repo\"], \"note\": \"") + tokenName.toUtf8() + QByteArrayLiteral("\" }"); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->setProperty("requestedTokenName", tokenName); job->addMetaData(QStringLiteral("customHTTPHeader"), httpHeader); return job; } Resource::Resource(QObject *parent, ProviderModel *model) : QObject(parent), m_model(model) { /* There's nothing to do here */ } void Resource::searchRepos(const QString &uri, const QString &token) { KIO::TransferJob *job = getTransferJob(uri, token); connect(job, &KIO::TransferJob::data, this, &Resource::slotRepos); } void Resource::getOrgs(const QString &token) { KIO::TransferJob *job = getTransferJob(QStringLiteral("/user/orgs"), token); connect(job, &KIO::TransferJob::data, this, &Resource::slotOrgs); } void Resource::authenticate(const QString &name, const QString &password) { auto job = createHttpAuthJob(QLatin1String("Authorization: Basic ") + QString::fromUtf8(QByteArray(name.toUtf8() + ':' + password.toUtf8()).toBase64())); job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); connect(job, &KIO::StoredTransferJob::result, this, &Resource::slotAuthenticate); job->start(); } void Resource::twoFactorAuthenticate(const QString &transferHeader, const QString &code) { auto job = createHttpAuthJob(transferHeader + QLatin1String("\nX-GitHub-OTP: ") + code); connect(job, &KIO::StoredTransferJob::result, this, &Resource::slotAuthenticate); job->start(); } void Resource::revokeAccess(const QString &id, const QString &name, const QString &password) { QUrl url = baseUrl; url.setPath(url.path() + "/authorizations/" + id); KIO::TransferJob *job = KIO::http_delete(url, KIO::HideProgressInfo); job->addMetaData(QStringLiteral("customHTTPHeader"), QLatin1String("Authorization: Basic ") + QString (name + ':' + password).toUtf8().toBase64()); /* And we don't care if it's successful ;) */ job->start(); } KIO::TransferJob * Resource::getTransferJob(const QString &path, const QString &token) const { QUrl url = baseUrl; url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + path); KIO::TransferJob *job = KIO::get(url, KIO::Reload, KIO::HideProgressInfo); if (!token.isEmpty()) job->addMetaData(QStringLiteral("customHTTPHeader"), "Authorization: token " + token); return job; } void Resource::retrieveRepos(const QByteArray &data) { QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data, &error); if (error.error == 0) { const QVariantList list = doc.toVariant().toList(); m_model->clear(); for (const QVariant& it : list) { const QVariantMap &map = it.toMap(); Response res; res.name = map.value(QStringLiteral("name")).toString(); res.url = map.value(QStringLiteral("clone_url")).toUrl(); if (map.value(QStringLiteral("fork")).toBool()) res.kind = Fork; else if (map.value(QStringLiteral("private")).toBool()) res.kind = Private; else res.kind = Public; ProviderItem *item = new ProviderItem(res); m_model->appendRow(item); } } emit reposUpdated(); } void Resource::retrieveOrgs(const QByteArray &data) { QStringList res; QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data, &error); if (error.error == 0) { const QVariantList json = doc.toVariant().toList(); res.reserve(json.size()); for (const QVariant& it : json) { QVariantMap map = it.toMap(); res << map.value(QStringLiteral("login")).toString(); } } emit orgsUpdated(res); } void Resource::slotAuthenticate(KJob *job) { const QString tokenName = job->property("requestedTokenName").toString(); Q_ASSERT(!tokenName.isEmpty()); if (job->error()) { emit authenticated("", "", tokenName); return; } const auto metaData = qobject_cast(job)->metaData(); if (metaData[QStringLiteral("responsecode")] == QStringLiteral("401")) { const auto& header = metaData[QStringLiteral("HTTP-Headers")]; if (header.contains(QStringLiteral("X-GitHub-OTP: required;"), Qt::CaseInsensitive)) { emit twoFactorAuthRequested(qobject_cast(job)->outgoingMetaData()[QStringLiteral("customHTTPHeader")]); return; } } QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(qobject_cast(job)->data(), &error); qCDebug(GHPROVIDER) << "Response:" << doc; if (error.error == 0) { QVariantMap map = doc.toVariant().toMap(); emit authenticated(map.value(QStringLiteral("id")).toByteArray(), map.value(QStringLiteral("token")).toByteArray(), tokenName); } else emit authenticated("", "", tokenName); } void Resource::slotRepos(KIO::Job *job, const QByteArray &data) { if (!job) { qCWarning(GHPROVIDER) << "NULL job returned!"; return; } if (job->error()) { qCWarning(GHPROVIDER) << "Job error: " << job->errorString(); return; } m_temp.append(data); if (data.isEmpty()) { retrieveRepos(m_temp); m_temp = ""; } } void Resource::slotOrgs(KIO::Job *job, const QByteArray &data) { QList res; if (!job) { qCWarning(GHPROVIDER) << "NULL job returned!"; emit orgsUpdated(res); return; } if (job->error()) { qCWarning(GHPROVIDER) << "Job error: " << job->errorString(); emit orgsUpdated(res); return; } m_orgTemp.append(data); if (data.isEmpty()) { retrieveOrgs(m_orgTemp); m_orgTemp = ""; } } } // End of namespace gh diff --git a/plugins/git/gitplugincheckinrepositoryjob.cpp b/plugins/git/gitplugincheckinrepositoryjob.cpp index 5ec13e012f..9e43fb3a1c 100644 --- a/plugins/git/gitplugincheckinrepositoryjob.cpp +++ b/plugins/git/gitplugincheckinrepositoryjob.cpp @@ -1,93 +1,93 @@ /*************************************************************************** * Copyright 2014 Sven Brauch * * * * 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 "gitplugincheckinrepositoryjob.h" #include "debug.h" #include -#include +#include #include GitPluginCheckInRepositoryJob::GitPluginCheckInRepositoryJob(KTextEditor::Document* document, const QString& rootDirectory) : CheckInRepositoryJob(document) , m_hashjob(nullptr) , m_findjob(nullptr) , m_rootDirectory(rootDirectory) {} void GitPluginCheckInRepositoryJob::start() { const QTextCodec* codec = QTextCodec::codecForName(document()->encoding().toLatin1()); const QDir workingDirectory(m_rootDirectory); if ( !workingDirectory.exists() ) { emit finished(false); return; } m_findjob = new QProcess(this); m_findjob->setWorkingDirectory(m_rootDirectory); m_hashjob = new QProcess(this); m_hashjob->setWorkingDirectory(m_rootDirectory); m_hashjob->setStandardOutputProcess(m_findjob); connect(m_findjob, static_cast(&QProcess::finished), this, &GitPluginCheckInRepositoryJob::repositoryQueryFinished); connect(m_hashjob, static_cast(&QProcess::error), this, &GitPluginCheckInRepositoryJob::processFailed); connect(m_findjob, static_cast(&QProcess::error), this, &GitPluginCheckInRepositoryJob::processFailed); m_hashjob->start(QStringLiteral("git"), QStringList{QStringLiteral("hash-object"), QStringLiteral("--stdin")}); m_findjob->start(QStringLiteral("git"), QStringList{QStringLiteral("cat-file"), QStringLiteral("--batch-check")}); for ( int i = 0; i < document()->lines(); i++ ) { m_hashjob->write(codec->fromUnicode(document()->line(i))); if ( i != document()->lines() - 1 ) { m_hashjob->write("\n"); } } m_hashjob->closeWriteChannel(); } GitPluginCheckInRepositoryJob::~GitPluginCheckInRepositoryJob() { if ( m_findjob && m_findjob->state() == QProcess::Running ) { m_findjob->kill(); } if ( m_hashjob && m_hashjob->state() == QProcess::Running ) { m_hashjob->kill(); } } void GitPluginCheckInRepositoryJob::processFailed(QProcess::ProcessError err) { qCDebug(PLUGIN_GIT) << "calling git failed with error:" << err; emit finished(false); } void GitPluginCheckInRepositoryJob::repositoryQueryFinished(int) { const QByteArray output = m_findjob->readAllStandardOutput(); bool requestSucceeded = output.contains(" blob "); emit finished(requestSucceeded); } diff --git a/plugins/git/gitpluginmetadata.cpp b/plugins/git/gitpluginmetadata.cpp index 4d73b019b7..a71781f45b 100644 --- a/plugins/git/gitpluginmetadata.cpp +++ b/plugins/git/gitpluginmetadata.cpp @@ -1,35 +1,35 @@ /*************************************************************************** * Copyright 2014 Alex Richardson * * * * 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 "gitplugin.h" -#include +#include // This file only exists so that the tests can be built: // test_git builds gitplugin.cpp again but in a different directory. // This means that the kdevgit.json file is no longer found. // Since the JSON metadata is not needed in the test, we simply move // the K_PLUGIN_FACTORY_WITH_JSON to a separate file. // TODO: use object or static library? K_PLUGIN_FACTORY_WITH_JSON(KDevGitFactory, "kdevgit.json", registerPlugin();) #include "gitpluginmetadata.moc" diff --git a/plugins/grepview/grepoutputmodel.cpp b/plugins/grepview/grepoutputmodel.cpp index 09e8c8526d..80bec34c6e 100644 --- a/plugins/grepview/grepoutputmodel.cpp +++ b/plugins/grepview/grepoutputmodel.cpp @@ -1,492 +1,493 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * 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 "grepoutputmodel.h" #include "grepviewplugin.h" #include "greputil.h" -#include -#include -#include - -#include #include #include #include + +#include +#include +#include + +#include #include using namespace KDevelop; GrepOutputItem::GrepOutputItem(const DocumentChangePointer& change, const QString &text, bool checkable) : QStandardItem(), m_change(change) { setText(text); setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); setCheckable(checkable); if(checkable) setCheckState(Qt::Checked); } GrepOutputItem::GrepOutputItem(const QString& filename, const QString& text, bool checkable) : QStandardItem(), m_change(new DocumentChange(IndexedString(filename), KTextEditor::Range::invalid(), QString(), QString())) { setText(text); setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); setCheckable(checkable); if(checkable) { #if QT_VERSION >= 0x050600 setAutoTristate(true); #else setTristate(true); #endif setCheckState(Qt::Checked); } } int GrepOutputItem::lineNumber() const { // line starts at 0 for cursor but we want to start at 1 return m_change->m_range.start().line() + 1; } QString GrepOutputItem::filename() const { return m_change->m_document.str(); } DocumentChangePointer GrepOutputItem::change() const { return m_change; } bool GrepOutputItem::isText() const { return m_change->m_range.isValid(); } void GrepOutputItem::propagateState() { for(int i = 0; i < rowCount(); i++) { GrepOutputItem *item = static_cast(child(i)); if(item->isEnabled()) { item->setCheckState(checkState()); item->propagateState(); } } } void GrepOutputItem::refreshState() { if(rowCount() > 0) { int checked = 0; int unchecked = 0; int enabled = 0; //only enabled items are relevants for(int i = 0; i < rowCount(); i++) { QStandardItem *item = child(i); if(item->isEnabled()) { enabled += 1; switch(child(i)->checkState()) { case Qt::Checked: checked += 1; break; case Qt::Unchecked: unchecked += 1; break; default: break; } } } if(enabled == 0) { setCheckState(Qt::Unchecked); setEnabled(false); } else if(checked == enabled) { setCheckState(Qt::Checked); } else if (unchecked == enabled) { setCheckState(Qt::Unchecked); } else { setCheckState(Qt::PartiallyChecked); } } if(GrepOutputItem *p = static_cast(parent())) { p->refreshState(); } } QVariant GrepOutputItem::data ( int role ) const { GrepOutputModel *grepModel = static_cast(model()); if(role == Qt::ToolTipRole && grepModel && isText()) { QString start = text().left(m_change->m_range.start().column()).toHtmlEscaped(); // show replaced version in tooltip if we are in replace mode const QString match = isCheckable() ? grepModel->replacementFor(m_change->m_oldText) : m_change->m_oldText; const QString repl = QLatin1String("") + match.toHtmlEscaped() + QLatin1String(""); QString end = text().mid(m_change->m_range.end().column()).toHtmlEscaped(); const QString toolTip = QLatin1String("") + QString(start + repl + end).trimmed() + QLatin1String(""); return toolTip; } else if (role == Qt::FontRole) { return QFontDatabase::systemFont(QFontDatabase::FixedFont); } else { return QStandardItem::data(role); } } GrepOutputItem::~GrepOutputItem() {} /////////////////////////////////////////////////////////////// GrepOutputModel::GrepOutputModel( QObject *parent ) : QStandardItemModel( parent ) { connect(this, &GrepOutputModel::itemChanged, this, &GrepOutputModel::updateCheckState); } GrepOutputModel::~GrepOutputModel() {} void GrepOutputModel::clear() { QStandardItemModel::clear(); // the above clear() also destroys the root item, so invalidate the pointer m_rootItem = nullptr; m_fileCount = 0; m_matchCount = 0; } void GrepOutputModel::setRegExp(const QRegExp& re) { m_regExp = re; m_finalUpToDate = false; } void GrepOutputModel::setReplacement(const QString& repl) { m_replacement = repl; m_finalUpToDate = false; } void GrepOutputModel::setReplacementTemplate(const QString& tmpl) { m_replacementTemplate = tmpl; m_finalUpToDate = false; } QString GrepOutputModel::replacementFor(const QString &text) { if(!m_finalUpToDate) { m_finalReplacement = substitudePattern(m_replacementTemplate, m_replacement); m_finalUpToDate = true; } return QString(text).replace(m_regExp, m_finalReplacement); } void GrepOutputModel::activate( const QModelIndex &idx ) { QStandardItem *stditem = itemFromIndex(idx); GrepOutputItem *grepitem = dynamic_cast(stditem); if( !grepitem || !grepitem->isText() ) return; QUrl url = QUrl::fromLocalFile(grepitem->filename()); int line = grepitem->lineNumber() - 1; KTextEditor::Range range( line, 0, line+1, 0); // Try to find the actual text range we found during the grep IDocument* doc = ICore::self()->documentController()->documentForUrl( url ); if(!doc) doc = ICore::self()->documentController()->openDocument( url, range ); if(!doc) return; if (KTextEditor::Document* tdoc = doc->textDocument()) { KTextEditor::Range matchRange = grepitem->change()->m_range; QString actualText = tdoc->text(matchRange); QString expectedText = grepitem->change()->m_oldText; if (actualText == expectedText) { range = matchRange; } } ICore::self()->documentController()->activateDocument( doc, range ); } QModelIndex GrepOutputModel::previousItemIndex(const QModelIndex ¤tIdx) const { GrepOutputItem* current_item = nullptr; if (!currentIdx.isValid()) { // no item selected, search recursively for the last item in search results QStandardItem *it = item(0,0); while (it) { QStandardItem *child = it->child( it->rowCount() - 1 ); if (!child) return it->index(); it = child; } return QModelIndex(); } else current_item = static_cast(itemFromIndex(currentIdx)); if (current_item->parent() != nullptr) { int row = currentIdx.row(); if(!current_item->isText()) // the item is a file { int item_row = current_item->row(); if(item_row > 0) { int idx_last_item = current_item->parent()->child(item_row - 1)->rowCount() - 1; return current_item->parent()->child(item_row - 1)->child(idx_last_item)->index(); } } else // the item is a match { if(row > 0) return current_item->parent()->child(row - 1)->index(); else // we return the index of the last item of the previous file { int parrent_row = current_item->parent()->row(); if(parrent_row > 0) { int idx_last_item = current_item->parent()->parent()->child(parrent_row - 1)->rowCount() - 1; return current_item->parent()->parent()->child(parrent_row - 1)->child(idx_last_item)->index(); } } } } return currentIdx; } QModelIndex GrepOutputModel::nextItemIndex(const QModelIndex ¤tIdx) const { GrepOutputItem* current_item = nullptr; if (!currentIdx.isValid()) { QStandardItem *it = item(0,0); if (!it) return QModelIndex(); current_item = static_cast(it); } else current_item = static_cast(itemFromIndex(currentIdx)); if (current_item->parent() == nullptr) { // root item with overview of search results if (current_item->rowCount() > 0) return nextItemIndex(current_item->child(0)->index()); else return QModelIndex(); } else { int row = currentIdx.row(); if(!current_item->isText()) // the item is a file { int item_row = current_item->row(); if(item_row < current_item->parent()->rowCount()) { return current_item->parent()->child(item_row)->child(0)->index(); } } else // the item is a match { if(row < current_item->parent()->rowCount() - 1) return current_item->parent()->child(row + 1)->index(); else // we return the index of the first item of the next file { int parrent_row = current_item->parent()->row(); if(parrent_row < current_item->parent()->parent()->rowCount() - 1) { return current_item->parent()->parent()->child(parrent_row + 1)->child(0)->index(); } } } } return currentIdx; } const GrepOutputItem *GrepOutputModel::getRootItem() const { return m_rootItem; } bool GrepOutputModel::itemsCheckable() const { return m_itemsCheckable; } void GrepOutputModel::makeItemsCheckable(bool checkable) { if(m_itemsCheckable == checkable) return; if(m_rootItem) makeItemsCheckable(checkable, m_rootItem); m_itemsCheckable = checkable; } void GrepOutputModel::makeItemsCheckable(bool checkable, GrepOutputItem* item) { item->setCheckable(checkable); if(checkable) { item->setCheckState(Qt::Checked); if(item->rowCount() && checkable) #if QT_VERSION >= 0x050600 item->setAutoTristate(true); #else item->setTristate(true); #endif } for(int row = 0; row < item->rowCount(); ++row) makeItemsCheckable(checkable, static_cast(item->child(row, 0))); } void GrepOutputModel::appendOutputs( const QString &filename, const GrepOutputItem::List &items ) { if(items.isEmpty()) return; if(rowCount() == 0) { m_rootItem = new GrepOutputItem(QString(), QString(), m_itemsCheckable); appendRow(m_rootItem); } m_fileCount += 1; m_matchCount += items.length(); const QString matchText = i18np("1 match", "%1 matches", m_matchCount); const QString fileText = i18np("1 file", "%1 files", m_fileCount); m_rootItem->setText(i18nc("%1 is e.g. '4 matches', %2 is e.g. '1 file'", "%1 in %2", matchText, fileText)); QString fnString = i18np("%2: 1 match", "%2: %1 matches", items.length(), ICore::self()->projectController()->prettyFileName(QUrl::fromLocalFile(filename))); GrepOutputItem *fileItem = new GrepOutputItem(filename, fnString, m_itemsCheckable); m_rootItem->appendRow(fileItem); for (const GrepOutputItem& item : items) { GrepOutputItem* copy = new GrepOutputItem(item); copy->setCheckable(m_itemsCheckable); if(m_itemsCheckable) { copy->setCheckState(Qt::Checked); if(copy->rowCount()) #if QT_VERSION >= 0x050600 copy->setAutoTristate(true); #else copy->setTristate(true); #endif } fileItem->appendRow(copy); } } void GrepOutputModel::updateCheckState(QStandardItem* item) { // if we don't disconnect the SIGNAL, the setCheckState will call it in loop disconnect(this, &GrepOutputModel::itemChanged, nullptr, nullptr); // try to update checkstate on non checkable items would make a checkbox appear if(item->isCheckable()) { GrepOutputItem *it = static_cast(item); it->propagateState(); it->refreshState(); } connect(this, &GrepOutputModel::itemChanged, this, &GrepOutputModel::updateCheckState); } void GrepOutputModel::doReplacements() { Q_ASSERT(m_rootItem); if (!m_rootItem) return; // nothing to do, abort DocumentChangeSet changeSet; changeSet.setFormatPolicy(DocumentChangeSet::NoAutoFormat); for(int fileRow = 0; fileRow < m_rootItem->rowCount(); fileRow++) { GrepOutputItem *file = static_cast(m_rootItem->child(fileRow)); for(int matchRow = 0; matchRow < file->rowCount(); matchRow++) { GrepOutputItem *match = static_cast(file->child(matchRow)); if(match->checkState() == Qt::Checked) { DocumentChangePointer change = match->change(); // setting replacement text based on current replace value change->m_newText = replacementFor(change->m_oldText); changeSet.addChange(change); // this item cannot be checked anymore match->setCheckState(Qt::Unchecked); match->setEnabled(false); } } } DocumentChangeSet::ChangeResult result = changeSet.applyAllChanges(); if(!result.m_success) { DocumentChangePointer ch = result.m_reasonChange; if(ch) emit showErrorMessage(i18nc("%1 is the old text, %2 is the new text, %3 is the file path, %4 and %5 are its row and column", "Failed to replace %1 by %2 in %3:%4:%5", ch->m_oldText.toHtmlEscaped(), ch->m_newText.toHtmlEscaped(), ch->m_document.toUrl().toLocalFile(), ch->m_range.start().line() + 1, ch->m_range.start().column() + 1)); } } void GrepOutputModel::showMessageSlot(IStatus* status, const QString& message) { m_savedMessage = message; m_savedIStatus = status; showMessageEmit(); } void GrepOutputModel::showMessageEmit() { emit showMessage(m_savedIStatus, m_savedMessage); } bool GrepOutputModel::hasResults() { return(m_matchCount > 0); } diff --git a/plugins/grepview/grepoutputview.cpp b/plugins/grepview/grepoutputview.cpp index b93b4103cb..cace3ad34f 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,478 +1,478 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * 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 "grepoutputview.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "ui_grepoutputview.h" #include "grepviewplugin.h" #include "grepdialog.h" #include "greputil.h" #include "grepjob.h" #include "debug.h" #include #include #include #include #include -#include +#include #include #include #include using namespace KDevelop; GrepOutputViewFactory::GrepOutputViewFactory(GrepViewPlugin* plugin) : m_plugin(plugin) {} QWidget* GrepOutputViewFactory::create(QWidget* parent) { return new GrepOutputView(parent, m_plugin); } Qt::DockWidgetArea GrepOutputViewFactory::defaultPosition() { return Qt::BottomDockWidgetArea; } QString GrepOutputViewFactory::id() const { return QStringLiteral("org.kdevelop.GrepOutputView"); } const int GrepOutputView::HISTORY_SIZE = 5; namespace { enum { GrepSettingsStorageItemCount = 10 }; } GrepOutputView::GrepOutputView(QWidget* parent, GrepViewPlugin* plugin) : QWidget(parent) , m_next(nullptr) , m_prev(nullptr) , m_collapseAll(nullptr) , m_expandAll(nullptr) , m_refresh(nullptr) , m_clearSearchHistory(nullptr) , m_statusLabel(nullptr) , m_plugin(plugin) { Ui::GrepOutputView::setupUi(this); setWindowTitle(i18nc("@title:window", "Find/Replace Output View")); setWindowIcon(QIcon::fromTheme(QStringLiteral("edit-find"), windowIcon())); m_prev = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("&Previous Item"), this); m_next = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("&Next Item"), this); m_collapseAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-left-double")), i18n("C&ollapse All"), this); // TODO change icon m_expandAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-right-double")), i18n("&Expand All"), this); // TODO change icon updateButtonState(false); QAction *separator = new QAction(this); separator->setSeparator(true); QAction *newSearchAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("New &Search"), this); m_refresh = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Refresh"), this); m_refresh->setEnabled(false); m_clearSearchHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18n("Clear Search History"), this); m_clearSearchHistory->setEnabled(false); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); addAction(m_refresh); addAction(m_clearSearchHistory); separator = new QAction(this); separator->setSeparator(true); addAction(separator); QWidgetAction *statusWidget = new QWidgetAction(this); m_statusLabel = new QLabel(this); statusWidget->setDefaultWidget(m_statusLabel); addAction(statusWidget); modelSelector->setEditable(false); modelSelector->setContextMenuPolicy(Qt::CustomContextMenu); connect(modelSelector, &KComboBox::customContextMenuRequested, this, &GrepOutputView::modelSelectorContextMenu); connect(modelSelector, static_cast(&KComboBox::currentIndexChanged), this, &GrepOutputView::changeModel); resultsTreeView->setItemDelegate(GrepOutputDelegate::self()); resultsTreeView->setRootIsDecorated(false); resultsTreeView->setHeaderHidden(true); resultsTreeView->setUniformRowHeights(false); resultsTreeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); connect(m_prev, &QAction::triggered, this, &GrepOutputView::selectPreviousItem); connect(m_next, &QAction::triggered, this, &GrepOutputView::selectNextItem); connect(m_collapseAll, &QAction::triggered, this, &GrepOutputView::collapseAllItems); connect(m_expandAll, &QAction::triggered, this, &GrepOutputView::expandAllItems); connect(applyButton, &QPushButton::clicked, this, &GrepOutputView::onApply); connect(m_refresh, &QAction::triggered, this, &GrepOutputView::refresh); connect(m_clearSearchHistory, &QAction::triggered, this, &GrepOutputView::clearSearchHistory); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); replacementCombo->addItems( cg.readEntry("LastReplacementItems", QStringList()) ); replacementCombo->setInsertPolicy(QComboBox::InsertAtTop); applyButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); connect(replacementCombo, &KComboBox::editTextChanged, this, &GrepOutputView::replacementTextChanged); connect(replacementCombo, static_cast(&KComboBox::returnPressed), this, &GrepOutputView::onApply); connect(newSearchAction, &QAction::triggered, this, &GrepOutputView::showDialog); resultsTreeView->header()->setStretchLastSection(true); resultsTreeView->header()->setStretchLastSection(true); // read Find/Replace settings history const QStringList s = cg.readEntry("LastSettings", QStringList()); if (s.size() % GrepSettingsStorageItemCount != 0) { qCWarning(PLUGIN_GREPVIEW) << "Stored settings history has unexpected size:" << s; } else { m_settingsHistory.reserve(s.size() / GrepSettingsStorageItemCount); auto it = s.begin(); while (it != s.end()) { GrepJobSettings settings; settings.projectFilesOnly = ((it++)->toUInt() != 0); settings.caseSensitive = ((it++)->toUInt() != 0); settings.regexp = ((it++)->toUInt() != 0); settings.depth = (it++)->toInt(); settings.pattern = *(it++); settings.searchTemplate = *(it++); settings.replacementTemplate = *(it++); settings.files = *(it++); settings.exclude = *(it++); settings.searchPaths = *(it++); settings.fromHistory = true; m_settingsHistory << settings; } } // rerun the grep jobs with settings from the history GrepDialog* dlg = new GrepDialog(m_plugin, this, false); dlg->historySearch(m_settingsHistory); updateCheckable(); } void GrepOutputView::replacementTextChanged() { updateCheckable(); if (model()) { // see https://bugs.kde.org/show_bug.cgi?id=274902 - renewModel can trigger a call here without an active model updateApplyState(model()->index(0, 0), model()->index(0, 0)); } } GrepOutputView::~GrepOutputView() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); cg.writeEntry("LastReplacementItems", qCombo2StringList(replacementCombo, true)); QStringList settingsStrings; settingsStrings.reserve(m_settingsHistory.size() * GrepSettingsStorageItemCount); foreach (const GrepJobSettings & s, m_settingsHistory) { settingsStrings << QString::number(s.projectFilesOnly ? 1 : 0) << QString::number(s.caseSensitive ? 1 : 0) << QString::number(s.regexp ? 1 : 0) << QString::number(s.depth) << s.pattern << s.searchTemplate << s.replacementTemplate << s.files << s.exclude << s.searchPaths; } cg.writeEntry("LastSettings", settingsStrings); emit outputViewIsClosed(); } GrepOutputModel* GrepOutputView::renewModel(const GrepJobSettings& settings, const QString& description) { // clear oldest model while(modelSelector->count() >= GrepOutputView::HISTORY_SIZE) { QVariant var = modelSelector->itemData(GrepOutputView::HISTORY_SIZE - 1); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(GrepOutputView::HISTORY_SIZE - 1); } while(m_settingsHistory.count() >= GrepOutputView::HISTORY_SIZE) { m_settingsHistory.removeFirst(); } replacementCombo->clearEditText(); GrepOutputModel* newModel = new GrepOutputModel(resultsTreeView); applyButton->setEnabled(false); // text may be already present newModel->setReplacement(replacementCombo->currentText()); connect(newModel, &GrepOutputModel::rowsRemoved, this, &GrepOutputView::rowsRemoved); connect(resultsTreeView, &QTreeView::activated, newModel, &GrepOutputModel::activate); connect(replacementCombo, &KComboBox::editTextChanged, newModel, &GrepOutputModel::setReplacement); connect(newModel, &GrepOutputModel::rowsInserted, this, &GrepOutputView::expandElements); connect(newModel, &GrepOutputModel::showErrorMessage, this, &GrepOutputView::showErrorMessage); connect(m_plugin, &GrepViewPlugin::grepJobFinished, this, &GrepOutputView::updateScrollArea); // appends new model to history modelSelector->insertItem(0, description, qVariantFromValue(newModel)); modelSelector->setCurrentIndex(0); m_settingsHistory.append(settings); updateCheckable(); return newModel; } GrepOutputModel* GrepOutputView::model() { return static_cast(resultsTreeView->model()); } void GrepOutputView::changeModel(int index) { if (model()) { disconnect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); disconnect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); } replacementCombo->clearEditText(); //after deleting the whole search history, index is -1 if(index >= 0) { QVariant var = modelSelector->itemData(index); GrepOutputModel *resultModel = static_cast(qvariant_cast(var)); resultsTreeView->setModel(resultModel); resultsTreeView->expandAll(); connect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); connect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); model()->showMessageEmit(); applyButton->setEnabled(model()->hasResults() && model()->getRootItem() && model()->getRootItem()->checkState() != Qt::Unchecked && !replacementCombo->currentText().isEmpty()); if(model()->hasResults()) expandElements(QModelIndex()); else { updateButtonState(false); } } updateCheckable(); updateApplyState(model()->index(0, 0), model()->index(0, 0)); m_refresh->setEnabled(true); m_clearSearchHistory->setEnabled(true); } void GrepOutputView::setMessage(const QString& msg, MessageType type) { if (type == Error) { QPalette palette = m_statusLabel->palette(); KColorScheme::adjustForeground(palette, KColorScheme::NegativeText, QPalette::WindowText); m_statusLabel->setPalette(palette); } else { m_statusLabel->setPalette(QPalette()); } m_statusLabel->setText(msg); } void GrepOutputView::showErrorMessage( const QString& errorMessage ) { setMessage(errorMessage, Error); } void GrepOutputView::showMessage( KDevelop::IStatus* , const QString& message ) { setMessage(message, Information); } void GrepOutputView::onApply() { if(model()) { Q_ASSERT(model()->rowCount()); // ask a confirmation before an empty string replacement if(replacementCombo->currentText().length() == 0 && KMessageBox::questionYesNo(this, i18n("Do you want to replace with an empty string?"), i18n("Start replacement")) == KMessageBox::No) { return; } setEnabled(false); model()->doReplacements(); setEnabled(true); } } void GrepOutputView::showDialog() { m_plugin->showDialog(true); } void GrepOutputView::refresh() { int index = modelSelector->currentIndex(); if (index >= 0) { QVariant var = modelSelector->currentData(); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(index); QVector refresh_history({ m_settingsHistory.takeAt(m_settingsHistory.count() - 1 - index) }); refresh_history.first().fromHistory = false; GrepDialog* dlg = new GrepDialog(m_plugin, this, false); dlg->historySearch(refresh_history); } } void GrepOutputView::expandElements(const QModelIndex& index) { updateButtonState(true); resultsTreeView->expand(index); } void GrepOutputView::updateButtonState(bool enable) { m_prev->setEnabled(enable); m_next->setEnabled(enable); m_collapseAll->setEnabled(enable); m_expandAll->setEnabled(enable); } void GrepOutputView::selectPreviousItem() { if (!model()) { return; } QModelIndex prev_idx = model()->previousItemIndex(resultsTreeView->currentIndex()); if (prev_idx.isValid()) { resultsTreeView->setCurrentIndex(prev_idx); model()->activate(prev_idx); } } void GrepOutputView::selectNextItem() { if (!model()) { return; } QModelIndex next_idx = model()->nextItemIndex(resultsTreeView->currentIndex()); if (next_idx.isValid()) { resultsTreeView->setCurrentIndex(next_idx); model()->activate(next_idx); } } void GrepOutputView::collapseAllItems() { // Collapse everything resultsTreeView->collapseAll(); if (resultsTreeView->model()) { // Now reopen the first children, which correspond to the files. resultsTreeView->expand(resultsTreeView->model()->index(0, 0)); } } void GrepOutputView::expandAllItems() { resultsTreeView->expandAll(); } void GrepOutputView::rowsRemoved() { updateButtonState(model()->rowCount() > 0); } void GrepOutputView::updateApplyState(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(bottomRight); if (!model() || !model()->hasResults()) { applyButton->setEnabled(false); return; } // we only care about the root item if(!topLeft.parent().isValid()) { applyButton->setEnabled(topLeft.data(Qt::CheckStateRole) != Qt::Unchecked && model()->itemsCheckable()); } } void GrepOutputView::updateCheckable() { if(model()) model()->makeItemsCheckable(!replacementCombo->currentText().isEmpty() || model()->itemsCheckable()); } void GrepOutputView::clearSearchHistory() { GrepJob *runningJob = m_plugin->grepJob(); if(runningJob) { connect(runningJob, &GrepJob::finished, this, [=]() {updateButtonState(false);}); runningJob->kill(); } while(modelSelector->count() > 0) { QVariant var = modelSelector->itemData(0); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(0); } m_settingsHistory.clear(); applyButton->setEnabled(false); updateButtonState(false); m_refresh->setEnabled(false); m_clearSearchHistory->setEnabled(false); m_statusLabel->setText(QString()); } void GrepOutputView::modelSelectorContextMenu(const QPoint& pos) { QPoint globalPos = modelSelector->mapToGlobal(pos); QMenu myMenu(this); myMenu.addAction(m_clearSearchHistory); myMenu.exec(globalPos); } void GrepOutputView::updateScrollArea() { for (int col = 0; col < model()->columnCount(); ++col) resultsTreeView->resizeColumnToContents(col); } diff --git a/plugins/grepview/grepviewpluginmetadata.cpp b/plugins/grepview/grepviewpluginmetadata.cpp index 2b004fd797..14bc398e20 100644 --- a/plugins/grepview/grepviewpluginmetadata.cpp +++ b/plugins/grepview/grepviewpluginmetadata.cpp @@ -1,35 +1,35 @@ /*************************************************************************** * Copyright 2014 Alex Richardson * * * * 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 "grepviewplugin.h" -#include +#include // This file only exists so that the tests can be built: // test_grepview builds grepview.cpp again but in a different directory. // This means that the kdevgrepview.json file is no longer found. // Since the JSON metadata is not needed in the test, we simply move // the K_PLUGIN_FACTORY_WITH_JSON to a separate file. // TODO: use object or static library? K_PLUGIN_FACTORY_WITH_JSON(KDevGrepviewFactory, "kdevgrepview.json", registerPlugin();) #include "grepviewpluginmetadata.moc" diff --git a/plugins/heaptrack/job.cpp b/plugins/heaptrack/job.cpp index b5822c364b..6b4faedbba 100644 --- a/plugins/heaptrack/job.cpp +++ b/plugins/heaptrack/job.cpp @@ -1,149 +1,150 @@ /* This file is part of KDevelop Copyright 2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "job.h" #include "debug.h" #include "globalsettings.h" #include "utils.h" #include #include #include #include -#include #include #include +#include + #include #include namespace Heaptrack { Job::Job(KDevelop::ILaunchConfiguration* launchConfig) : m_pid(-1) { Q_ASSERT(launchConfig); auto pluginController = KDevelop::ICore::self()->pluginController(); auto iface = pluginController->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"))->extension(); Q_ASSERT(iface); QString envProfile = iface->environmentProfileName(launchConfig); if (envProfile.isEmpty()) { envProfile = KDevelop::EnvironmentProfileList(KSharedConfig::openConfig()).defaultProfileName(); } setEnvironmentProfile(envProfile); QString errorString; m_analyzedExecutable = iface->executable(launchConfig, errorString).toLocalFile(); if (!errorString.isEmpty()) { setError(-1); setErrorText(errorString); } QStringList analyzedExecutableArguments = iface->arguments(launchConfig, errorString); if (!errorString.isEmpty()) { setError(-1); setErrorText(errorString); } QUrl workDir = iface->workingDirectory(launchConfig); if (workDir.isEmpty() || !workDir.isValid()) { workDir = QUrl::fromLocalFile(QFileInfo(m_analyzedExecutable).absolutePath()); } setWorkingDirectory(workDir); *this << KDevelop::Path(GlobalSettings::heaptrackExecutable()).toLocalFile(); *this << m_analyzedExecutable; *this << analyzedExecutableArguments; setup(); } Job::Job(long int pid) : m_pid(pid) { *this << KDevelop::Path(GlobalSettings::heaptrackExecutable()).toLocalFile(); *this << QStringLiteral("-p"); *this << QString::number(m_pid); setup(); } void Job::setup() { setProperties(DisplayStdout); setProperties(DisplayStderr); setProperties(PostProcessOutput); setCapabilities(Killable); setStandardToolView(KDevelop::IOutputView::TestView); setBehaviours(KDevelop::IOutputView::AutoScroll); KDevelop::ICore::self()->uiController()->registerStatus(this); connect(this, &Job::finished, this, [this]() { emit hideProgress(this); }); } Job::~Job() { } QString Job::statusName() const { QString target = m_pid < 0 ? QFileInfo(m_analyzedExecutable).fileName() : QStringLiteral("PID: %1").arg(m_pid); return i18n("Heaptrack Analysis (%1)", target); } QString Job::resultsFile() const { return m_resultsFile; } void Job::start() { emit showProgress(this, 0, 0, 0); OutputExecuteJob::start(); } void Job::postProcessStdout(const QStringList& lines) { static const auto resultRegex = QRegularExpression(QStringLiteral("heaptrack output will be written to \\\"(.+)\\\"")); if (m_resultsFile.isEmpty()) { QRegularExpressionMatch match; for (const QString & line : lines) { match = resultRegex.match(line); if (match.hasMatch()) { m_resultsFile = match.captured(1); break; } } } OutputExecuteJob::postProcessStdout(lines); } } diff --git a/plugins/heaptrack/plugin.cpp b/plugins/heaptrack/plugin.cpp index c6a494282e..a21cdf1e01 100644 --- a/plugins/heaptrack/plugin.cpp +++ b/plugins/heaptrack/plugin.cpp @@ -1,162 +1,163 @@ /* This file is part of KDevelop Copyright 2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugin.h" #include "config/globalconfigpage.h" #include "debug.h" #include "job.h" #include "utils.h" #include "visualizer.h" #include #if KF5SysGuard_FOUND #include "dialogs/processselection.h" #include #endif #include #include -#include -#include #include #include #include #include +#include +#include + #include #include K_PLUGIN_FACTORY_WITH_JSON(HeaptrackFactory, "kdevheaptrack.json", registerPlugin();) namespace Heaptrack { Plugin::Plugin(QObject* parent, const QVariantList&) : IPlugin(QStringLiteral("kdevheaptrack"), parent) { setXMLFile(QStringLiteral("kdevheaptrack.rc")); m_launchAction = new QAction( QIcon::fromTheme(QStringLiteral("office-chart-area")), i18n("Run Heaptrack Analysis"), this); connect(m_launchAction, &QAction::triggered, this, &Plugin::launchHeaptrack); actionCollection()->addAction(QStringLiteral("heaptrack_launch"), m_launchAction); #if KF5SysGuard_FOUND m_attachAction = new QAction( QIcon::fromTheme(QStringLiteral("office-chart-area")), i18n("Attach to Process with Heaptrack"), this); connect(m_attachAction, &QAction::triggered, this, &Plugin::attachHeaptrack); actionCollection()->addAction(QStringLiteral("heaptrack_attach"), m_attachAction); #endif } Plugin::~Plugin() { } void Plugin::launchHeaptrack() { auto runController = KDevelop::Core::self()->runControllerInternal(); if (runController->launchConfigurations().isEmpty()) { runController->showConfigurationDialog(); } auto defaultLaunch = runController->defaultLaunch(); if (!defaultLaunch) { return; } auto pluginController = core()->self()->pluginController(); auto iface = pluginController->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"))->extension(); Q_ASSERT(iface); auto heaptrackJob = new Job(defaultLaunch); connect(heaptrackJob, &Job::finished, this, &Plugin::jobFinished); QList jobList; if (KJob* depJob = iface->dependencyJob(defaultLaunch)) { jobList += depJob; } jobList += heaptrackJob; auto ecJob = new KDevelop::ExecuteCompositeJob(runController, jobList); ecJob->setObjectName(heaptrackJob->statusName()); runController->registerJob(ecJob); m_launchAction->setEnabled(false); } void Plugin::attachHeaptrack() { #if KF5SysGuard_FOUND QPointer dlg = new KDevMI::ProcessSelectionDialog(activeMainWindow()); if (!dlg->exec() || !dlg->pidSelected()) { delete dlg; return; } auto heaptrackJob = new Job(dlg->pidSelected()); delete dlg; connect(heaptrackJob, &Job::finished, this, &Plugin::jobFinished); heaptrackJob->setObjectName(heaptrackJob->statusName()); core()->runController()->registerJob(heaptrackJob); m_launchAction->setEnabled(false); #endif } void Plugin::jobFinished(KJob* kjob) { auto job = static_cast(kjob); Q_ASSERT(job); if (job->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded) { auto visualizer = new Visualizer(job->resultsFile(), this); visualizer->start(); } else { QFile::remove(job->resultsFile()); } m_launchAction->setEnabled(true); } int Plugin::configPages() const { return 1; } KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) { if (number) { return nullptr; } return new GlobalConfigPage(this, parent); } } #include "plugin.moc" diff --git a/plugins/heaptrack/visualizer.cpp b/plugins/heaptrack/visualizer.cpp index 1ae339f944..02a30bcee4 100644 --- a/plugins/heaptrack/visualizer.cpp +++ b/plugins/heaptrack/visualizer.cpp @@ -1,72 +1,73 @@ /* This file is part of KDevelop Copyright 2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "visualizer.h" #include "debug.h" #include "globalsettings.h" #include "utils.h" -#include -#include #include +#include +#include + #include namespace Heaptrack { Visualizer::Visualizer(const QString& resultsFile, QObject* parent) : QProcess(parent) , m_resultsFile(resultsFile) { #if QT_VERSION < 0x050600 connect(this, static_cast(&QProcess::error), #else connect(this, &QProcess::errorOccurred, #endif this, [this](QProcess::ProcessError error) { QString errorMessage; if (error == QProcess::FailedToStart) { errorMessage = i18n("Failed to start visualizer from \"%1\".", program()) + QStringLiteral("\n\n") + i18n("Check your settings and install the visualizer if necessary."); } else { errorMessage = i18n("Error during visualizer execution:") + QStringLiteral("\n\n") + errorString(); } KMessageBox::error(activeMainWindow(), errorMessage, i18n("Heaptrack Error")); }); connect(this, static_cast(&QProcess::finished), this, [this]() { deleteLater(); }); setProgram(KDevelop::Path(GlobalSettings::heaptrackGuiExecutable()).toLocalFile()); setArguments({ resultsFile }); } Visualizer::~Visualizer() { QFile::remove(m_resultsFile); } } diff --git a/plugins/kdeprovider/kdeproviderplugin.cpp b/plugins/kdeprovider/kdeproviderplugin.cpp index e6717591ab..f13ece28f8 100644 --- a/plugins/kdeprovider/kdeproviderplugin.cpp +++ b/plugins/kdeprovider/kdeproviderplugin.cpp @@ -1,50 +1,50 @@ /* * 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 "kdeproviderplugin.h" #include -#include +#include #include #include "kdeproviderwidget.h" using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(KDevKDEProviderFactory, "kdevkdeprovider.json", registerPlugin(); ) KDEProviderPlugin::KDEProviderPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( QStringLiteral("kdevkdeprovider"), parent ) { } KDEProviderPlugin::~KDEProviderPlugin() {} QString KDEProviderPlugin::name() const { return i18n("KDE"); } KDevelop::IProjectProviderWidget* KDEProviderPlugin::providerWidget(QWidget* parent) { return new KDEProviderWidget(parent); } #include "kdeproviderplugin.moc" diff --git a/plugins/manpage/manpagedocumentation.cpp b/plugins/manpage/manpagedocumentation.cpp index 18ebb33159..7b5a829024 100644 --- a/plugins/manpage/manpagedocumentation.cpp +++ b/plugins/manpage/manpagedocumentation.cpp @@ -1,97 +1,99 @@ /* 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 "manpagedocumentation.h" + #include "manpageplugin.h" #include "manpagedocumentationwidget.h" -#include -#include #include +#include +#include + +#include + + 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->initZoom(provider()->name()); view->setDocumentation(IDocumentation::Ptr(this)); view->setDelegateLinks(true); QObject::connect(view, &KDevelop::StandardDocumentationView::linkClicked, ManPageDocumentation::s_provider->model(), &ManPageModel::showItemFromUrl); // apply custom style-sheet to normalize look of the page const QString cssFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevmanpage/manpagedocumentation.css")); view->setOverrideCss(QUrl::fromLocalFile(cssFile)); 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/plugins/okteta/oktetadocument.cpp b/plugins/okteta/oktetadocument.cpp index 9acc3320b3..bee5564527 100644 --- a/plugins/okteta/oktetadocument.cpp +++ b/plugins/okteta/oktetadocument.cpp @@ -1,249 +1,249 @@ /* 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 +#include +#include +#include +#include // Kasten #include #include #include #include #include // KDevelop #include #include #include #include // Sublime #include #include #include #include // KF #include #include #include // Qt #include #include namespace KDevelop { OktetaDocument::OktetaDocument( const QUrl &url , ICore* core ) : Sublime::UrlDocument( core->uiController()->controller(), url ) , IDocument( core ) , mPlugin( nullptr ) , 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 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(); for (Sublime::Area* area : allAreas ) { const QList areaViews = area->views(); for (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 == 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 ); 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/plugins/okteta/oktetadocument.h b/plugins/okteta/oktetadocument.h index 2b823ab9e4..12cafec956 100644 --- a/plugins/okteta/oktetadocument.h +++ b/plugins/okteta/oktetadocument.h @@ -1,98 +1,98 @@ /* 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 OKTETADOCUMENT_H #define OKTETADOCUMENT_H // Kasten core -#include +#include // KDevPlatform #include #include namespace Kasten { class ByteArrayDocument; class AbstractDocument; } namespace KDevelop { class ICore; class OktetaPlugin; class OktetaDocument : public Sublime::UrlDocument, public IDocument { Q_OBJECT public: OktetaDocument( const QUrl &url, ICore* core ); ~OktetaDocument() override; public: // KDevelop::IDocument API KTextEditor::Cursor cursorPosition() const override; bool isActive() const override; QMimeType mimeType() const override; KParts::Part* partForView( QWidget* widget ) const override; DocumentState state() const override; KTextEditor::Document* textDocument() const override; QUrl url() const override; void activate( Sublime::View* view, KParts::MainWindow* mainWindow ) override; bool close( IDocument::DocumentSaveMode = IDocument::Default ) override; void reload() override; bool save( IDocument::DocumentSaveMode = IDocument::Default ) override; void setCursorPosition( const KTextEditor::Cursor& cursor ) override; void setTextSelection( const KTextEditor::Range& range ) override; public: // Sublime::Document API bool closeDocument(bool silent) override; public: OktetaPlugin* plugin() const; Kasten::ByteArrayDocument* byteArrayDocument() const; public: void setPlugin( OktetaPlugin* plugin ); protected Q_SLOTS: // Sublime::Document API Sublime::View* newView( Sublime::Document* document ) override; protected Q_SLOTS: void onByteArrayDocumentChanged(); // Moc is too primitive to know about namespace aliase void onByteArrayDocumentLoaded( Kasten::AbstractDocument* document ); private: OktetaPlugin* mPlugin; Kasten::ByteArrayDocument* mByteArrayDocument; }; inline OktetaPlugin* OktetaDocument::plugin() const { return mPlugin; } inline Kasten::ByteArrayDocument* OktetaDocument::byteArrayDocument() const { return mByteArrayDocument; } } #endif diff --git a/plugins/okteta/oktetaplugin.cpp b/plugins/okteta/oktetaplugin.cpp index a8883a4925..35b87f2743 100644 --- a/plugins/okteta/oktetaplugin.cpp +++ b/plugins/okteta/oktetaplugin.cpp @@ -1,154 +1,154 @@ /* 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 "oktetaplugin.h" // plugin #include "oktetadocumentfactory.h" #include "oktetatoolviewfactory.h" #include "oktetadocument.h" // Okteta Kasten tools -#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 #ifndef BIG_ENDIAN -#include -#include +#include +#include #endif // Okteta Kasten -#include +#include // KDev #include #include #include #include #include #include // KDE #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(OktetaPluginFactory, "kdevokteta.json", registerPlugin(); ) namespace KDevelop { static inline void addTool( IUiController* uiController, Kasten::AbstractToolViewFactory* toolViewFactory, Kasten::AbstractToolFactory* toolFactory ) { OktetaToolViewFactory* factory = new OktetaToolViewFactory( toolViewFactory, toolFactory ); uiController->addToolView( toolViewFactory->title(), factory ); } OktetaPlugin::OktetaPlugin( QObject* parent, const QVariantList& args ) : IPlugin( QStringLiteral("kdevokteta"), parent ) , mDocumentFactory( new OktetaDocumentFactory(this) ) , mViewProfileManager( new Kasten::ByteArrayViewProfileManager() ) { Q_UNUSED(args) IUiController* uiController = core()->uiController(); addTool( uiController, new Kasten::ChecksumToolViewFactory(), new Kasten::ChecksumToolFactory() ); addTool( uiController, new Kasten::FilterToolViewFactory(), new Kasten::FilterToolFactory() ); addTool( uiController, new Kasten::StringsExtractToolViewFactory, new Kasten::StringsExtractToolFactory() ); addTool( uiController, new Kasten::ByteTableToolViewFactory(), new Kasten::ByteTableToolFactory() ); addTool( uiController, new Kasten::InfoToolViewFactory(), new Kasten::InfoToolFactory() ); addTool( uiController, new Kasten::PodDecoderToolViewFactory(), new Kasten::PodDecoderToolFactory() ); // disable Okteta Structures tool on big-endian as it's disable in kdesdk #ifndef BIG_ENDIAN addTool( uiController, new Kasten::StructuresToolViewFactory(), new Kasten::StructuresToolFactory() ); #endif addTool( uiController, new Kasten::BookmarksToolViewFactory, new Kasten::BookmarksToolFactory() ); KDevelop::IDocumentController* documentController = core()->documentController(); documentController->registerDocumentForMimetype(QStringLiteral("application/octet-stream"), mDocumentFactory); } ContextMenuExtension OktetaPlugin::contextMenuExtension(Context* context, QWidget* parent) { OpenWithContext* openWithContext = dynamic_cast( context ); if( openWithContext && !openWithContext->mimeType().inherits(QStringLiteral("inode/directory"))) { QAction* openAction = new QAction(i18n("Hex Editor"), parent); openAction->setIcon( QIcon::fromTheme(QStringLiteral("document-open")) ); openAction->setData( QVariant::fromValue(openWithContext->urls()) ); connect( openAction, &QAction::triggered, this, &OktetaPlugin::onOpenTriggered ); KDevelop::ContextMenuExtension contextMenuExtension; contextMenuExtension.addAction( KDevelop::ContextMenuExtension::OpenEmbeddedGroup, openAction ); return contextMenuExtension; } return KDevelop::IPlugin::contextMenuExtension(context, parent); } void OktetaPlugin::onOpenTriggered() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); KDevelop::ICore* core = KDevelop::ICore::self(); IDocumentController* documentController = core->documentController(); foreach( const QUrl &url, action->data().value>() ) { IDocument* existingDocument = documentController->documentForUrl(url); if( existingDocument ) if( ! existingDocument->close() ) continue; IDocument* createdDocument = mDocumentFactory->create( url, core ); if( createdDocument ) documentController->openDocument( createdDocument ); } } OktetaPlugin::~OktetaPlugin() { delete mDocumentFactory; } } #include "oktetaplugin.moc" diff --git a/plugins/okteta/oktetaview.cpp b/plugins/okteta/oktetaview.cpp index 9fe65e5f19..96c1b262b0 100644 --- a/plugins/okteta/oktetaview.cpp +++ b/plugins/okteta/oktetaview.cpp @@ -1,54 +1,54 @@ /* 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 "oktetaview.h" // lib #include "oktetaplugin.h" #include "oktetadocument.h" #include "oktetawidget.h" // Okteta Kasten -#include +#include namespace KDevelop { OktetaView::OktetaView( OktetaDocument* document, Kasten::ByteArrayViewProfileSynchronizer* viewProfileSynchronizer ) : Sublime::View( document, View::TakeOwnership ), mByteArrayView( new Kasten::ByteArrayView( document->byteArrayDocument(), viewProfileSynchronizer ) ) { } QWidget* OktetaView::createWidget( QWidget* parent ) { OktetaPlugin* plugin = static_cast( document() )->plugin(); return new OktetaWidget( parent, mByteArrayView, plugin ); } OktetaView::~OktetaView() { delete mByteArrayView; } } diff --git a/plugins/okteta/oktetawidget.cpp b/plugins/okteta/oktetawidget.cpp index 0ad78d1e00..8ce89a2a73 100644 --- a/plugins/okteta/oktetawidget.cpp +++ b/plugins/okteta/oktetawidget.cpp @@ -1,158 +1,158 @@ /* 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 "oktetawidget.h" // plugin #include "oktetadocument.h" #include "oktetaplugin.h" // Okteta Kasten -#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 // Kasten -#include +#include // #include // #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include // KDevelop #include // KDE #include -#include #include #include // Qt #include +#include namespace KDevelop { OktetaWidget::OktetaWidget( QWidget* parent, Kasten::ByteArrayView* byteArrayView, OktetaPlugin* plugin ) : QWidget( parent ), KXMLGUIClient(), mByteArrayView( byteArrayView ) { setComponentName( QStringLiteral("kdevokteta") , QStringLiteral("KDevelop Okteta")); setXMLFile( QStringLiteral("kdevokteta.rc") ); setupActions(plugin); QVBoxLayout* layout = new QVBoxLayout( this ); layout->setMargin( 0 ); QWidget* widget = mByteArrayView->widget(); layout->addWidget( widget ); setFocusProxy( widget ); } void OktetaWidget::setupActions(OktetaPlugin* plugin) { Kasten::ByteArrayViewProfileManager* viewProfileManager = plugin->viewProfileManager(); mControllers = { new Kasten::VersionController(this), new Kasten::ReadOnlyController(this), // TODO: save_as // mControllers.append( new ExportController(mProgram->viewManager(),mProgram->documentManager(),this) ); new Kasten::ZoomController(this), new Kasten::SelectController(this), new Kasten::ClipboardController(this), // if( modus != BrowserViewModus ) // mControllers.append( new Kasten::InsertController(mProgram->viewManager(),mProgram->documentManager(),this) ); // mControllers.append( new Kasten::CopyAsController(mProgram->viewManager(),mProgram->documentManager(),this) ); new Kasten::OverwriteModeController(this), new Kasten::SearchController(this,this), new Kasten::ReplaceController(this,this), // mControllers.append( new Kasten::GotoOffsetController(mGroupedViews,this) ); // mControllers.append( new Kasten::SelectRangeController(mGroupedViews,this) ); new Kasten::BookmarksController(this), new Kasten::PrintController(this), new Kasten::ViewConfigController(this), new Kasten::ViewModeController(this), new Kasten::ViewProfileController(viewProfileManager, mByteArrayView->widget(), this), new Kasten::ViewProfilesManageController(this, viewProfileManager, mByteArrayView->widget()), }; // update the text of the viewprofiles_manage action, to make clear this is just for byte arrays QAction* viewprofilesManageAction = actionCollection()->action(QStringLiteral("settings_viewprofiles_manage")); viewprofilesManageAction->setText( i18nc("@action:inmenu", "Manage Byte Array View Profiles...") ); // Kasten::StatusBar* bottomBar = static_cast( statusBar() ); // mControllers.append( new ViewStatusController(bottomBar) ); // mControllers.append( new ReadOnlyBarController(bottomBar) ); // mControllers.append( new ZoomBarController(bottomBar) ); foreach( Kasten::AbstractXmlGuiController* controller, mControllers ) controller->setTargetModel( mByteArrayView ); #if 0 QDesignerFormWindowManagerInterface* manager = mDocument->form()->core()->formWindowManager(); KActionCollection* ac = actionCollection(); KStandardAction::save( this, SLOT(save()), ac); ac->addAction( "adjust_size", manager->actionAdjustSize() ); ac->addAction( "break_layout", manager->actionBreakLayout() ); ac->addAction( "designer_cut", manager->actionCut() ); ac->addAction( "designer_copy", manager->actionCopy() ); ac->addAction( "designer_paste", manager->actionPaste() ); ac->addAction( "designer_delete", manager->actionDelete() ); ac->addAction( "layout_grid", manager->actionGridLayout() ); ac->addAction( "layout_horiz", manager->actionHorizontalLayout() ); ac->addAction( "layout_vertical", manager->actionVerticalLayout() ); ac->addAction( "layout_split_horiz", manager->actionSplitHorizontal() ); ac->addAction( "layout_split_vert", manager->actionSplitVertical() ); ac->addAction( "designer_undo", manager->actionUndo() ); ac->addAction( "designer_redo", manager->actionRedo() ); ac->addAction( "designer_select_all", manager->actionSelectAll() ); #endif } #if 0 void OktetaWidget::save() { mDocument->save(); } #endif OktetaWidget::~OktetaWidget() { qDeleteAll( mControllers ); } } diff --git a/plugins/patchreview/patchhighlighter.cpp b/plugins/patchreview/patchhighlighter.cpp index d58b71f434..a1961807aa 100644 --- a/plugins/patchreview/patchhighlighter.cpp +++ b/plugins/patchreview/patchhighlighter.cpp @@ -1,691 +1,691 @@ /*************************************************************************** Copyright 2006 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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 "patchhighlighter.h" #include #include #include "patchreview.h" #include "debug.h" #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; namespace { QPointer currentTooltip; KTextEditor::MovingRange* currentTooltipMark; QSize sizeHintForHtml( const QString& html, QSize maxSize ) { QTextDocument doc; doc.setHtml( html ); QSize ret; if( doc.idealWidth() > maxSize.width() ) { doc.setPageSize( QSize( maxSize.width(), 30 ) ); ret.setWidth( maxSize.width() ); }else{ ret.setWidth( doc.idealWidth() ); } ret.setHeight( doc.size().height() ); if( ret.height() > maxSize.height() ) ret.setHeight( maxSize.height() ); return ret; } } const unsigned int PatchHighlighter::m_allmarks = KTextEditor::MarkInterface::markType22 | KTextEditor::MarkInterface::markType23 | KTextEditor::MarkInterface::markType24 | KTextEditor::MarkInterface::markType25 | KTextEditor::MarkInterface::markType26 | KTextEditor::MarkInterface::markType27; void PatchHighlighter::showToolTipForMark(const QPoint& pos, KTextEditor::MovingRange* markRange) { if( currentTooltipMark == markRange && currentTooltip ) return; delete currentTooltip; //Got the difference Diff2::Difference* diff = m_ranges[markRange]; QString html; #if 0 if( diff->hasConflict() ) html += i18n( "Conflict
" ); #endif Diff2::DifferenceStringList lines; html += QLatin1String(""); if( diff->applied() ) { if( !m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Applied.
" ); if( isInsertion( diff ) ) { html += i18n( "Insertion
" ); } else { if( isRemoval( diff ) ) html += i18n( "Removal
" ); html += i18n( "Previous:
" ); lines = diff->sourceLines(); } } else { if( m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Reverted.
" ); if( isRemoval( diff ) ) { html += i18n( "Removal
" ); } else { if( isInsertion( diff ) ) html += i18n( "Insertion
" ); html += i18n( "Alternative:
" ); lines = diff->destinationLines(); } } html += QLatin1String("
"); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; uint currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { QString spanText = string.mid( currentPos, markers[b]->offset() - currentPos ).toHtmlEscaped(); if( markers[b]->type() == Diff2::Marker::End && ( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) ) { html += "" + spanText + ""; }else{ html += spanText; } currentPos = markers[b]->offset(); } html += string.mid(currentPos, string.length()-currentPos).toHtmlEscaped() + QLatin1String("
"); } auto browser = new QTextBrowser; browser->setPalette( QApplication::palette() ); browser->setHtml( html ); int maxHeight = 500; browser->setMinimumSize( sizeHintForHtml( html, QSize( ( ICore::self()->uiController()->activeMainWindow()->width()*2 )/3, maxHeight ) ) ); browser->setMaximumSize( browser->minimumSize() + QSize( 10, 10 ) ); if( browser->minimumHeight() != maxHeight ) browser->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); QVBoxLayout* layout = new QVBoxLayout; layout->setMargin( 0 ); layout->addWidget( browser ); KDevelop::ActiveToolTip* tooltip = new KDevelop::ActiveToolTip( ICore::self()->uiController()->activeMainWindow(), pos + QPoint( 5, -browser->sizeHint().height() - 30 ) ); tooltip->setLayout( layout ); tooltip->resize( tooltip->sizeHint() + QSize( 10, 10 ) ); tooltip->move( pos - QPoint( 0, 20 + tooltip->height() ) ); tooltip->setHandleRect( QRect( pos - QPoint( 15, 15 ), pos + QPoint( 15, 15 ) ) ); currentTooltip = tooltip; currentTooltipMark = markRange; ActiveToolTip::showToolTip( tooltip ); } void PatchHighlighter::markClicked( KTextEditor::Document* doc, const KTextEditor::Mark& mark, bool& handled ) { if( handled || !(mark.type & m_allmarks) ) return; auto range_diff = rangeForMark(mark); m_applying = true; if (range_diff.first) { handled = true; KTextEditor::MovingRange *&range = range_diff.first; Diff2::Difference *&diff = range_diff.second; QString currentText = doc->text( range->toRange() ); removeLineMarker( range ); QString sourceText; QString targetText; for( int a = 0; a < diff->sourceLineCount(); ++a ) { sourceText += diff->sourceLineAt( a )->string(); if( !sourceText.endsWith( '\n' ) ) sourceText += '\n'; } for( int a = 0; a < diff->destinationLineCount(); ++a ) { targetText += diff->destinationLineAt( a )->string(); if( !targetText.endsWith( '\n' ) ) targetText += '\n'; } bool applied = diff->applied(); QString &replace(applied ? targetText : sourceText); QString &replaceWith(applied ? sourceText : targetText); if( currentText.simplified() != replace.simplified() ) { KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n( "Could not apply the change: Text should be \"%1\", but is \"%2\".", replace, currentText ) ); m_applying = false; return; } diff->apply(!applied); KTextEditor::Cursor start = range->start().toCursor(); range->document()->replaceText( range->toRange(), replaceWith ); uint replaceWithLines = replaceWith.count( '\n' ); KTextEditor::Range newRange( start, KTextEditor::Cursor(start.line() + replaceWithLines, start.column()) ); range->setRange( newRange ); addLineMarker( range, diff ); { // After applying the change, show the tooltip again, mainly to update an old tooltip delete currentTooltip; currentTooltip = nullptr; bool h = false; markToolTipRequested( doc, mark, QCursor::pos(), h ); } } m_applying = false; } QPair PatchHighlighter::rangeForMark( const KTextEditor::Mark &mark ) { if (!m_applying) { for( QMap::const_iterator it = m_ranges.constBegin(); it != m_ranges.constEnd(); ++it ) { if (it.value() && it.key()->start().line() <= mark.line && mark.line <= it.key()->end().line()) { return qMakePair(it.key(), it.value()); } } } return qMakePair(nullptr, nullptr); } void PatchHighlighter::markToolTipRequested( KTextEditor::Document*, const KTextEditor::Mark& mark, QPoint pos, bool& handled ) { if( handled ) return; if( mark.type & m_allmarks ) { //There is a mark in this line. Show the old text. auto range = rangeForMark(mark); if( range.first ) { showToolTipForMark( pos, range.first ); handled = true; } } } bool PatchHighlighter::isInsertion( Diff2::Difference* diff ) { return diff->sourceLineCount() == 0; } bool PatchHighlighter::isRemoval( Diff2::Difference* diff ) { return diff->destinationLineCount() == 0; } void PatchHighlighter::performContentChange( KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber ) { QPair, QList > diffChange = m_model->linesChanged( oldLines, newLines, editLineNumber ); QList inserted = diffChange.first; QList removed = diffChange.second; foreach(Diff2::Difference* d, removed) { foreach(Diff2::DifferenceString* s, d->sourceLines()) qCDebug(PLUGIN_PATCHREVIEW) << "removed source" << s->string(); foreach(Diff2::DifferenceString* s, d->destinationLines()) qCDebug(PLUGIN_PATCHREVIEW) << "removed destination" << s->string(); } foreach(Diff2::Difference* d, inserted) { foreach(Diff2::DifferenceString* s, d->sourceLines()) qCDebug(PLUGIN_PATCHREVIEW) << "inserted source" << s->string(); foreach(Diff2::DifferenceString* s, d->destinationLines()) qCDebug(PLUGIN_PATCHREVIEW) << "inserted destination" << s->string(); } // Remove all ranges that are in the same line (the line markers) for (auto it = m_ranges.begin(); it != m_ranges.end();) { if (removed.contains(it.value())) { KTextEditor::MovingRange* r = it.key(); removeLineMarker(r); // is altering m_ranges it = m_ranges.erase(it); delete r; } else { ++it; } } qDeleteAll(removed); KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; foreach( Diff2::Difference* diff, inserted ) { int lineStart = diff->destinationLineNumber(); if ( lineStart > 0 ) { --lineStart; } int lineEnd = diff->destinationLineEnd(); if ( lineEnd > 0 ) { --lineEnd; } KTextEditor::Range newRange( lineStart, 0, lineEnd, 0 ); KTextEditor::MovingRange * r = moving->newMovingRange( newRange ); m_ranges[r] = diff; addLineMarker( r, diff ); } } void PatchHighlighter::textRemoved( KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& oldText ) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "removal range" << range; qCDebug(PLUGIN_PATCHREVIEW) << "removed text" << oldText; KTextEditor::Cursor cursor = range.start(); int startLine = cursor.line(); QStringList removedLines; QStringList remainingLines; if (startLine > 0) { QString above = doc->line(--startLine); removedLines << above; remainingLines << above; } QString changed = doc->line(cursor.line()) + '\n'; removedLines << changed.midRef(0, cursor.column()) + oldText + changed.midRef(cursor.column()); remainingLines << changed; if (doc->documentRange().end().line() > cursor.line()) { QString below = doc->line(cursor.line() + 1); removedLines << below; remainingLines << below; } performContentChange(doc, removedLines, remainingLines, startLine + 1); } void PatchHighlighter::newlineRemoved(KTextEditor::Document* doc, int line) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "remove newline" << line; KTextEditor::Cursor cursor = m_doc->cursorPosition(); int startLine = line - 1; QStringList removedLines; QStringList remainingLines; if (startLine > 0) { QString above = doc->line(--startLine); removedLines << above; remainingLines << above; } QString changed = doc->line(line - 1); if (cursor.line() == line - 1) { removedLines << changed.mid(0, cursor.column()); removedLines << changed.mid(cursor.column()); } else { removedLines << changed; removedLines << QString(); } remainingLines << changed; if (doc->documentRange().end().line() >= line) { QString below = doc->line(line); removedLines << below; remainingLines << below; } performContentChange(doc, removedLines, remainingLines, startLine + 1); } void PatchHighlighter::documentReloaded(KTextEditor::Document* doc) { qCDebug(PLUGIN_PATCHREVIEW) << "re-doing"; //The document was loaded / reloaded if ( !m_model->differences() ) return; KTextEditor::MovingInterface* moving = dynamic_cast( doc ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( doc ); if( !markIface ) return; clear(); KColorScheme scheme( QPalette::Active ); QImage tintedInsertion = QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedInsertion, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); QImage tintedRemoval = QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedRemoval, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); QImage tintedChange = QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedChange, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType22, i18n( "Insertion" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType22, QPixmap::fromImage( tintedInsertion ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType23, i18n( "Removal" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType23, QPixmap::fromImage( tintedRemoval ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType24, i18n( "Change" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType24, QPixmap::fromImage( tintedChange ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType25, i18n( "Insertion" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType25, QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType26, i18n( "Removal" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType26, QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType27, i18n( "Change" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType27, QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ) ); for ( Diff2::DifferenceList::const_iterator it = m_model->differences()->constBegin(); it != m_model->differences()->constEnd(); ++it ) { Diff2::Difference* diff = *it; int line, lineCount; Diff2::DifferenceStringList lines; if( diff->applied() ) { line = diff->destinationLineNumber(); lineCount = diff->destinationLineCount(); lines = diff->destinationLines(); } else { line = diff->sourceLineNumber(); lineCount = diff->sourceLineCount(); lines = diff->sourceLines(); } if ( line > 0 ) line -= 1; KTextEditor::Cursor c( line, 0 ); KTextEditor::Cursor endC( line + lineCount, 0 ); if ( doc->lines() <= c.line() ) c.setLine( doc->lines() - 1 ); if ( doc->lines() <= endC.line() ) endC.setLine( doc->lines() ); if ( endC.isValid() && c.isValid() ) { KTextEditor::MovingRange * r = moving->newMovingRange( KTextEditor::Range( c, endC ) ); m_ranges[r] = diff; addLineMarker( r, diff ); } } } void PatchHighlighter::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { if ( m_applying ) { // Do not interfere with patch application return; } int startLine = cursor.line(); int endColumn = cursor.column() + text.length(); qCDebug(PLUGIN_PATCHREVIEW) << "insertion range" << KTextEditor::Range(cursor, KTextEditor::Cursor(startLine, endColumn)); qCDebug(PLUGIN_PATCHREVIEW) << "inserted text" << text; QStringList removedLines; QStringList insertedLines; if (startLine > 0) { QString above = doc->line(--startLine) + '\n'; removedLines << above; insertedLines << above; } QString changed = doc->line(cursor.line()) + '\n'; removedLines << changed.midRef(0, cursor.column()) + changed.midRef(endColumn); insertedLines << changed; if (doc->documentRange().end().line() > cursor.line()) { QString below = doc->line(cursor.line() + 1) + '\n'; removedLines << below; insertedLines << below; } performContentChange(doc, removedLines, insertedLines, startLine + 1); } void PatchHighlighter::newlineInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "newline range" << KTextEditor::Range(cursor, KTextEditor::Cursor(cursor.line() + 1, 0)); int startLine = cursor.line(); QStringList removedLines; QStringList insertedLines; if (startLine > 0) { QString above = doc->line(--startLine) + '\n'; removedLines << above; insertedLines << above; } insertedLines << QString('\n'); if (doc->documentRange().end().line() > cursor.line()) { QString below = doc->line(cursor.line() + 1) + '\n'; removedLines << below; insertedLines << below; } performContentChange(doc, removedLines, insertedLines, startLine + 1); } PatchHighlighter::PatchHighlighter( Diff2::DiffModel* model, IDocument* kdoc, PatchReviewPlugin* plugin, bool updatePatchFromEdits ) : m_doc( kdoc ), m_plugin( plugin ), m_model( model ), m_applying( false ) { KTextEditor::Document* doc = kdoc->textDocument(); // connect( kdoc, SIGNAL(destroyed(QObject*)), this, SLOT(documentDestroyed()) ); if (updatePatchFromEdits) { connect(doc, &KTextEditor::Document::textInserted, this, &PatchHighlighter::textInserted); connect(doc, &KTextEditor::Document::lineWrapped, this, &PatchHighlighter::newlineInserted); connect(doc, &KTextEditor::Document::textRemoved, this, &PatchHighlighter::textRemoved); connect(doc, &KTextEditor::Document::lineUnwrapped, this, &PatchHighlighter::newlineRemoved); } connect(doc, &KTextEditor::Document::reloaded, this, &PatchHighlighter::documentReloaded); connect(doc, &KTextEditor::Document::destroyed, this, &PatchHighlighter::documentDestroyed); if ( doc->lines() == 0 ) return; if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MarkInterface is not a QObject connect(doc, SIGNAL(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), this, SLOT(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&))); connect(doc, SIGNAL(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)), this, SLOT(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&))); } if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MovingInterface is not a QObject connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); } documentReloaded(doc); } void PatchHighlighter::removeLineMarker( KTextEditor::MovingRange* range ) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; for (int line = range->start().line(); line <= range->end().line(); ++line) { markIface->removeMark(line, m_allmarks); } // Remove all ranges that are in the same line (the line markers) for (auto it = m_ranges.begin(); it != m_ranges.end();) { if (it.key() != range && range->overlaps(it.key()->toRange())) { delete it.key(); it = m_ranges.erase(it); } else { ++it; } } } void PatchHighlighter::addLineMarker( KTextEditor::MovingRange* range, Diff2::Difference* diff ) { KTextEditor::MovingInterface* moving = dynamic_cast( range->document() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( range->document() ); if( !markIface ) return; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); bool isOriginalState = diff->applied() == m_plugin->patch()->isAlreadyApplied(); if( isOriginalState ) { t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 0, 255, 255 ), 20 ) ) ); }else{ t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 255 ), 20 ) ) ); } range->setAttribute( t ); range->setZDepth( -500 ); KTextEditor::MarkInterface::MarkTypes mark; if( isOriginalState ) { mark = KTextEditor::MarkInterface::markType27; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType25; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType26; }else{ mark = KTextEditor::MarkInterface::markType24; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType22; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType23; } markIface->addMark( range->start().line(), mark ); Diff2::DifferenceStringList lines; if( diff->applied() ) lines = diff->destinationLines(); else lines = diff->sourceLines(); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; int currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { if( markers[b]->type() == Diff2::Marker::End ) { if( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) { KTextEditor::MovingRange * r2 = moving->newMovingRange( KTextEditor::Range( KTextEditor::Cursor( a + range->start().line(), currentPos ), KTextEditor::Cursor( a + range->start().line(), markers[b]->offset() ) ) ); m_ranges[r2] = nullptr; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 0 ), 70 ) ) ); r2->setAttribute( t ); r2->setZDepth( -600 ); } } currentPos = markers[b]->offset(); } } } void PatchHighlighter::clear() { if( m_ranges.empty() ) return; KTextEditor::MovingInterface* moving = dynamic_cast( m_doc->textDocument() ); if ( !moving ) return; KTextEditor::MarkInterface* markIface = dynamic_cast( m_doc->textDocument() ); if( !markIface ) return; foreach( int line, markIface->marks().keys() ) { markIface->removeMark( line, m_allmarks ); } // Diff is taking care of its own objects (except removed ones) qDeleteAll( m_ranges.keys() ); m_ranges.clear(); } PatchHighlighter::~PatchHighlighter() { clear(); } IDocument* PatchHighlighter::doc() { return m_doc; } void PatchHighlighter::documentDestroyed() { qCDebug(PLUGIN_PATCHREVIEW) << "document destroyed"; m_ranges.clear(); } void PatchHighlighter::aboutToDeleteMovingInterfaceContent( KTextEditor::Document* ) { qCDebug(PLUGIN_PATCHREVIEW) << "about to delete"; clear(); } QList< KTextEditor::MovingRange* > PatchHighlighter::ranges() const { return m_ranges.keys(); } diff --git a/plugins/patchreview/patchhighlighter.h b/plugins/patchreview/patchhighlighter.h index 0ddfef0bd1..a6641f820e 100644 --- a/plugins/patchreview/patchhighlighter.h +++ b/plugins/patchreview/patchhighlighter.h @@ -1,85 +1,85 @@ /*************************************************************************** Copyright 2006 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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 KDEVPLATFORM_PLUGIN_PATCHHIGHLIGHTER_H #define KDEVPLATFORM_PLUGIN_PATCHHIGHLIGHTER_H +#include + #include #include #include #include #include -#include - namespace Diff2 { class Difference; class DiffModel; } class PatchReviewPlugin; namespace KDevelop { class IDocument; } namespace KTextEditor { class Document; class Range; class Cursor; class Mark; class MovingRange; } ///Delete itself when the document(or textDocument), or Diff-Model is deleted. class PatchHighlighter : public QObject { Q_OBJECT public: PatchHighlighter( Diff2::DiffModel* model, KDevelop::IDocument* doc, PatchReviewPlugin* plugin, bool updatePatchFromEdits ); ~PatchHighlighter() override; KDevelop::IDocument* doc(); QList< KTextEditor::MovingRange* > ranges() const; private Q_SLOTS: void documentReloaded( KTextEditor::Document* ); void documentDestroyed(); void aboutToDeleteMovingInterfaceContent( KTextEditor::Document* ); private: void addLineMarker( KTextEditor::MovingRange* arg1, Diff2::Difference* arg2 ); void removeLineMarker( KTextEditor::MovingRange* range ); void performContentChange( KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber ); QPair rangeForMark(const KTextEditor::Mark &mark); void clear(); QMap< KTextEditor::MovingRange*, Diff2::Difference* > m_ranges; KDevelop::IDocument* m_doc; PatchReviewPlugin* m_plugin; Diff2::DiffModel* m_model; bool m_applying; static const unsigned int m_allmarks; public Q_SLOTS: void markToolTipRequested( KTextEditor::Document*, const KTextEditor::Mark&, QPoint, bool & ); void showToolTipForMark(const QPoint& arg1, KTextEditor::MovingRange* arg2); bool isRemoval( Diff2::Difference* ); bool isInsertion( Diff2::Difference* ); void markClicked( KTextEditor::Document*, const KTextEditor::Mark&, bool& ); void textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text); void newlineInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor); void textRemoved( KTextEditor::Document*, const KTextEditor::Range&, const QString& oldText ); void newlineRemoved(KTextEditor::Document*, int line); }; #endif diff --git a/plugins/patchreview/patchreview.cpp b/plugins/patchreview/patchreview.cpp index 4e6787a3b7..9d8e2c3983 100644 --- a/plugins/patchreview/patchreview.cpp +++ b/plugins/patchreview/patchreview.cpp @@ -1,630 +1,630 @@ /*************************************************************************** Copyright 2006-2009 David Nolden ***************************************************************************/ /*************************************************************************** * * * 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 "patchreview.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 ///Whether arbitrary exceptions that occurred while diff-parsing within the library should be caught #define CATCHLIBDIFF /* Exclude this file from doublequote_chars check as krazy doesn't understand std::string*/ //krazy:excludeall=doublequote_chars #include #include #include #include #include #include #include "patchhighlighter.h" #include "patchreviewtoolview.h" #include "localpatchsource.h" #include "debug.h" using namespace KDevelop; namespace { // Maximum number of files to open directly within a tab when the review is started const int maximumFilesToOpenDirectly = 15; } Q_DECLARE_METATYPE( const Diff2::DiffModel* ) void PatchReviewPlugin::seekHunk( bool forwards, const QUrl& fileName ) { try { qCDebug(PLUGIN_PATCHREVIEW) << forwards << fileName << fileName.isEmpty(); if ( !m_modelList ) throw "no model"; for ( int a = 0; a < m_modelList->modelCount(); ++a ) { const Diff2::DiffModel* model = m_modelList->modelAt( a ); if ( !model || !model->differences() ) continue; QUrl file = urlForFileModel( model ); if ( !fileName.isEmpty() && fileName != file ) continue; IDocument* doc = ICore::self()->documentController()->documentForUrl( file ); if ( doc && m_highlighters.contains( doc->url() ) && m_highlighters[doc->url()] ) { if ( doc->textDocument() ) { const QList ranges = m_highlighters[doc->url()]->ranges(); KTextEditor::View * v = doc->activeTextView(); if ( v ) { int bestLine = -1; KTextEditor::Cursor c = v->cursorPosition(); for ( QList::const_iterator it = ranges.begin(); it != ranges.end(); ++it ) { int line = ( *it )->start().line(); if ( forwards ) { if ( line > c.line() && ( bestLine == -1 || line < bestLine ) ) bestLine = line; } else { if ( line < c.line() && ( bestLine == -1 || line > bestLine ) ) bestLine = line; } } if ( bestLine != -1 ) { v->setCursorPosition( KTextEditor::Cursor( bestLine, 0 ) ); return; } else if(fileName.isEmpty()) { int next = qBound(0, forwards ? a+1 : a-1, m_modelList->modelCount()-1); if (next < maximumFilesToOpenDirectly) { ICore::self()->documentController()->openDocument(urlForFileModel(m_modelList->modelAt(next))); } } } } } } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } qCDebug(PLUGIN_PATCHREVIEW) << "no matching hunk found"; } void PatchReviewPlugin::addHighlighting( const QUrl& highlightFile, IDocument* document ) { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); if ( file != highlightFile ) continue; qCDebug(PLUGIN_PATCHREVIEW) << "highlighting" << file.toDisplayString(); IDocument* doc = document; if( !doc ) doc = ICore::self()->documentController()->documentForUrl( file ); qCDebug(PLUGIN_PATCHREVIEW) << "highlighting file" << file << "with doc" << doc; if ( !doc || !doc->textDocument() ) continue; removeHighlighting( file ); m_highlighters[file] = new PatchHighlighter( model, doc, this, dynamic_cast(m_patch.data()) == nullptr ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::highlightPatch() { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { const Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); addHighlighting( file ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::removeHighlighting( const QUrl& file ) { if ( file.isEmpty() ) { ///Remove all highlighting qDeleteAll( m_highlighters ); m_highlighters.clear(); } else { HighlightMap::iterator it = m_highlighters.find( file ); if ( it != m_highlighters.end() ) { delete *it; m_highlighters.erase( it ); } } } void PatchReviewPlugin::notifyPatchChanged() { if (m_patch) { qCDebug(PLUGIN_PATCHREVIEW) << "notifying patch change: " << m_patch->file(); m_updateKompareTimer->start( 500 ); } else { m_updateKompareTimer->stop(); } } void PatchReviewPlugin::forceUpdate() { if( m_patch ) { // don't trigger an update if we know the plugin cannot update itself VCSDiffPatchSource *vcsPatch = dynamic_cast(m_patch.data()); if (!vcsPatch || vcsPatch->m_updater) { m_patch->update(); notifyPatchChanged(); } } } void PatchReviewPlugin::updateKompareModel() { if ( !m_patch ) { ///TODO: this method should be cleaned up, it can be called by the timer and /// e.g. https://bugs.kde.org/show_bug.cgi?id=267187 shows how it could /// lead to asserts before... return; } qCDebug(PLUGIN_PATCHREVIEW) << "updating model"; removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; delete m_diffSettings; { IDocument* patchDoc = ICore::self()->documentController()->documentForUrl( m_patch->file() ); if( patchDoc ) patchDoc->reload(); } QString patchFile; if( m_patch->file().isLocalFile() ) patchFile = m_patch->file().toLocalFile(); else if( m_patch->file().isValid() && !m_patch->file().isEmpty() ) { patchFile = QStandardPaths::writableLocation(QStandardPaths::TempLocation); bool ret = KIO::copy(m_patch->file(), QUrl::fromLocalFile(patchFile), KIO::HideProgressInfo)->exec(); if( !ret ) { qCWarning(PLUGIN_PATCHREVIEW) << "Problem while downloading: " << m_patch->file() << "to" << patchFile; patchFile.clear(); } } if (!patchFile.isEmpty()) //only try to construct the model if we have a patch to load try { m_diffSettings = new DiffSettings( nullptr ); m_kompareInfo.reset( new Kompare::Info() ); m_kompareInfo->localDestination = patchFile; m_kompareInfo->localSource = m_patch->baseDir().toLocalFile(); m_kompareInfo->depth = m_patch->depth(); m_kompareInfo->applied = m_patch->isAlreadyApplied(); m_modelList.reset( new Diff2::KompareModelList( m_diffSettings.data(), new QWidget, this ) ); m_modelList->slotKompareInfo( m_kompareInfo.data() ); try { m_modelList->openDirAndDiff(); } catch ( const QString & str ) { throw; } catch ( ... ) { throw QStringLiteral( "lib/libdiff2 crashed, memory may be corrupted. Please restart kdevelop." ); } for (m_depth = 0; m_depth < 10; ++m_depth) { bool allFound = true; for( int i = 0; i < m_modelList->modelCount(); i++ ) { if (!QFile::exists(urlForFileModel(m_modelList->modelAt(i)).toLocalFile())) { allFound = false; } } if (allFound) { break; // found depth } } emit patchChanged(); for( int i = 0; i < m_modelList->modelCount(); i++ ) { const Diff2::DiffModel* model = m_modelList->modelAt( i ); for( int j = 0; j < model->differences()->count(); j++ ) { model->differences()->at( j )->apply( m_patch->isAlreadyApplied() ); } } highlightPatch(); return; } catch ( const QString & str ) { KMessageBox::error( nullptr, str, i18n( "Kompare Model Update" ) ); } catch ( const char * str ) { KMessageBox::error( nullptr, str, i18n( "Kompare Model Update" ) ); } removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; m_kompareInfo.reset( nullptr ); delete m_diffSettings; emit patchChanged(); } K_PLUGIN_FACTORY_WITH_JSON(KDevPatchReviewFactory, "kdevpatchreview.json", registerPlugin();) class PatchReviewToolViewFactory : public KDevelop::IToolViewFactory { public: explicit PatchReviewToolViewFactory( PatchReviewPlugin *plugin ) : m_plugin( plugin ) {} QWidget* create( QWidget *parent = nullptr ) override { return new PatchReviewToolView( parent, m_plugin ); } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.PatchReview"); } private: PatchReviewPlugin *m_plugin; }; PatchReviewPlugin::~PatchReviewPlugin() { removeHighlighting(); // Tweak to work around a crash on OS X; see https://bugs.kde.org/show_bug.cgi?id=338829 // and http://qt-project.org/forums/viewthread/38406/#162801 // modified tweak: use setPatch() and deleteLater in that method. setPatch(nullptr); } void PatchReviewPlugin::clearPatch( QObject* _patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "clearing patch" << _patch << "current:" << ( QObject* )m_patch; IPatchSource::Ptr patch( ( IPatchSource* )_patch ); if( patch == m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "is current patch"; setPatch( IPatchSource::Ptr( new LocalPatchSource ) ); } } void PatchReviewPlugin::closeReview() { if( m_patch ) { IDocument* patchDocument = ICore::self()->documentController()->documentForUrl( m_patch->file() ); if (patchDocument) { // Revert modifications to the text document which we've done in updateReview patchDocument->setPrettyName( QString() ); patchDocument->textDocument()->setReadWrite( true ); KTextEditor::ModificationInterface* modif = dynamic_cast( patchDocument->textDocument() ); modif->setModifiedOnDiskWarning( true ); } removeHighlighting(); m_modelList.reset( nullptr ); m_depth = 0; if( !dynamic_cast( m_patch.data() ) ) { // make sure "show" button still openes the file dialog to open a custom patch file setPatch( new LocalPatchSource ); } else emit patchChanged(); Sublime::Area* area = ICore::self()->uiController()->activeArea(); if( area->objectName() == QLatin1String("review") ) { if( ICore::self()->documentController()->saveAllDocuments() ) ICore::self()->uiController()->switchToArea( QStringLiteral("code"), KDevelop::IUiController::ThisWindow ); } } } void PatchReviewPlugin::cancelReview() { if( m_patch ) { m_patch->cancelReview(); closeReview(); } } void PatchReviewPlugin::finishReview(const QList& selection) { if( m_patch && m_patch->finishReview( selection ) ) { closeReview(); } } void PatchReviewPlugin::startReview( IPatchSource* patch, IPatchReview::ReviewMode mode ) { Q_UNUSED( mode ); emit startingNewReview(); setPatch( patch ); QMetaObject::invokeMethod( this, "updateReview", Qt::QueuedConnection ); } void PatchReviewPlugin::switchToEmptyReviewArea() { foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == QLatin1String("review")) { area->clearDocuments(); } } if ( ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("review") ) ICore::self()->uiController()->switchToArea( QStringLiteral("review"), KDevelop::IUiController::ThisWindow ); } QUrl PatchReviewPlugin::urlForFileModel( const Diff2::DiffModel* model ) { KDevelop::Path path(QDir::cleanPath(m_patch->baseDir().toLocalFile())); QVector destPath = KDevelop::Path("/"+model->destinationPath()).segments(); if (destPath.size() >= (int)m_depth) { destPath = destPath.mid(m_depth); } foreach(const QString& segment, destPath) { path.addPath(segment); } path.addPath(model->destinationFile()); return path.toUrl(); } void PatchReviewPlugin::updateReview() { if( !m_patch ) return; m_updateKompareTimer->stop(); switchToEmptyReviewArea(); KDevelop::IDocumentController *docController = ICore::self()->documentController(); // don't add documents opened automatically to the Files/Open Recent list IDocument* futureActiveDoc = docController->openDocument( m_patch->file(), KTextEditor::Range::invalid(), IDocumentController::DoNotAddToRecentOpen ); updateKompareModel(); if ( !m_modelList || !futureActiveDoc || !futureActiveDoc->textDocument() ) { // might happen if e.g. openDocument dialog was cancelled by user // or under the theoretic possibility of a non-text document getting opened return; } futureActiveDoc->textDocument()->setReadWrite( false ); futureActiveDoc->setPrettyName( i18n( "Overview" ) ); KTextEditor::ModificationInterface* modif = dynamic_cast( futureActiveDoc->textDocument() ); modif->setModifiedOnDiskWarning( false ); docController->activateDocument( futureActiveDoc ); PatchReviewToolView* toolView = qobject_cast(ICore::self()->uiController()->findToolView( i18n( "Patch Review" ), m_factory )); Q_ASSERT( toolView ); //Open all relates files for( int a = 0; a < m_modelList->modelCount() && a < maximumFilesToOpenDirectly; ++a ) { QUrl absoluteUrl = urlForFileModel( m_modelList->modelAt( a ) ); if (absoluteUrl.isRelative()) { KMessageBox::error( nullptr, i18n("The base directory of the patch must be an absolute directory"), i18n( "Patch Review" ) ); break; } if( QFileInfo::exists( absoluteUrl.toLocalFile() ) && absoluteUrl.toLocalFile() != QLatin1String("/dev/null") ) { toolView->open( absoluteUrl, false ); }else{ // Maybe the file was deleted qCDebug(PLUGIN_PATCHREVIEW) << "could not open" << absoluteUrl << "because it doesn't exist"; } } } void PatchReviewPlugin::setPatch( IPatchSource* patch ) { if ( patch == m_patch ) { return; } if( m_patch ) { disconnect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); if ( qobject_cast( m_patch ) ) { // make sure we don't leak this // TODO: what about other patch sources? m_patch->deleteLater(); } } m_patch = patch; if( m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "setting new patch" << patch->name() << "with file" << patch->file() << "basedir" << patch->baseDir(); connect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); } QString finishText = i18n( "Finish Review" ); if( m_patch && !m_patch->finishReviewCustomText().isEmpty() ) finishText = m_patch->finishReviewCustomText(); m_finishReview->setText( finishText ); m_finishReview->setEnabled( patch ); notifyPatchChanged(); } PatchReviewPlugin::PatchReviewPlugin( QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevpatchreview"), parent ), m_patch( nullptr ), m_factory( new PatchReviewToolViewFactory( this ) ) { qRegisterMetaType( "const Diff2::DiffModel*" ); setXMLFile( QStringLiteral("kdevpatchreview.rc") ); connect( ICore::self()->documentController(), &IDocumentController::documentClosed, this, &PatchReviewPlugin::documentClosed ); connect( ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &PatchReviewPlugin::textDocumentCreated ); connect( ICore::self()->documentController(), &IDocumentController::documentSaved, this, &PatchReviewPlugin::documentSaved ); m_updateKompareTimer = new QTimer( this ); m_updateKompareTimer->setSingleShot( true ); connect( m_updateKompareTimer, &QTimer::timeout, this, &PatchReviewPlugin::updateKompareModel ); m_finishReview = new QAction(i18n("Finish Review"), this); m_finishReview->setIcon( QIcon::fromTheme( QStringLiteral("dialog-ok") ) ); actionCollection()->setDefaultShortcut( m_finishReview, Qt::CTRL|Qt::Key_Return ); actionCollection()->addAction(QStringLiteral("commit_or_finish_review"), m_finishReview); foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == QLatin1String("review")) area->addAction(m_finishReview); } core()->uiController()->addToolView( i18n( "Patch Review" ), m_factory, IUiController::None ); areaChanged(ICore::self()->uiController()->activeArea()); } void PatchReviewPlugin::documentClosed( IDocument* doc ) { removeHighlighting( doc->url() ); } void PatchReviewPlugin::documentSaved( IDocument* doc ) { // Only update if the url is not the patch-file, because our call to // the reload() KTextEditor function also causes this signal, // which would lead to an endless update loop. // Also, don't automatically update local patch sources, because // they may correspond to static files which don't match any more // after an edit was done. if( m_patch && doc->url() != m_patch->file() && !dynamic_cast(m_patch.data()) ) forceUpdate(); } void PatchReviewPlugin::textDocumentCreated( IDocument* doc ) { if (m_patch) { addHighlighting( doc->url(), doc ); } } void PatchReviewPlugin::unload() { core()->uiController()->removeToolView( m_factory ); KDevelop::IPlugin::unload(); } void PatchReviewPlugin::areaChanged(Sublime::Area* area) { bool reviewing = area->objectName() == QLatin1String("review"); m_finishReview->setEnabled(reviewing); if(!reviewing) { closeReview(); } } KDevelop::ContextMenuExtension PatchReviewPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { QList urls; if ( context->type() == KDevelop::Context::FileContext ) { KDevelop::FileContext* filectx = static_cast(context); urls = filectx->urls(); } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* projctx = static_cast(context); foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { if ( item->file() ) { urls << item->file()->path().toUrl(); } } } else if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext* econtext = static_cast(context); urls << econtext->url(); } if (urls.size() == 1) { QAction* reviewAction = new QAction( QIcon::fromTheme(QStringLiteral("text-x-patch")), i18n("Review Patch"), parent); reviewAction->setData(QVariant(urls[0])); connect( reviewAction, &QAction::triggered, this, &PatchReviewPlugin::executeFileReviewAction ); ContextMenuExtension cm; cm.addAction( KDevelop::ContextMenuExtension::VcsGroup, reviewAction ); return cm; } return KDevelop::IPlugin::contextMenuExtension(context, parent); } void PatchReviewPlugin::executeFileReviewAction() { QAction* reviewAction = qobject_cast(sender()); KDevelop::Path path(reviewAction->data().toUrl()); LocalPatchSource* ps = new LocalPatchSource(); ps->setFilename(path.toUrl()); ps->setBaseDir(path.parent().toUrl()); ps->setAlreadyApplied(true); ps->createWidget(); startReview(ps, OpenAndRaise); } #include "patchreview.moc" // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on diff --git a/plugins/perforce/perforceplugin.cpp b/plugins/perforce/perforceplugin.cpp index 577122de8d..9c45a7c3ca 100644 --- a/plugins/perforce/perforceplugin.cpp +++ b/plugins/perforce/perforceplugin.cpp @@ -1,689 +1,689 @@ /*************************************************************************** * Copyright 2010 Morten Danielsen Volden * * * * 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 "perforceplugin.h" #include "ui/perforceimportmetadatawidget.h" #include "debug.h" #include "qtcompat_p.h" #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString toRevisionName(const KDevelop::VcsRevision& rev, const QString& currentRevision=QString()) { bool ok; int previous = currentRevision.toInt(&ok); previous--; QString tmp; switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return QStringLiteral("#head"); case VcsRevision::Base: return QStringLiteral("#have"); case VcsRevision::Working: return QStringLiteral("#have"); case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); tmp.setNum(previous); tmp.prepend(QLatin1Char('#')); return tmp; case VcsRevision::Start: return QString(); case VcsRevision::UserSpecialType: //Not used Q_ASSERT(false && "i don't know how to do that"); } break; case VcsRevision::GlobalNumber: tmp.append(QLatin1Char('#') + rev.revisionValue().toString()); return tmp; case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserType: Q_ASSERT(false); } return QString(); } VcsItemEvent::Actions actionsFromString(QString const& changeDescription) { if(changeDescription == QLatin1String("add")) return VcsItemEvent::Added; if(changeDescription == QLatin1String("delete")) return VcsItemEvent::Deleted; return VcsItemEvent::Modified; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } } PerforcePlugin::PerforcePlugin(QObject* parent, const QVariantList&): KDevelop::IPlugin(QStringLiteral("kdevperforce"), parent) , m_common(new KDevelop::VcsPluginHelper(this, this)) , m_perforceConfigName(QStringLiteral("p4config.txt")) , m_perforceExecutable(QStringLiteral("p4")) , m_edit_action(nullptr) { QProcessEnvironment currentEviron(QProcessEnvironment::systemEnvironment()); QString tmp(currentEviron.value(QStringLiteral("P4CONFIG"))); if (tmp.isEmpty()) { // We require the P4CONFIG variable to be set because the perforce command line client will need it setErrorDescription(i18n("The variable P4CONFIG is not set. Is perforce installed on the system?")); return; } else { m_perforceConfigName = tmp; } qCDebug(PLUGIN_PERFORCE) << "The value of P4CONFIG is : " << tmp; } PerforcePlugin::~PerforcePlugin() { } QString PerforcePlugin::name() const { return i18n("Perforce"); } KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* parent) { return new PerforceImportMetadataWidget(parent); } bool PerforcePlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { Q_UNUSED(remoteLocation); // TODO return false; } bool PerforcePlugin::isValidDirectory(const QUrl & dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(dirPath.toLocalFile()) : finfo.absoluteDir(); do { if (dir.exists(m_perforceConfigName)) { return true; } } while (dir.cdUp()); return false; } bool PerforcePlugin::isVersionControlled(const QUrl& localLocation) { QFileInfo fsObject(localLocation.toLocalFile()); if (fsObject.isDir()) { return isValidDirectory(localLocation); } return parseP4fstat(fsObject, KDevelop::OutputJob::Silent); } DVcsJob* PerforcePlugin::p4fstatJob(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(curFile.absolutePath(), this, verbosity); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); return job; } bool PerforcePlugin::parseP4fstat(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(p4fstatJob(curFile, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { qCDebug(PLUGIN_PERFORCE) << "Perforce returned: " << job->output(); if (!job->output().isEmpty()) return true; } return false; } QString PerforcePlugin::getRepositoryName(const QFileInfo& curFile) { static const QString DEPOT_FILE_STR(QStringLiteral("... depotFile ")); QString ret; QScopedPointer job(p4fstatJob(curFile, KDevelop::OutputJob::Silent)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { const QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); for (const QString& line : outputLines) { int idx(line.indexOf(DEPOT_FILE_STR)); if (idx != -1) { ret = line.mid(DEPOT_FILE_STR.size()); return ret; } } } } return ret; } KDevelop::VcsJob* PerforcePlugin::repositoryLocation(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "add" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::remove(const QList& /*localLocations*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::copy(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDstn*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::move(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDst*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "fstat" << curFile.fileName(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4StatusOutput); return job; } KDevelop::VcsJob* PerforcePlugin::revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.count() != 1) { KMessageBox::error(nullptr, i18n("Please select only one item for this operation")); return nullptr; } QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "revert" << curFile.fileName(); return job; } KDevelop::VcsJob* PerforcePlugin::update(const QList& localLocations, const KDevelop::VcsRevision& /*rev*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); //*job << m_perforceExecutable << "-p" << "127.0.0.1:1666" << "info"; - Let's keep this for now it's very handy for debugging QString fileOrDirectory; if (curFile.isDir()) fileOrDirectory = curFile.absolutePath() + "/..."; else fileOrDirectory = curFile.fileName(); *job << m_perforceExecutable << "sync" << fileOrDirectory; return job; } KDevelop::VcsJob* PerforcePlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "submit" << "-d" << message << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { QFileInfo curFile(fileOrDirectory.toLocalFile()); QString depotSrcFileName = getRepositoryName(curFile); QString depotDstFileName = depotSrcFileName; depotSrcFileName.append(toRevisionName(srcRevision, dstRevision.prettyValue())); // dstRevision acutally contains the number that we want to take previous of DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); switch (dstRevision.revisionType()) { case VcsRevision::FileNumber: case VcsRevision::GlobalNumber: depotDstFileName.append(QLatin1Char('#') + dstRevision.prettyValue()); *job << m_perforceExecutable << "diff2" << "-u" << depotSrcFileName << depotDstFileName; break; case VcsRevision::Special: switch (dstRevision.revisionValue().value()) { case VcsRevision::Working: *job << m_perforceExecutable << "diff" << "-du" << depotSrcFileName; break; case VcsRevision::Start: case VcsRevision::UserSpecialType: default: break; } default: break; } connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4DiffOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, long unsigned int limit) { static QString lastSeenChangeList; QFileInfo curFile(localLocation.toLocalFile()); QString localLocationAndRevStr = localLocation.toLocalFile(); DVcsJob* job = new DVcsJob(urlDir(localLocation), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit"; if(limit > 0) *job << QStringLiteral("-m %1").arg(limit); if (curFile.isDir()) { localLocationAndRevStr.append("/..."); } QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) { // This is not too nice, but perforce argument for restricting output from filelog does not Work :-( // So putting this in so we do not end up in infinite loop calling log, if(revStr == lastSeenChangeList) { localLocationAndRevStr.append("#none"); lastSeenChangeList.clear(); } else { localLocationAndRevStr.append(revStr); lastSeenChangeList = revStr; } } *job << localLocationAndRevStr; qCDebug(PLUGIN_PERFORCE) << "Issuing the following command to p4: " << job->dvcsCommand(); connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/, const KDevelop::VcsRevision& /*limit*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "filelog" << "-lit" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4LogOutput); return job; } KDevelop::VcsJob* PerforcePlugin::annotate(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/) { QFileInfo curFile(localLocation.toLocalFile()); if (curFile.isDir()) { KMessageBox::error(nullptr, i18n("Please select a file for this operation")); return errorsFound(i18n("Directory not supported for this operation")); } DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "annotate" << "-qi" << localLocation; connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4AnnotateOutput); return job; } KDevelop::VcsJob* PerforcePlugin::resolve(const QList& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::createWorkingCopy(const KDevelop::VcsLocation& /*sourceRepository*/, const QUrl& /*destinationDirectory*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/) { return nullptr; } KDevelop::VcsLocationWidget* PerforcePlugin::vcsLocation(QWidget* parent) const { return new StandardVcsLocationWidget(parent); } KDevelop::VcsJob* PerforcePlugin::edit(const QList& localLocations) { QFileInfo curFile(localLocations.front().toLocalFile()); DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose); setEnvironmentForJob(job, curFile); *job << m_perforceExecutable << "edit" << localLocations; return job; } KDevelop::VcsJob* PerforcePlugin::edit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::unedit(const QUrl& /*localLocation*/) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::localRevision(const QUrl& /*localLocation*/, KDevelop::VcsRevision::RevisionType) { return nullptr; } KDevelop::VcsJob* PerforcePlugin::import(const QString& /*commitMessage*/, const QUrl& /*sourceDirectory*/, const KDevelop::VcsLocation& /*destinationRepository*/) { return nullptr; } KDevelop::ContextMenuExtension PerforcePlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { m_common->setupFromContext(context); const QList & ctxUrlList = m_common->contextUrlList(); bool hasVersionControlledEntries = false; for( const QUrl& url : ctxUrlList) { if (isValidDirectory(url)) { hasVersionControlledEntries = true; break; } } if (!hasVersionControlledEntries) return IPlugin::contextMenuExtension(context, parent); QMenu * perforceMenu = m_common->commonActions(parent); perforceMenu->addSeparator(); perforceMenu->addSeparator(); if (!m_edit_action) { m_edit_action = new QAction(i18n("Edit"), this); connect(m_edit_action, &QAction::triggered, this, & PerforcePlugin::ctxEdit); } perforceMenu->addAction(m_edit_action); ContextMenuExtension menuExt; menuExt.addAction(ContextMenuExtension::VcsGroup, perforceMenu->menuAction()); return menuExt; } void PerforcePlugin::ctxEdit() { QList const & ctxUrlList = m_common->contextUrlList(); KDevelop::ICore::self()->runController()->registerJob(edit(ctxUrlList)); } void PerforcePlugin::setEnvironmentForJob(DVcsJob* job, const QFileInfo& curFile) { KProcess* jobproc = job->process(); jobproc->setEnv(QStringLiteral("P4CONFIG"), m_perforceConfigName); if (curFile.isDir()) { jobproc->setEnv(QStringLiteral("PWD"), curFile.filePath()); } else { jobproc->setEnv(QStringLiteral("PWD"), curFile.absolutePath()); } } QList PerforcePlugin::getQvariantFromLogOutput(QStringList const& outputLines) { static const QString LOGENTRY_START(QStringLiteral("... #")); static const QString DEPOTMESSAGE_START(QStringLiteral("... .")); QMap changes; QList commits; QString currentFileName; QString changeNumberStr, author,changeDescription, commitMessage; VcsEvent currentVcsEvent; VcsItemEvent currentRepoFile; VcsRevision rev; int indexofAt; int changeNumber = 0; for (const QString& line : outputLines) { if (!line.startsWith(LOGENTRY_START) && !line.startsWith(DEPOTMESSAGE_START) && !line.startsWith('\t')) { currentFileName = line; } if(line.indexOf(LOGENTRY_START) != -1) { // expecting the Logentry line to be of the form: //... #5 change 10 edit on 2010/12/06 12:07:31 by mvo@testbed (text) changeNumberStr = line.section(' ', 3, 3 ); // We use global change number changeNumber = changeNumberStr.toInt(); author = line.section(' ', 9, 9); changeDescription = line.section(' ' , 4, 4 ); indexofAt = author.indexOf('@'); author.remove(indexofAt, author.size()); // Only keep the username itself rev.setRevisionValue(changeNumberStr, KDevelop::VcsRevision::GlobalNumber); changes[changeNumber].setRevision(rev); changes[changeNumber].setAuthor(author); changes[changeNumber].setDate(QDateTime::fromString(line.section(' ', 6, 7), QStringLiteral("yyyy/MM/dd hh:mm:ss"))); currentRepoFile.setRepositoryLocation(currentFileName); currentRepoFile.setActions( actionsFromString(changeDescription) ); changes[changeNumber].addItem(currentRepoFile); commitMessage.clear(); // We have a new entry, clear message } if (line.startsWith('\t') || line.startsWith(DEPOTMESSAGE_START)) { commitMessage += line.trimmed() + '\n'; changes[changeNumber].setMessage(commitMessage); } } for(const auto& item : qAsConst(changes)) { commits.prepend(QVariant::fromValue(item)); } return commits; } void PerforcePlugin::parseP4StatusOutput(DVcsJob* job) { const QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QVariantList statuses; static const QString ACTION_STR(QStringLiteral("... action ")); static const QString CLIENT_FILE_STR(QStringLiteral("... clientFile ")); VcsStatusInfo status; status.setState(VcsStatusInfo::ItemUserState); for (const QString& line : outputLines) { int idx(line.indexOf(ACTION_STR)); if (idx != -1) { QString curr = line.mid(ACTION_STR.size()); if (curr == QLatin1String("edit")) { status.setState(VcsStatusInfo::ItemModified); } else if (curr == QLatin1String("add")) { status.setState(VcsStatusInfo::ItemAdded); } else { status.setState(VcsStatusInfo::ItemUserState); } continue; } idx = line.indexOf(CLIENT_FILE_STR); if (idx != -1) { QUrl fileUrl = QUrl::fromLocalFile(line.mid(CLIENT_FILE_STR.size())); status.setUrl(fileUrl); } } statuses.append(qVariantFromValue(status)); job->setResults(statuses); } void PerforcePlugin::parseP4LogOutput(KDevelop::DVcsJob* job) { QList commits(getQvariantFromLogOutput(job->output().split('\n', QString::SkipEmptyParts))); job->setResults(commits); } void PerforcePlugin::parseP4DiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); QDir dir(job->directory()); do { if (dir.exists(m_perforceConfigName)) { break; } } while (dir.cdUp()); diff.setBaseDiff(QUrl::fromLocalFile(dir.absolutePath())); job->setResults(qVariantFromValue(diff)); } void PerforcePlugin::parseP4AnnotateOutput(DVcsJob *job) { QVariantList results; /// First get the changelists for this file QStringList strList(job->dvcsCommand()); QString localLocation(strList.last()); /// ASSUMPTION WARNING - localLocation is the last in the annotate command KDevelop::VcsRevision dummyRev; QScopedPointer logJob(new DVcsJob(job->directory(), this, OutputJob::Silent)); QFileInfo curFile(localLocation); setEnvironmentForJob(logJob.data(), curFile); *logJob << m_perforceExecutable << "filelog" << "-lit" << localLocation; QList commits; if (logJob->exec() && logJob->status() == KDevelop::VcsJob::JobSucceeded) { if (!job->output().isEmpty()) { commits = getQvariantFromLogOutput(logJob->output().split('\n', QString::SkipEmptyParts)); } } VcsEvent item; QMap globalCommits; /// Move the VcsEvents to a more suitable data strucure for (QList::const_iterator commitsIt = commits.constBegin(), commitsEnd = commits.constEnd(); commitsIt != commitsEnd; ++commitsIt) { if(commitsIt->canConvert()) { item = commitsIt->value(); } globalCommits.insert(item.revision().revisionValue().toLongLong(), item); } QStringList lines = job->output().split('\n'); int lineNumber = 0; QMap::iterator currentEvent; bool convertToIntOk(false); int globalRevisionInt(0); QString globalRevision; for (QStringList::const_iterator it = lines.constBegin(), itEnd = lines.constEnd(); it != itEnd; ++it) { if (it->isEmpty()) { continue; } globalRevision = it->left(it->indexOf(':')); VcsAnnotationLine annotation; annotation.setLineNumber(lineNumber); VcsRevision rev; rev.setRevisionValue(globalRevision, KDevelop::VcsRevision::GlobalNumber); annotation.setRevision(rev); // Find the other info in the commits list globalRevisionInt = globalRevision.toLongLong(&convertToIntOk); if(convertToIntOk) { currentEvent = globalCommits.find(globalRevisionInt); annotation.setAuthor(currentEvent->author()); annotation.setCommitMessage(currentEvent->message()); annotation.setDate(currentEvent->date()); } results += qVariantFromValue(annotation); ++lineNumber; } job->setResults(results); } KDevelop::VcsJob* PerforcePlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } diff --git a/plugins/perforce/perforcepluginmetadata.cpp b/plugins/perforce/perforcepluginmetadata.cpp index ba3ab5cfe4..5cf29ce468 100644 --- a/plugins/perforce/perforcepluginmetadata.cpp +++ b/plugins/perforce/perforcepluginmetadata.cpp @@ -1,34 +1,34 @@ /*************************************************************************** * Copyright 2015 Morten Danielsen Volden * * * * 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 "perforceplugin.h" -#include +#include // This file only exists so that the tests can be built: // test_kdevperforce builds perforceplugin.cpp again but in a different directory. // This means that the kdevperforce.json file is no longer found. // Since the JSON metadata is not needed in the test, we simply move // the K_PLUGIN_FACTORY_WITH_JSON to a separate file. K_PLUGIN_FACTORY_WITH_JSON(KdevPerforceFactory, "kdevperforce.json", registerPlugin();) #include "perforcepluginmetadata.moc" diff --git a/plugins/problemreporter/problemhighlighter.cpp b/plugins/problemreporter/problemhighlighter.cpp index ae90911066..186113285c 100644 --- a/plugins/problemreporter/problemhighlighter.cpp +++ b/plugins/problemreporter/problemhighlighter.cpp @@ -1,228 +1,227 @@ /* * KDevelop Problem Reporter * * Copyright 2008 Hamish Rodda * Copyright 2008-2009 David Nolden * * 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 "problemhighlighter.h" -#include -#include -#include -#include - #include #include #include #include #include #include #include #include #include #include -#include - #include +#include +#include +#include +#include +#include + using namespace KTextEditor; using namespace KDevelop; namespace { QColor colorForSeverity(IProblem::Severity severity) { KColorScheme scheme(QPalette::Active); switch (severity) { case IProblem::Error: return scheme.foreground(KColorScheme::NegativeText).color(); case IProblem::Warning: return scheme.foreground(KColorScheme::NeutralText).color(); case IProblem::Hint: default: return scheme.foreground(KColorScheme::PositiveText).color(); } } } ProblemHighlighter::ProblemHighlighter(KTextEditor::Document* document) : m_document(document) { Q_ASSERT(m_document); connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemHighlighter::settingsChanged); connect(m_document.data(), &Document::aboutToReload, this, &ProblemHighlighter::clearProblems); if (qobject_cast(m_document)) { // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearProblems())); } connect(m_document, SIGNAL(aboutToRemoveText(KTextEditor::Range)), this, SLOT(aboutToRemoveText(KTextEditor::Range))); } void ProblemHighlighter::settingsChanged() { // Re-highlight setProblems(m_problems); } ProblemHighlighter::~ProblemHighlighter() { if (m_topHLRanges.isEmpty() || !m_document) return; qDeleteAll(m_topHLRanges); } void ProblemHighlighter::setProblems(const QVector& problems) { if (!m_document) return; if (m_problems == problems) return; const bool hadProblems = !m_problems.isEmpty(); m_problems = problems; qDeleteAll(m_topHLRanges); m_topHLRanges.clear(); IndexedString url(m_document->url()); /// TODO: create a better MarkInterface that makes it possible to add the marks to the scrollbar /// but having no background. /// also make it nicer together with other plugins, this would currently fail with /// this method... const uint errorMarkType = KTextEditor::MarkInterface::Error; const uint warningMarkType = KTextEditor::MarkInterface::Warning; KTextEditor::MarkInterface* markIface = dynamic_cast(m_document.data()); if (markIface && hadProblems) { // clear previously added marks foreach (KTextEditor::Mark* mark, markIface->marks()) { if (mark->type == errorMarkType || mark->type == warningMarkType) { markIface->removeMark(mark->line, mark->type); } } } if (problems.isEmpty()) { return; } DUChainReadLocker lock; TopDUContext* top = DUChainUtils::standardContextForUrl(m_document->url()); KTextEditor::MovingInterface* iface = dynamic_cast(m_document.data()); Q_ASSERT(iface); for (const IProblem::Ptr& problem : problems) { if (problem->finalLocation().document != url || !problem->finalLocation().isValid()) continue; KTextEditor::Range range; if (top) range = top->transformFromLocalRevision(RangeInRevision::castFromSimpleRange(problem->finalLocation())); else range = problem->finalLocation(); // Fix problem's location range if necessary if (problem->finalLocationMode() != IProblem::Range && range.onSingleLine()) { int line = range.start().line(); const QString lineString = m_document->line(line); int startColumn = 0; int endColumn = lineString.length(); // If the line contains only space-characters then // we will highlight it "as is", without trimming. if (problem->finalLocationMode() == IProblem::TrimmedLine && !lineString.trimmed().isEmpty()) { while (lineString.at(startColumn++).isSpace()) {} --startColumn; while (lineString.at(--endColumn).isSpace()) {} ++endColumn; } range.setStart(Cursor(line, startColumn)); range.setEnd(Cursor(line, endColumn)); problem->setFinalLocation(DocumentRange(problem->finalLocation().document, range)); problem->setFinalLocationMode(IProblem::Range); } if (range.end().line() >= m_document->lines()) range.end() = KTextEditor::Cursor(m_document->endOfLine(m_document->lines() - 1)); if (range.isEmpty()) { range.setEnd(range.end() + KTextEditor::Cursor(0, 1)); } KTextEditor::MovingRange* problemRange = iface->newMovingRange(range); m_topHLRanges.append(problemRange); if (problem->source() != IProblem::ToDo && (problem->severity() != IProblem::Hint || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems())) { KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); attribute->setUnderlineStyle(QTextCharFormat::WaveUnderline); attribute->setUnderlineColor(colorForSeverity(problem->severity())); problemRange->setAttribute(attribute); } if (markIface && ICore::self()->languageController()->completionSettings()->highlightProblematicLines()) { uint mark; if (problem->severity() == IProblem::Error) { mark = errorMarkType; } else if (problem->severity() == IProblem::Warning) { mark = warningMarkType; } else { continue; } markIface->addMark(problem->finalLocation().start().line(), mark); } } } void ProblemHighlighter::aboutToRemoveText(const KTextEditor::Range& range) { if (range.onSingleLine()) { // no need to optimize this return; } QList::iterator it = m_topHLRanges.begin(); while (it != m_topHLRanges.end()) { if (range.contains((*it)->toRange())) { delete (*it); it = m_topHLRanges.erase(it); } else { ++it; } } } void ProblemHighlighter::clearProblems() { setProblems({}); } diff --git a/plugins/problemreporter/problemhighlighter.h b/plugins/problemreporter/problemhighlighter.h index 21f7cb3f31..ed6307be92 100644 --- a/plugins/problemreporter/problemhighlighter.h +++ b/plugins/problemreporter/problemhighlighter.h @@ -1,53 +1,55 @@ /* * KDevelop Problem Reporter * * Copyright 2008 Hamish Rodda * Copyright 2008-2009 David Nolden * * 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 KDEVPLATFORM_PLUGIN_PROBLEMHIGHLIGHTER_H #define KDEVPLATFORM_PLUGIN_PROBLEMHIGHLIGHTER_H #include -#include -#include #include +#include + +#include + class ProblemHighlighter : public QObject { Q_OBJECT public: explicit ProblemHighlighter(KTextEditor::Document* document); ~ProblemHighlighter() override; void setProblems(const QVector& problems); private Q_SLOTS: void aboutToRemoveText(const KTextEditor::Range& range); void clearProblems(); private: QPointer m_document; QList m_topHLRanges; QVector m_problems; public Q_SLOTS: void settingsChanged(); }; #endif // KDEVPLATFORM_PLUGIN_PROBLEMHIGHLIGHTER_H diff --git a/plugins/problemreporter/problemreporterplugin.cpp b/plugins/problemreporter/problemreporterplugin.cpp index 962705562b..ea9f2962df 100644 --- a/plugins/problemreporter/problemreporterplugin.cpp +++ b/plugins/problemreporter/problemreporterplugin.cpp @@ -1,249 +1,249 @@ /* * KDevelop Problem Reporter * * Copyright 2006 Adam Treat * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * 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 "problemreporterplugin.h" #include #include -#include +#include #include #include #include #include #include #include #include #include #include "problemhighlighter.h" #include "problemreportermodel.h" #include "language/assistant/staticassistantsmanager.h" #include #include #include #include #include #include #include "shell/problemmodelset.h" #include "problemsview.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevproblemreporter.json", registerPlugin();) using namespace KDevelop; class ProblemReporterFactory : public KDevelop::IToolViewFactory { public: QWidget* create(QWidget* parent = nullptr) override { Q_UNUSED(parent); ProblemsView* v = new ProblemsView(); v->load(); return v; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ProblemReporterView"); } }; ProblemReporterPlugin::ProblemReporterPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevproblemreporter"), parent) , m_factory(new ProblemReporterFactory) , m_model(new ProblemReporterModel(this)) { KDevelop::ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->addModel(QStringLiteral("Parser"), i18n("Parser"), m_model); core()->uiController()->addToolView(i18n("Problems"), m_factory); setXMLFile(QStringLiteral("kdevproblemreporter.rc")); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemReporterPlugin::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &ProblemReporterPlugin::textDocumentCreated); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemReporterPlugin::documentActivated); connect(DUChain::self(), &DUChain::updateReady, this, &ProblemReporterPlugin::updateReady); connect(ICore::self()->languageController()->staticAssistantsManager(), &StaticAssistantsManager::problemsChanged, this, &ProblemReporterPlugin::updateHighlight); connect(pms, &ProblemModelSet::showRequested, this, &ProblemReporterPlugin::showModel); connect(pms, &ProblemModelSet::problemsChanged, this, &ProblemReporterPlugin::updateOpenedDocumentsHighlight); } ProblemReporterPlugin::~ProblemReporterPlugin() { qDeleteAll(m_highlighters); } ProblemReporterModel* ProblemReporterPlugin::model() const { return m_model; } void ProblemReporterPlugin::unload() { KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); pms->removeModel(QStringLiteral("Parser")); core()->uiController()->removeToolView(m_factory); } void ProblemReporterPlugin::documentClosed(IDocument* doc) { if (!doc->textDocument()) return; IndexedString url(doc->url()); delete m_highlighters.take(url); m_reHighlightNeeded.remove(url); } void ProblemReporterPlugin::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); m_highlighters.insert(IndexedString(document->url()), new ProblemHighlighter(document->textDocument())); DUChain::self()->updateContextForUrl(IndexedString(document->url()), KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this); } void ProblemReporterPlugin::documentActivated(KDevelop::IDocument* document) { IndexedString documentUrl(document->url()); if (m_reHighlightNeeded.contains(documentUrl)) { m_reHighlightNeeded.remove(documentUrl); updateHighlight(documentUrl); } } void ProblemReporterPlugin::updateReady(const IndexedString& url, const KDevelop::ReferencedTopDUContext&) { m_model->problemsUpdated(url); updateHighlight(url); } void ProblemReporterPlugin::updateHighlight(const KDevelop::IndexedString& url) { ProblemHighlighter* ph = m_highlighters.value(url); if (!ph) return; KDevelop::ProblemModelSet* pms(core()->languageController()->problemModelSet()); QVector documentProblems; foreach (const ModelData& modelData, pms->models()) { documentProblems += modelData.model->problems({url}); } ph->setProblems(documentProblems); } void ProblemReporterPlugin::showModel(const QString& id) { auto w = dynamic_cast(core()->uiController()->findToolView(i18n("Problems"), m_factory)); if (w) w->showModel(id); } KDevelop::ContextMenuExtension ProblemReporterPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { KDevelop::ContextMenuExtension extension; KDevelop::EditorContext* editorContext = dynamic_cast(context); if (editorContext) { DUChainReadLocker lock(DUChain::lock(), 1000); if (!lock.locked()) { qCDebug(PLUGIN_PROBLEMREPORTER) << "failed to lock duchain in time"; return extension; } QString title; QList actions; TopDUContext* top = DUChainUtils::standardContextForUrl(editorContext->url()); if (top) { foreach (KDevelop::ProblemPointer problem, top->problems()) { if (problem->range().contains( top->transformToLocalRevision(KTextEditor::Cursor(editorContext->position())))) { KDevelop::IAssistant::Ptr solution = problem->solutionAssistant(); if (solution) { title = solution->title(); foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) actions << action->toQAction(parent); } } } } if (!actions.isEmpty()) { QString text; if (title.isEmpty()) text = i18n("Solve Problem"); else { text = i18n("Solve: %1", KDevelop::htmlToPlainText(title)); } QMenu* menu = new QMenu(text, parent); foreach (QAction* action, actions) menu->addAction(action); extension.addAction(ContextMenuExtension::ExtensionGroup, menu->menuAction()); } } return extension; } void ProblemReporterPlugin::updateOpenedDocumentsHighlight() { foreach(auto document, core()->documentController()->openDocuments()) { // Skip non-text documents. // This also fixes crash caused by calling updateOpenedDocumentsHighlight() method without // any opened documents. In this case documentController()->openDocuments() returns single // (non-text) document with url like file:///tmp/kdevelop_QW2530.patch which has fatal bug: // if we call isActive() method from this document the crash will happens. if (!document->isTextDocument()) continue; IndexedString documentUrl(document->url()); if (document->isActive()) updateHighlight(documentUrl); else m_reHighlightNeeded.insert(documentUrl); } } #include "problemreporterplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/projectmanagerview/vcsoverlayproxymodel.h b/plugins/projectmanagerview/vcsoverlayproxymodel.h index 025ad53660..8507424b12 100644 --- a/plugins/projectmanagerview/vcsoverlayproxymodel.h +++ b/plugins/projectmanagerview/vcsoverlayproxymodel.h @@ -1,54 +1,54 @@ /* This file is part of KDevelop Copyright 2008 Aleix Pol 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 KDEVPLATFORM_PLUGIN_VCSOVERLAYPROXYMODEL_H #define KDEVPLATFORM_PLUGIN_VCSOVERLAYPROXYMODEL_H -#include #include +#include class QUrl; namespace KDevelop { class IProject; class VcsJob; } class VcsOverlayProxyModel : public QIdentityProxyModel { Q_OBJECT public: enum Roles { VcsStatusRole = KDevelop::ProjectModel::LastRole }; explicit VcsOverlayProxyModel(QObject* parent = nullptr); QVariant data(const QModelIndex& proxyIndex, int role = Qt::DisplayRole) const override; private Q_SLOTS: void addProject(KDevelop::IProject* p); void removeProject(KDevelop::IProject* p); void repositoryBranchChanged(const QUrl& url); void branchNameReady(KDevelop::VcsJob* job); private: QModelIndex indexFromProject(QObject* project); QHash m_branchName; }; #endif // KDEVPLATFORM_PLUGIN_VCSOVERLAYPROXYMODEL_H diff --git a/plugins/qmakebuilder/qmakebuilder.cpp b/plugins/qmakebuilder/qmakebuilder.cpp index 5f1440bbfa..8292913469 100644 --- a/plugins/qmakebuilder/qmakebuilder.cpp +++ b/plugins/qmakebuilder/qmakebuilder.cpp @@ -1,177 +1,177 @@ /* KDevelop QMake 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 "qmakebuilder.h" #include "qmakebuilderpreferences.h" #include "qmakeconfig.h" #include "qmakeutils.h" #include #include #include #include #include -#include +#include #include "qmakejob.h" #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(QMakeBuilderFactory, "kdevqmakebuilder.json", registerPlugin();) QMakeBuilder::QMakeBuilder(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevqmakebuilder"), parent) { m_makeBuilder = core()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IMakeBuilder")); if (m_makeBuilder) { IMakeBuilder* mbuilder = m_makeBuilder->extension(); if (mbuilder) { connect(m_makeBuilder, SIGNAL(built(KDevelop::ProjectBaseItem*)), this, SIGNAL(built(KDevelop::ProjectBaseItem*))); connect(m_makeBuilder, SIGNAL(cleaned(KDevelop::ProjectBaseItem*)), this, SIGNAL(cleaned(KDevelop::ProjectBaseItem*))); connect(m_makeBuilder, SIGNAL(installed(KDevelop::ProjectBaseItem*)), this, SIGNAL(installed(KDevelop::ProjectBaseItem*))); connect(m_makeBuilder, SIGNAL(failed(KDevelop::ProjectBaseItem*)), this, SIGNAL(failed(KDevelop::ProjectBaseItem*))); connect(m_makeBuilder, SIGNAL(makeTargetBuilt(KDevelop::ProjectBaseItem*,QString)), this, SIGNAL(pruned(KDevelop::ProjectBaseItem*))); } } } QMakeBuilder::~QMakeBuilder() { } KJob* QMakeBuilder::prune(KDevelop::IProject* project) { qCDebug(KDEV_QMAKEBUILDER) << "Distcleaning"; if (m_makeBuilder) { IMakeBuilder* builder = m_makeBuilder->extension(); if (builder) { qCDebug(KDEV_QMAKEBUILDER) << "Distcleaning with make"; return builder->executeMakeTarget(project->projectItem(), QStringLiteral("distclean")); } } return nullptr; } KJob* QMakeBuilder::build(KDevelop::ProjectBaseItem* dom) { qCDebug(KDEV_QMAKEBUILDER) << "Building"; if (m_makeBuilder) { IMakeBuilder* builder = m_makeBuilder->extension(); if (builder) { qCDebug(KDEV_QMAKEBUILDER) << "Building with make"; return maybePrependConfigureJob(dom, builder->build(dom), BuilderJob::Build); } } return nullptr; } KJob* QMakeBuilder::configure(KDevelop::IProject* project) { auto job = new QMakeJob(this); job->setProject(project); return job; } KJob* QMakeBuilder::clean(KDevelop::ProjectBaseItem* dom) { qCDebug(KDEV_QMAKEBUILDER) << "Cleaning"; if (m_makeBuilder) { IMakeBuilder* builder = m_makeBuilder->extension(); if (builder) { qCDebug(KDEV_QMAKEBUILDER) << "Cleaning with make"; return maybePrependConfigureJob(dom, builder->clean(dom), BuilderJob::Clean); } } return nullptr; } KJob* QMakeBuilder::install(KDevelop::ProjectBaseItem* dom, const QUrl& /* prefix */) { qCDebug(KDEV_QMAKEBUILDER) << "Installing"; if (m_makeBuilder) { IMakeBuilder* builder = m_makeBuilder->extension(); if (builder) { qCDebug(KDEV_QMAKEBUILDER) << "Installing with make"; return maybePrependConfigureJob(dom, builder->install(dom), BuilderJob::Install); } } return nullptr; } int QMakeBuilder::perProjectConfigPages() const { return 1; } KDevelop::ConfigPage* QMakeBuilder::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) { switch (number) { case 0: return new QMakeBuilderPreferences(this, options, parent); default: return nullptr; } } QList QMakeBuilder::additionalBuilderPlugins(KDevelop::IProject* project) const { Q_UNUSED(project); if (IMakeBuilder* makeBuilder = m_makeBuilder->extension()) { return QList() << makeBuilder; } return QList(); } KJob* QMakeBuilder::maybePrependConfigureJob(ProjectBaseItem* dom, KJob* job, BuilderJob::BuildType type) { Q_ASSERT(dom); if (!job) { qCDebug(KDEV_QMAKEBUILDER) << "Null job passed"; return nullptr; } const bool needsConfigure = QMakeUtils::checkForNeedingConfigure(dom->project()); if (needsConfigure) { qCDebug(KDEV_QMAKEBUILDER) << "Project" << dom->project()->name() << "needs configure"; auto builderJob = new BuilderJob; builderJob->addCustomJob(BuilderJob::Configure, configure(dom->project()), dom); builderJob->addCustomJob(type, job, dom); builderJob->updateJobName(); return builderJob; } return job; } #include "qmakebuilder.moc" diff --git a/plugins/qmakebuilder/qmakebuilderpreferences.cpp b/plugins/qmakebuilder/qmakebuilderpreferences.cpp index 5f42dc24bf..b4804924d9 100644 --- a/plugins/qmakebuilder/qmakebuilderpreferences.cpp +++ b/plugins/qmakebuilder/qmakebuilderpreferences.cpp @@ -1,162 +1,162 @@ /* KDevelop QMake Support * * Copyright 2007 Andreas Pakulat * 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) 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 "qmakebuilderpreferences.h" #include -#include -#include +#include +#include #include #include #include "ui_qmakeconfig.h" #include "qmakebuilddirchooser.h" #include "qmakebuilddirchooserdialog.h" #include "qmakeconfig.h" #include "qmakebuilderconfig.h" #include #include QMakeBuilderPreferences::QMakeBuilderPreferences(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent) : KDevelop::ConfigPage(plugin, nullptr, parent) , m_project(options.project) { m_prefsUi = new Ui::QMakeConfig; m_prefsUi->setupUi(this); m_chooserUi = new QMakeBuildDirChooser(m_project); auto groupBoxLayout = new QVBoxLayout(m_prefsUi->groupBox); groupBoxLayout->addWidget(m_chooserUi); m_chooserUi->kcfg_buildDir->setEnabled(false); // build directory MUST NOT be changed here connect(m_chooserUi, &QMakeBuildDirChooser::changed, this, &QMakeBuilderPreferences::changed); connect(m_chooserUi, &QMakeBuildDirChooser::changed, this, &QMakeBuilderPreferences::validate); connect(m_prefsUi->buildDirCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(loadOtherConfig(QString))); connect(m_prefsUi->buildDirCombo, static_cast(&QComboBox::currentIndexChanged), this, &QMakeBuilderPreferences::changed); connect(m_prefsUi->addButton, &QAbstractButton::pressed, this, &QMakeBuilderPreferences::addBuildConfig); connect(m_prefsUi->removeButton, &QAbstractButton::pressed, this, &QMakeBuilderPreferences::removeBuildConfig); reset(); // load initial values } QMakeBuilderPreferences::~QMakeBuilderPreferences() { // not a QObject ! delete m_chooserUi; } void QMakeBuilderPreferences::reset() { qCDebug(KDEV_QMAKEBUILDER) << "loading data"; // refresh combobox KConfigGroup cg(m_project->projectConfiguration(), QMakeConfig::CONFIG_GROUP); const QString buildPath = cg.readEntry(QMakeConfig::BUILD_FOLDER, QString()); // update build list (this will trigger loadOtherConfig if signals are still connected) disconnect(m_prefsUi->buildDirCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(loadOtherConfig(QString))); m_prefsUi->buildDirCombo->clear(); m_prefsUi->buildDirCombo->insertItems(0, cg.groupList()); if (m_prefsUi->buildDirCombo->contains(buildPath)) { m_prefsUi->buildDirCombo->setCurrentItem(buildPath); m_chooserUi->loadConfig(buildPath); } qCDebug(KDEV_QMAKEBUILDER) << "Loaded" << cg.groupList() << buildPath; m_prefsUi->removeButton->setEnabled(m_prefsUi->buildDirCombo->count() > 1); connect(m_prefsUi->buildDirCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(loadOtherConfig(QString))); validate(); } QString QMakeBuilderPreferences::name() const { return i18n("QMake"); } void QMakeBuilderPreferences::apply() { qCDebug(KDEV_QMAKEBUILDER) << "Saving data"; QString errormsg; if (m_chooserUi->validate(&errormsg)) { // data is valid: save, once in the build dir's data and also as current data m_chooserUi->saveConfig(); KConfigGroup config(m_project->projectConfiguration(), QMakeConfig::CONFIG_GROUP); m_chooserUi->saveConfig(config); config.writeEntry(QMakeConfig::BUILD_FOLDER, m_chooserUi->buildDir()); } else { // invalid data: message box KMessageBox::error(nullptr, errormsg, QStringLiteral("Data is invalid!")); // FIXME dialog behaves like if save really happened (dialog closes if user click ok) even if changed signal is // emitted } } void QMakeBuilderPreferences::validate() { m_chooserUi->validate(); } void QMakeBuilderPreferences::loadOtherConfig(const QString& config) { qCDebug(KDEV_QMAKEBUILDER) << "Loading config " << config; m_chooserUi->loadConfig(config); apply(); // since current config has changed, it must be saved immediateley } void QMakeBuilderPreferences::addBuildConfig() { qCDebug(KDEV_QMAKEBUILDER) << "Adding a new config."; // for more simpicity, just launch regular dialog auto dlg = new QMakeBuildDirChooserDialog(m_project); if (dlg->exec() == QDialog::Accepted) { m_prefsUi->buildDirCombo->setCurrentItem(dlg->buildDir(), true); m_prefsUi->removeButton->setEnabled(m_prefsUi->buildDirCombo->count() > 1); // TODO run qmake } } void QMakeBuilderPreferences::removeBuildConfig() { qCDebug(KDEV_QMAKEBUILDER) << "Removing config" << m_prefsUi->buildDirCombo->currentText(); QString removed = m_prefsUi->buildDirCombo->currentText(); KConfigGroup cg(m_project->projectConfiguration(), QMakeConfig::CONFIG_GROUP); m_prefsUi->buildDirCombo->removeItem(m_prefsUi->buildDirCombo->currentIndex()); m_prefsUi->removeButton->setEnabled(m_prefsUi->buildDirCombo->count() > 1); cg.group(removed).deleteGroup(KConfigBase::Persistent); if (QDir(removed).exists()) { int 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) { auto deleteJob = KIO::del(QUrl::fromLocalFile(removed)); KJobWidgets::setWindow(deleteJob, this); if (!deleteJob->exec()) KMessageBox::error(this, i18n("Could not remove: %1.", removed)); } } } diff --git a/plugins/qmakemanager/qmakemanager.cpp b/plugins/qmakemanager/qmakemanager.cpp index 7104291528..9f73463c86 100644 --- a/plugins/qmakemanager/qmakemanager.cpp +++ b/plugins/qmakemanager/qmakemanager.cpp @@ -1,514 +1,514 @@ /* KDevelop QMake Support * * Copyright 2006 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 "qmakemanager.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 "qmakemodelitems.h" #include "qmakeprojectfile.h" #include "qmakecache.h" #include "qmakemkspecs.h" #include "qmakejob.h" #include "qmakebuilddirchooserdialog.h" #include "qmakeconfig.h" #include "qmakeutils.h" #include using namespace KDevelop; // BEGIN Helpers QMakeFolderItem* findQMakeFolderParent(ProjectBaseItem* item) { QMakeFolderItem* p = nullptr; while (!p && item) { p = dynamic_cast(item); item = item->parent(); } return p; } // END Helpers K_PLUGIN_FACTORY_WITH_JSON(QMakeSupportFactory, "kdevqmakemanager.json", registerPlugin();) QMakeProjectManager::QMakeProjectManager(QObject* parent, const QVariantList&) : AbstractFileManagerPlugin(QStringLiteral("kdevqmakemanager"), parent) , IBuildSystemManager() { IPlugin* i = core()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IQMakeBuilder")); Q_ASSERT(i); m_builder = i->extension(); Q_ASSERT(m_builder); connect(this, SIGNAL(folderAdded(KDevelop::ProjectFolderItem*)), this, SLOT(slotFolderAdded(KDevelop::ProjectFolderItem*))); m_runQMake = new QAction(QIcon::fromTheme(QStringLiteral("qtlogo")), i18n("Run QMake"), this); connect(m_runQMake, &QAction::triggered, this, &QMakeProjectManager::slotRunQMake); } QMakeProjectManager::~QMakeProjectManager() { } IProjectFileManager::Features QMakeProjectManager::features() const { return Features(Folders | Targets | Files); } bool QMakeProjectManager::isValid(const Path& path, const bool isFolder, IProject* project) const { if (!isFolder && path.lastPathSegment().startsWith(QLatin1String("Makefile"))) { return false; } return AbstractFileManagerPlugin::isValid(path, isFolder, project); } Path QMakeProjectManager::buildDirectory(ProjectBaseItem* item) const { /// TODO: support includes by some other parent or sibling in a different file-tree-branch QMakeFolderItem* qmakeItem = findQMakeFolderParent(item); Path dir; if (qmakeItem) { if (!qmakeItem->parent()) { // build root item dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), qmakeItem->path()); } else { // build sub-item foreach (QMakeProjectFile* pro, qmakeItem->projectFiles()) { if (QDir(pro->absoluteDir()) == QFileInfo(qmakeItem->path().toUrl().toLocalFile() + '/').absoluteDir() || pro->hasSubProject(qmakeItem->path().toUrl().toLocalFile())) { // get path from project root and it to buildDir dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), Path(pro->absoluteDir())); break; } } } } qCDebug(KDEV_QMAKE) << "build dir for" << item->text() << item->path() << "is:" << dir; return dir; } ProjectFolderItem* QMakeProjectManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { if (!parent) { return projectRootItem(project, path); } else if (ProjectFolderItem* buildFolder = buildFolderItem(project, path, parent)) { // child folder in a qmake folder return buildFolder; } else { return AbstractFileManagerPlugin::createFolderItem(project, path, parent); } } ProjectFolderItem* QMakeProjectManager::projectRootItem(IProject* project, const Path& path) { QFileInfo fi(path.toLocalFile()); QDir dir(path.toLocalFile()); auto item = new QMakeFolderItem(project, path); QHash qmvars = QMakeUtils::queryQMake(project); const QString mkSpecFile = QMakeConfig::findBasicMkSpec(qmvars); Q_ASSERT(!mkSpecFile.isEmpty()); QMakeMkSpecs* mkspecs = new QMakeMkSpecs(mkSpecFile, qmvars); mkspecs->setProject(project); mkspecs->read(); QMakeCache* cache = findQMakeCache(project); if (cache) { cache->setMkSpecs(mkspecs); cache->read(); } QStringList projectfiles = dir.entryList(QStringList() << QStringLiteral("*.pro")); for (const auto& projectfile : projectfiles) { Path proPath(path, projectfile); /// TODO: use Path in QMakeProjectFile QMakeProjectFile* scope = new QMakeProjectFile(proPath.toLocalFile()); scope->setProject(project); scope->setMkSpecs(mkspecs); if (cache) { scope->setQMakeCache(cache); } scope->read(); qCDebug(KDEV_QMAKE) << "top-level scope with variables:" << scope->variables(); item->addProjectFile(scope); } return item; } ProjectFolderItem* QMakeProjectManager::buildFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // find .pro or .pri files in dir QDir dir(path.toLocalFile()); const QStringList projectFiles = dir.entryList(QStringList{QStringLiteral("*.pro"), QStringLiteral("*.pri")}, QDir::Files); if (projectFiles.isEmpty()) { return nullptr; } auto folderItem = new QMakeFolderItem(project, path, parent); // TODO: included by not-parent file (in a nother file-tree-branch). QMakeFolderItem* qmakeParent = findQMakeFolderParent(parent); if (!qmakeParent) { // happens for bad qmake configurations return nullptr; } for (const QString& file : projectFiles) { const QString absFile = dir.absoluteFilePath(file); // TODO: multiple includes by different .pro's QMakeProjectFile* parentPro = nullptr; foreach (QMakeProjectFile* p, qmakeParent->projectFiles()) { if (p->hasSubProject(absFile)) { parentPro = p; break; } } if (!parentPro && file.endsWith(QLatin1String(".pri"))) { continue; } qCDebug(KDEV_QMAKE) << "add project file:" << absFile; if (parentPro) { qCDebug(KDEV_QMAKE) << "parent:" << parentPro->absoluteFile(); } else { qCDebug(KDEV_QMAKE) << "no parent, assume project root"; } auto qmscope = new QMakeProjectFile(absFile); qmscope->setProject(project); const QFileInfo info(absFile); const QDir d = info.dir(); /// TODO: cleanup if (parentPro) { // subdir if (QMakeCache* cache = findQMakeCache(project, Path(d.canonicalPath()))) { cache->setMkSpecs(parentPro->mkSpecs()); cache->read(); qmscope->setQMakeCache(cache); } else { qmscope->setQMakeCache(parentPro->qmakeCache()); } qmscope->setMkSpecs(parentPro->mkSpecs()); } else { // new project QMakeFolderItem* root = dynamic_cast(project->projectItem()); Q_ASSERT(root); qmscope->setMkSpecs(root->projectFiles().first()->mkSpecs()); if (root->projectFiles().first()->qmakeCache()) { qmscope->setQMakeCache(root->projectFiles().first()->qmakeCache()); } } if (qmscope->read()) { // TODO: only on read? folderItem->addProjectFile(qmscope); } else { delete qmscope; return nullptr; } } return folderItem; } void QMakeProjectManager::slotFolderAdded(ProjectFolderItem* folder) { QMakeFolderItem* qmakeParent = dynamic_cast(folder); if (!qmakeParent) { return; } qCDebug(KDEV_QMAKE) << "adding targets for" << folder->path(); foreach (QMakeProjectFile* pro, qmakeParent->projectFiles()) { foreach (const QString& s, pro->targets()) { if (!isValid(Path(folder->path(), s), false, folder->project())) { continue; } qCDebug(KDEV_QMAKE) << "adding target:" << s; Q_ASSERT(!s.isEmpty()); auto target = new QMakeTargetItem(pro, folder->project(), s, folder); foreach (const QString& path, pro->filesForTarget(s)) { new ProjectFileItem(folder->project(), Path(path), target); /// TODO: signal? } } } } ProjectFolderItem* QMakeProjectManager::import(IProject* project) { const Path dirName = project->path(); if (dirName.isRemote()) { // FIXME turn this into a real warning qCWarning(KDEV_QMAKE) << "not a local file. QMake support doesn't handle remote projects"; return nullptr; } QMakeUtils::checkForNeedingConfigure(project); ProjectFolderItem* ret = AbstractFileManagerPlugin::import(project); connect(projectWatcher(project), &KDirWatch::dirty, this, &QMakeProjectManager::slotDirty); return ret; } void QMakeProjectManager::slotDirty(const QString& path) { if (!path.endsWith(QLatin1String(".pro")) && !path.endsWith(QLatin1String(".pri"))) { return; } QFileInfo info(path); if (!info.isFile()) { return; } const QUrl url = QUrl::fromLocalFile(path); if (!isValid(Path(url), false, nullptr)) { return; } IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if (!project) { // this can happen when we create/remove lots of files in a // sub dir of a project - ignore such cases for now return; } bool finished = false; foreach (ProjectFolderItem* folder, project->foldersForPath(IndexedString(KIO::upUrl(url)))) { if (QMakeFolderItem* qmakeFolder = dynamic_cast(folder)) { foreach (QMakeProjectFile* pro, qmakeFolder->projectFiles()) { if (pro->absoluteFile() == path) { // TODO: children // TODO: cache added qCDebug(KDEV_QMAKE) << "reloading" << pro << path; pro->read(); } } finished = true; } else if (ProjectFolderItem* newFolder = buildFolderItem(project, folder->path(), folder->parent())) { qCDebug(KDEV_QMAKE) << "changing from normal folder to qmake project folder:" << folder->path().toUrl(); // .pro / .pri file did not exist before while (folder->rowCount()) { newFolder->appendRow(folder->takeRow(0)); } folder->parent()->removeRow(folder->row()); folder = newFolder; finished = true; } if (finished) { // remove existing targets and readd them for (int i = 0; i < folder->rowCount(); ++i) { if (folder->child(i)->target()) { folder->removeRow(i); } } /// TODO: put into it's own function once we add more stuff to that slot slotFolderAdded(folder); break; } } } QList QMakeProjectManager::targets(ProjectFolderItem* item) const { Q_UNUSED(item) return QList(); } IProjectBuilder* QMakeProjectManager::builder() const { Q_ASSERT(m_builder); return m_builder; } Path::List QMakeProjectManager::collectDirectories(ProjectBaseItem* item, const bool collectIncludes) const { Path::List list; QMakeFolderItem* folder = findQMakeFolderParent(item); if (folder) { foreach (QMakeProjectFile* pro, folder->projectFiles()) { if (pro->files().contains(item->path().toLocalFile())) { const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); for (const QString& dir : directories) { Path path(dir); if (!list.contains(path)) { list << path; } } } } if (list.isEmpty()) { // fallback for new files, use all possible include dirs foreach (QMakeProjectFile* pro, folder->projectFiles()) { const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); for (const QString& dir : directories) { Path path(dir); if (!list.contains(path)) { list << path; } } } } // make sure the base dir is included if (!list.contains(folder->path())) { list << folder->path(); } // qCDebug(KDEV_QMAKE) << "include dirs for" << item->path() << ":" << list; } return list; } Path::List QMakeProjectManager::includeDirectories(ProjectBaseItem* item) const { return collectDirectories(item); } Path::List QMakeProjectManager::frameworkDirectories(ProjectBaseItem* item) const { return collectDirectories(item, false); } QHash QMakeProjectManager::defines(ProjectBaseItem* item) const { QHash d; QMakeFolderItem* folder = findQMakeFolderParent(item); if (!folder) { // happens for bad qmake configurations return d; } foreach (QMakeProjectFile* pro, folder->projectFiles()) { foreach (QMakeProjectFile::DefinePair def, pro->defines()) { d.insert(def.first, def.second); } } return d; } QString QMakeProjectManager::extraArguments(KDevelop::ProjectBaseItem *item) const { QMakeFolderItem* folder = findQMakeFolderParent(item); if (!folder) { // happens for bad qmake configurations return {}; } QStringList d; foreach (QMakeProjectFile* pro, folder->projectFiles()) { d << pro->extraArguments(); } return d.join(QLatin1Char(' ')); } bool QMakeProjectManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const { return findQMakeFolderParent(item); } QMakeCache* QMakeProjectManager::findQMakeCache(IProject* project, const Path& path) const { QDir curdir(QMakeConfig::buildDirFromSrc(project, !path.isValid() ? project->path() : path).toLocalFile()); curdir.makeAbsolute(); while (!curdir.exists(QStringLiteral(".qmake.cache")) && !curdir.isRoot() && curdir.cdUp()) { qCDebug(KDEV_QMAKE) << curdir; } if (curdir.exists(QStringLiteral(".qmake.cache"))) { qCDebug(KDEV_QMAKE) << "Found QMake cache in " << curdir.absolutePath(); return new QMakeCache(curdir.canonicalPath() + "/.qmake.cache"); } return nullptr; } ContextMenuExtension QMakeProjectManager::contextMenuExtension(Context* context, QWidget* parent) { Q_UNUSED(parent); ContextMenuExtension ext; if (context->hasType(Context::ProjectItemContext)) { ProjectItemContext* pic = dynamic_cast(context); Q_ASSERT(pic); if (pic->items().isEmpty()) { return ext; } m_actionItem = dynamic_cast(pic->items().first()); if (m_actionItem) { ext.addAction(ContextMenuExtension::ProjectGroup, m_runQMake); } } return ext; } void QMakeProjectManager::slotRunQMake() { Q_ASSERT(m_actionItem); Path srcDir = m_actionItem->path(); Path buildDir = QMakeConfig::buildDirFromSrc(m_actionItem->project(), srcDir); QMakeJob* job = new QMakeJob(srcDir.toLocalFile(), buildDir.toLocalFile(), this); job->setQMakePath(QMakeConfig::qmakeExecutable(m_actionItem->project())); KConfigGroup cg(m_actionItem->project()->projectConfiguration(), QMakeConfig::CONFIG_GROUP); QString installPrefix = cg.readEntry(QMakeConfig::INSTALL_PREFIX, QString()); if (!installPrefix.isEmpty()) job->setInstallPrefix(installPrefix); job->setBuildType(cg.readEntry(QMakeConfig::BUILD_TYPE, 0)); job->setExtraArguments(cg.readEntry(QMakeConfig::EXTRA_ARGUMENTS, QString())); KDevelop::ICore::self()->runController()->registerJob(job); } #include "qmakemanager.moc" diff --git a/plugins/qmljs/codecompletion/items/completionitem.cpp b/plugins/qmljs/codecompletion/items/completionitem.cpp index bde81b3ac6..3a49094853 100644 --- a/plugins/qmljs/codecompletion/items/completionitem.cpp +++ b/plugins/qmljs/codecompletion/items/completionitem.cpp @@ -1,231 +1,231 @@ /* * This file is part of qmljs, the QML/JS language support plugin for KDevelop * Copyright (c) 2014 Denis Steckelmacher * * 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 "completionitem.h" #include "context.h" #include #include #include #include #include #include #include #include -#include -#include - #include "../../duchain/functiontype.h" +#include +#include + using namespace QmlJS; using namespace KDevelop; CompletionItem::CompletionItem(const DeclarationPointer& decl, int inheritanceDepth, Decoration decoration) : NormalDeclarationCompletionItem(decl, QExplicitlySharedDataPointer(), inheritanceDepth), m_decoration(decoration) { } QVariant CompletionItem::data(const QModelIndex& index, int role, const CodeCompletionModel* model) const { DUChainReadLocker lock; Declaration* decl = declaration().data(); if (!decl) { return QVariant(); } ClassDeclaration* classDecl = dynamic_cast(decl); StructureType::Ptr declType = StructureType::Ptr::dynamicCast(decl->abstractType()); auto funcType = QmlJS::FunctionType::Ptr::dynamicCast(decl->abstractType()); if (role == CodeCompletionModel::BestMatchesCount) { return 5; } else if (role == CodeCompletionModel::MatchQuality) { AbstractType::Ptr referenceType = static_cast(model->completionContext().data())->typeToMatch(); if (!referenceType) { return QVariant(); } AbstractType::Ptr declType = decl->abstractType(); if (!declType) { return QVariant(); } QmlJS::FunctionType::Ptr declFunc = QmlJS::FunctionType::Ptr::dynamicCast(declType); if (declType->equals(referenceType.constData())) { // Perfect type match return QVariant(10); } else if (declFunc && declFunc->returnType() && declFunc->returnType()->equals(referenceType.constData())) { // Also very nice: a function returning the proper type return QVariant(9); } else { // Completely different types, no luck return QVariant(); } } else if (role == Qt::DisplayRole && funcType) { // Functions are displayed using the "type funcName(arg, arg, arg...)" format Declaration* funcDecl = funcType->declaration(decl->topContext()); if (funcDecl) { switch (index.column()) { case CodeCompletionModel::Prefix: return funcType->returnType()->toString(); case CodeCompletionModel::Name: // Return the identifier of the declaration being listed, not of its // function declaration (because the function may have been declared // anonymously, even if it has been assigned to a variable) return decl->identifier().toString(); case CodeCompletionModel::Arguments: { QStringList args; const auto localDeclarations = funcDecl->internalContext()->localDeclarations(); args.reserve(localDeclarations.size()); for (auto* arg : localDeclarations) { args.append(arg->toString()); } return QStringLiteral("(%1)").arg(args.join(QStringLiteral(", "))); } } } } else if (role == Qt::DisplayRole && index.column() == CodeCompletionModel::Prefix) { if (classDecl) { if (classDecl->classType() == ClassDeclarationData::Class) { // QML component return QStringLiteral("component"); } else if (classDecl->classType() == ClassDeclarationData::Interface) { // C++-ish QML component return QStringLiteral("wrapper"); } } if (decl && ( decl->kind() == Declaration::NamespaceAlias || decl->kind() == Declaration::Namespace )) { // Display namespaces and namespace aliases as modules return QStringLiteral("module"); } if (decl && decl->abstractType() && decl->kind() == Declaration::Type && decl->abstractType()->whichType() == AbstractType::TypeEnumeration) { // Enum return QStringLiteral("enum"); } if (declType && decl->kind() == Declaration::Instance && declType->declarationId().qualifiedIdentifier().isEmpty()) { // QML component instance. The type that should be displayed is the // base class of its anonymous class ClassDeclaration* anonymousClass = dynamic_cast(declType->declaration(decl->topContext())); if (anonymousClass && anonymousClass->baseClassesSize() > 0) { return anonymousClass->baseClasses()[0].baseClass.abstractType()->toString(); } } } return NormalDeclarationCompletionItem::data(index, role, model); } QString CompletionItem::declarationName() const { ClassFunctionDeclaration* classFuncDecl = dynamic_cast(declaration().data()); if (classFuncDecl && classFuncDecl->isSignal() && m_decoration == QmlJS::CompletionItem::ColonOrBracket) { // Signals, when completed in a QML component context, are transformed into slots QString signal = classFuncDecl->identifier().toString(); if (signal.size() > 0) { return QLatin1String("on") + signal.at(0).toUpper() + signal.midRef(1); } } return NormalDeclarationCompletionItem::declarationName(); } CodeCompletionModel::CompletionProperties CompletionItem::completionProperties() const { DUChainReadLocker lock; // Variables having a function type should have a function icon. FunctionDeclarations // are skipped here because they are already handled properly by completionProperties() if (declaration() && declaration()->abstractType() && !declaration()->isFunctionDeclaration() && declaration()->abstractType()->whichType() == AbstractType::TypeFunction) { return CodeCompletionModel::Function; } // Put declarations in a context owned by a namespace in the namespace scope auto properties = NormalDeclarationCompletionItem::completionProperties(); if (declaration() && declaration()->context() && declaration()->context()->owner() && ( declaration()->context()->owner()->kind() == Declaration::Namespace || declaration()->context()->type() == DUContext::Enum )) { properties &= ~(CodeCompletionModel::LocalScope | CodeCompletionModel::GlobalScope | CodeCompletionModel::Public); properties |= CodeCompletionModel::NamespaceScope; } return properties; } void CompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { KTextEditor::Document* document = view->document(); QString base = declarationName(); switch (m_decoration) { case QmlJS::CompletionItem::NoDecoration: document->replaceText(word, base); break; case QmlJS::CompletionItem::Quotes: document->replaceText(word, '\"' + base + '\"'); break; case QmlJS::CompletionItem::QuotesAndBracket: document->replaceText(word, '\"' + base + "\"]"); break; case QmlJS::CompletionItem::ColonOrBracket: if (declaration() && declaration()->abstractType() && declaration()->abstractType()->whichType() == AbstractType::TypeStructure) { document->replaceText(word, base + " {}"); } else { document->replaceText(word, base + ": "); } break; case QmlJS::CompletionItem::Brackets: document->replaceText(word, base + "()"); } } diff --git a/plugins/qmljs/codecompletion/items/modulecompletionitem.cpp b/plugins/qmljs/codecompletion/items/modulecompletionitem.cpp index 2bfa558c2a..bd82ae3f9c 100644 --- a/plugins/qmljs/codecompletion/items/modulecompletionitem.cpp +++ b/plugins/qmljs/codecompletion/items/modulecompletionitem.cpp @@ -1,99 +1,100 @@ /* * This file is part of qmljs, the QML/JS language support plugin for KDevelop * Copyright (c) 2014 Denis Steckelmacher * * 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 "modulecompletionitem.h" #include #include -#include -#include + +#include +#include #include using namespace KDevelop; QmlJS::ModuleCompletionItem::ModuleCompletionItem(const QString& name, Decoration decoration) : m_name(name), m_decoration(decoration) { } int QmlJS::ModuleCompletionItem::argumentHintDepth() const { return 0; } int QmlJS::ModuleCompletionItem::inheritanceDepth() const { return m_name.count(QLatin1Char('.')); } CodeCompletionModel::CompletionProperties QmlJS::ModuleCompletionItem::completionProperties() const { return CodeCompletionModel::Namespace; } QVariant QmlJS::ModuleCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { Q_UNUSED(model) switch (role) { case CodeCompletionModel::IsExpandable: return QVariant(false); case Qt::DisplayRole: switch (index.column()) { case CodeCompletionModel::Prefix: return QLatin1String("module"); case CodeCompletionModel::Name: return m_name; } break; case CodeCompletionModel::CompletionRole: return (int)completionProperties(); case Qt::DecorationRole: if(index.column() == CodeCompletionModel::Icon) { CodeCompletionModel::CompletionProperties p = completionProperties(); return DUChainUtils::iconForProperties(p); } break; } return QVariant(); } void QmlJS::ModuleCompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { switch (m_decoration) { case Import: // Replace the whole line with an import statement view->document()->replaceText( KTextEditor::Range(word.start().line(), 0, word.start().line(), INT_MAX), QStringLiteral("import %1").arg(m_name) ); break; case Quotes: view->document()->replaceText(word, QStringLiteral("\"%1\"").arg(m_name)); break; } } diff --git a/plugins/qmljs/navigation/propertypreviewwidget.h b/plugins/qmljs/navigation/propertypreviewwidget.h index 6ee5bfb7e2..3ecaa71449 100644 --- a/plugins/qmljs/navigation/propertypreviewwidget.h +++ b/plugins/qmljs/navigation/propertypreviewwidget.h @@ -1,105 +1,106 @@ /************************************************************************************* * Copyright (C) 2013 by Sven Brauch * * * * 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 PROPERTYPREVIEWWIDGET_H #define PROPERTYPREVIEWWIDGET_H #include -#include #include #include +#include + using namespace KDevelop; class QQuickWidget; // Describes one supported property, such as "width" struct SupportedProperty { explicit SupportedProperty(const QUrl& qmlfile, const QString &typeContains = QString(), const QString &classContains = QString()) : qmlfile(qmlfile), typeContains(typeContains), classContains(classContains) { } // the absolute (!) URL to the qml file to load when creating a widget // for this property QUrl qmlfile; // A string that must be contained into the string representation of the // type of the key being matched. QString typeContains; // A string that must be contained into the name of the class in which the // key is declared QString classContains; }; Q_DECLARE_TYPEINFO(SupportedProperty, Q_MOVABLE_TYPE); // This class is responsible for creating the property widgets for editing QML properties // with e.g. sliders. It knows which properties are supported, and creates a widget from // a QML file for each supported property when requested. For the actual implementations // of the widgets, see the propertywidgets/ subfolder, especially the README file. class PropertyPreviewWidget : public QWidget { Q_OBJECT public: // Constructs a widget operating on the given document if the given key is in the list // of supported properties. // key and value must be the key and the current value of the property in question, // without spaces or other extra characters. // The ranges must encompass the text which should be replaced by the new values // selected by the user. // Returns 0 when the property is not supported, which tells kdevplatform not to // display any widget when returned from e.g. specialLanguageObjectNavigationWidget. static QWidget* constructIfPossible(KTextEditor::Document* doc, const KTextEditor::Range& keyRange, const KTextEditor::Range& valueRange, Declaration* decl, const QString& key, const QString& value); ~PropertyPreviewWidget() override; private: // private because you should use the static constructIfPossible function to create instances, // to make sure you don't have widgets which operate on unsupported properties. explicit PropertyPreviewWidget(KTextEditor::Document* doc, const KTextEditor::Range& keyRange, const KTextEditor::Range& valueRange, const SupportedProperty& property, const QString& value); static QHash supportedProperties; QQuickWidget* view; // the document the widget replaces text in KTextEditor::Document* document; // the range of the key KTextEditor::Range const keyRange; // the range of the value to be modified. Not const because the range might change // if the newly inserted text is smaller or larger than what was there before // (e.g. 9 -> 10) KTextEditor::Range valueRange; // the SupportedProperty instance for this widget SupportedProperty const property; private Q_SLOTS: // updates the text in the document to contain the new value in valueRange void updateValue(); }; #endif diff --git a/plugins/qthelp/qthelpplugin.cpp b/plugins/qthelp/qthelpplugin.cpp index 259ee9891d..7851f7494e 100644 --- a/plugins/qthelp/qthelpplugin.cpp +++ b/plugins/qthelp/qthelpplugin.cpp @@ -1,181 +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 "qthelpprovider.h" #include "qthelpqtdoc.h" #include "qthelp_config_shared.h" #include "debug.h" #include "qthelpconfig.h" +#include + +#include + QtHelpPlugin *QtHelpPlugin::s_plugin = nullptr; K_PLUGIN_FACTORY_WITH_JSON(QtHelpPluginFactory, "kdevqthelp.json", registerPlugin(); ) QtHelpPlugin::QtHelpPlugin(QObject* parent, const QVariantList& args) : KDevelop::IPlugin(QStringLiteral("kdevqthelp"), parent) , m_qtHelpProviders() , m_qtDoc(new QtHelpQtDoc(this, QVariantList())) , m_loadSystemQtDoc(false) { 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() << QStringLiteral("*.qch"), QDir::Files, QDirIterator::Subdirectories); const QString logo(QStringLiteral("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(const QStringList& pathList, const QStringList& nameList, const 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 = 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; list.reserve(m_qtHelpProviders.size() + (m_loadSystemQtDoc?1:0)); 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/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp index f1c1dd13c2..db9065adc8 100644 --- a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp +++ b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp @@ -1,597 +1,597 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 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 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 "expandingwidgetmodel.h" #include #include #include #include #include -#include -#include -#include +#include +#include +#include #include "expandingdelegate.h" #include QIcon ExpandingWidgetModel::m_expandedIcon; QIcon ExpandingWidgetModel::m_collapsedIcon; using namespace KTextEditor; inline QModelIndex firstColumn(const QModelIndex& index) { return index.sibling(index.row(), 0); } ExpandingWidgetModel::ExpandingWidgetModel(QWidget* parent) : QAbstractTableModel(parent) { } ExpandingWidgetModel::~ExpandingWidgetModel() { clearExpanding(); } static QColor doAlternate(const QColor& color) { QColor background = QApplication::palette().background().color(); return KColorUtils::mix(color, background, 0.15); } uint ExpandingWidgetModel::matchColor(const QModelIndex& index) const { int matchQuality = contextMatchQuality(index.sibling(index.row(), 0)); if (matchQuality > 0) { bool alternate = index.row() & 1; QColor badMatchColor(0xff00aa44); //Blue-ish green QColor goodMatchColor(0xff00ff00); //Green QColor background = treeView()->palette().light().color(); QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, (( float )matchQuality) / 10.0); if (alternate) { totalColor = doAlternate(totalColor); } const float dynamicTint = 0.2f; const float minimumTint = 0.2f; double tintStrength = (dynamicTint * matchQuality) / 10; if (tintStrength) { tintStrength += minimumTint; //Some minimum tinting strength, else it's not visible any more } return KColorUtils::tint(background, totalColor, tintStrength).rgb(); } else { return 0; } } QVariant ExpandingWidgetModel::data(const QModelIndex& index, int role) const { switch (role) { case Qt::BackgroundRole: { if (index.column() == 0) { //Highlight by match-quality uint color = matchColor(index); if (color) { return QBrush(color); } } //Use a special background-color for expanded items if (isExpanded(index)) { if (index.row() & 1) { return doAlternate(treeView()->palette().toolTipBase().color()); } else { return treeView()->palette().toolTipBase(); } } } } return QVariant(); } QModelIndex ExpandingWidgetModel::mapFromSource(const QModelIndex& index) const { const auto proxyModel = qobject_cast(treeView()->model()); Q_ASSERT(proxyModel); Q_ASSERT(!index.isValid() || index.model() == this); return proxyModel->mapFromSource(index); } QModelIndex ExpandingWidgetModel::mapToSource(const QModelIndex& index) const { const auto proxyModel = qobject_cast(treeView()->model()); Q_ASSERT(proxyModel); Q_ASSERT(!index.isValid() || index.model() == proxyModel); return proxyModel->mapToSource(index); } void ExpandingWidgetModel::clearMatchQualities() { m_contextMatchQualities.clear(); } QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const { if (m_partiallyExpanded.isEmpty()) { return QModelIndex(); } else { return m_partiallyExpanded.constBegin().key(); } } void ExpandingWidgetModel::clearExpanding() { clearMatchQualities(); QMap oldExpandState = m_expandState; foreach (QPointer widget, m_expandingWidgets) { delete widget; } m_expandingWidgets.clear(); m_expandState.clear(); m_partiallyExpanded.clear(); for (QMap::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it) { if (it.value() == Expanded) { emit dataChanged(it.key(), it.key()); } } } ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex& index) const { if (m_partiallyExpanded.contains(firstColumn(index))) { return m_partiallyExpanded[firstColumn(index)]; } else { return NotExpanded; } } void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex& idx_) { QModelIndex index(firstColumn(idx_)); m_partiallyExpanded.remove(index); m_partiallyExpanded.remove(idx_); } int ExpandingWidgetModel::partiallyExpandWidgetHeight() const { return 60; ///@todo use font-metrics text-height*2 for 2 lines } void ExpandingWidgetModel::rowSelected(const QModelIndex& idx_) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); if (!m_partiallyExpanded.contains(idx)) { QModelIndex oldIndex = partiallyExpandedRow(); //Unexpand the previous partially expanded row if (!m_partiallyExpanded.isEmpty()) { ///@todo allow multiple partially expanded rows while (!m_partiallyExpanded.isEmpty()) m_partiallyExpanded.erase(m_partiallyExpanded.begin()); //partiallyUnExpand( m_partiallyExpanded.begin().key() ); } //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget. if (!idx.isValid()) { //All items have been unselected if (oldIndex.isValid()) { emit dataChanged(oldIndex, oldIndex); } } else { QVariant variant = data(idx, CodeCompletionModel::ItemSelected); if (!isExpanded(idx) && variant.type() == QVariant::String) { //Either expand upwards or downwards, choose in a way that //the visible fields of the new selected entry are not moved. if (oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()))) { m_partiallyExpanded.insert(idx, ExpandUpwards); } else { m_partiallyExpanded.insert(idx, ExpandDownwards); } //Say that one row above until one row below has changed, so no items will need to be moved(the space that is taken from one item is given to the other) if (oldIndex.isValid() && oldIndex < idx) { emit dataChanged(oldIndex, idx); if (treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem) { const QModelIndex viewIndex = mapFromSource(idx); //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible, //so we do the scrolling by hand. QRect selectedRect = treeView()->visualRect(viewIndex); QRect frameRect = treeView()->frameRect(); if (selectedRect.bottom() > frameRect.bottom()) { int diff = selectedRect.bottom() - frameRect.bottom(); //We need to scroll down QModelIndex newTopIndex = viewIndex; QModelIndex nextTopIndex = viewIndex; QRect nextRect = treeView()->visualRect(nextTopIndex); while (nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff) { newTopIndex = nextTopIndex; nextTopIndex = treeView()->indexAbove(nextTopIndex); if (nextTopIndex.isValid()) { nextRect = treeView()->visualRect(nextTopIndex); } } treeView()->scrollTo(newTopIndex, QAbstractItemView::PositionAtTop); } } //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible. //But we must make sure that it isn't too expensive. //We need to make sure that scrolling is efficient, and the whole content is not repainted. //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature. //Since this also doesn't work smoothly, leave it for now //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); } else if (oldIndex.isValid() && idx < oldIndex) { emit dataChanged(idx, oldIndex); //For consistency with the down-scrolling, we keep one additional line visible above the current visible. //Since this also doesn't work smoothly, leave it for now /* QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column()); if( prevLine.isValid() ) treeView()->scrollTo( prevLine );*/ } else { emit dataChanged(idx, idx); } } else if (oldIndex.isValid()) { //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded. emit dataChanged(oldIndex, oldIndex); } } } else { qCDebug(PLUGIN_QUICKOPEN) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded"; } } QString ExpandingWidgetModel::partialExpandText(const QModelIndex& idx) const { Q_ASSERT(idx.model() == this); if (!idx.isValid()) { return QString(); } return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); } QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); if (!idx.isValid()) { return QRect(); } ExpansionType expansion = ExpandDownwards; if (m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd()) { expansion = m_partiallyExpanded[idx]; } //Get the whole rectangle of the row: const QModelIndex viewIndex = mapFromSource(idx); QModelIndex rightMostIndex = viewIndex; QModelIndex tempIndex = viewIndex; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) rightMostIndex = tempIndex; QRect rect = treeView()->visualRect(viewIndex); QRect rightMostRect = treeView()->visualRect(rightMostIndex); rect.setLeft(rect.left() + 20); rect.setRight(rightMostRect.right() - 5); //These offsets must match exactly those used in ExpandingDelegate::sizeHint() int top = rect.top() + 5; int bottom = rightMostRect.bottom() - 5; if (expansion == ExpandDownwards) { top += basicRowHeight(viewIndex); } else { bottom -= basicRowHeight(viewIndex); } rect.setTop(top); rect.setBottom(bottom); return rect; } bool ExpandingWidgetModel::isExpandable(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); if (!m_expandState.contains(idx)) { m_expandState.insert(idx, NotExpandable); QVariant v = data(idx, CodeCompletionModel::IsExpandable); if (v.canConvert() && v.toBool()) { m_expandState[idx] = Expandable; } } return m_expandState[idx] != NotExpandable; } bool ExpandingWidgetModel::isExpanded(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); return m_expandState.contains(idx) && m_expandState[idx] == Expanded; } void ExpandingWidgetModel::setExpanded(const QModelIndex& idx_, bool expanded) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); qCDebug(PLUGIN_QUICKOPEN) << "Setting expand-state of row " << idx.row() << " to " << expanded; if (!idx.isValid()) { return; } if (isExpandable(idx)) { if (!expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx]) { m_expandingWidgets[idx]->hide(); } m_expandState[idx] = expanded ? Expanded : Expandable; if (expanded) { partiallyUnExpand(idx); } if (expanded && !m_expandingWidgets.contains(idx)) { QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); if (v.canConvert()) { m_expandingWidgets[idx] = v.value(); } else if (v.canConvert()) { //Create a html widget that shows the given string KTextEdit* edit = new KTextEdit(v.toString()); edit->setReadOnly(true); edit->resize(200, 50); //Make the widget small so it embeds nicely. m_expandingWidgets[idx] = edit; } else { m_expandingWidgets[idx] = nullptr; } } //Eventually partially expand the row if (!expanded && firstColumn(mapToSource(treeView()->currentIndex())) == idx && (isPartiallyExpanded(idx) == ExpandingWidgetModel::ExpansionType::NotExpanded)) { rowSelected(idx); //Partially expand the row. } emit dataChanged(idx, idx); if (treeView()) { treeView()->scrollTo(mapFromSource(idx)); } } } int ExpandingWidgetModel::basicRowHeight(const QModelIndex& idx_) const { Q_ASSERT(idx_.model() == treeView()->model()); QModelIndex idx(firstColumn(idx_)); ExpandingDelegate* delegate = dynamic_cast(treeView()->itemDelegate(idx)); if (!delegate || !idx.isValid()) { qCDebug(PLUGIN_QUICKOPEN) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; return 15; } return delegate->basicSizeHint(idx).height(); } void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex& idx_) { Q_ASSERT(idx_.model() == this); QModelIndex idx(firstColumn(idx_)); QWidget* w = nullptr; if (m_expandingWidgets.contains(idx)) { w = m_expandingWidgets[idx]; } if (w && isExpanded(idx)) { if (!idx.isValid()) { return; } const QModelIndex viewIndex = mapFromSource(idx_); QRect rect = treeView()->visualRect(viewIndex); if (!rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height()) { //The item is currently not visible w->hide(); return; } QModelIndex rightMostIndex = viewIndex; QModelIndex tempIndex = viewIndex; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) rightMostIndex = tempIndex; QRect rightMostRect = treeView()->visualRect(rightMostIndex); //Find out the basic height of the row rect.setLeft(rect.left() + 20); rect.setRight(rightMostRect.right() - 5); //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() rect.setTop(rect.top() + basicRowHeight(viewIndex) + 5); rect.setHeight(w->height()); if (w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible()) { w->setParent(treeView()->viewport()); w->setGeometry(rect); w->show(); } } } void ExpandingWidgetModel::placeExpandingWidgets() { for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { placeExpandingWidget(it.key()); } } int ExpandingWidgetModel::expandingWidgetsHeight() const { int sum = 0; for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { if (isExpanded(it.key()) && (*it)) { sum += (*it)->height(); } } return sum; } QWidget* ExpandingWidgetModel::expandingWidget(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if (m_expandingWidgets.contains(idx)) { return m_expandingWidgets[idx]; } else { return nullptr; } } void ExpandingWidgetModel::cacheIcons() const { if (m_expandedIcon.isNull()) { m_expandedIcon = QIcon::fromTheme(QStringLiteral("arrow-down")); } if (m_collapsedIcon.isNull()) { m_collapsedIcon = QIcon::fromTheme(QStringLiteral("arrow-right")); } } QList mergeCustomHighlighting(int leftSize, const QList& left, int rightSize, const QList& right) { QList ret = left; if (left.isEmpty()) { ret << QVariant(0); ret << QVariant(leftSize); ret << QTextFormat(QTextFormat::CharFormat); } if (right.isEmpty()) { ret << QVariant(leftSize); ret << QVariant(rightSize); ret << QTextFormat(QTextFormat::CharFormat); } else { QList::const_iterator it = right.constBegin(); while (it != right.constEnd()) { { QList::const_iterator testIt = it; for (int a = 0; a < 2; a++) { ++testIt; if (testIt == right.constEnd()) { qCWarning(PLUGIN_QUICKOPEN) << "Length of input is not multiple of 3"; break; } } } ret << QVariant((*it).toInt() + leftSize); ++it; ret << QVariant((*it).toInt()); ++it; ret << *it; if (!(*it).value().isValid()) { qCDebug(PLUGIN_QUICKOPEN) << "Text-format is invalid"; } ++it; } } return ret; } //It is assumed that between each two strings, one space is inserted QList mergeCustomHighlighting(const QStringList& strings_, const QList& highlights_, int grapBetweenStrings) { QStringList strings(strings_); QList highlights(highlights_); if (strings.isEmpty()) { qCWarning(PLUGIN_QUICKOPEN) << "List of strings is empty"; return QList(); } if (highlights.isEmpty()) { qCWarning(PLUGIN_QUICKOPEN) << "List of highlightings is empty"; return QList(); } if (strings.count() != highlights.count()) { qCWarning(PLUGIN_QUICKOPEN) << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; return QList(); } //Merge them together QString totalString = strings[0]; QVariantList totalHighlighting = highlights[0]; strings.pop_front(); highlights.pop_front(); while (!strings.isEmpty()) { const int stringLength = strings[0].length(); totalHighlighting = mergeCustomHighlighting(totalString.length(), totalHighlighting, stringLength, highlights[0]); totalString.reserve(totalString.size() + stringLength + grapBetweenStrings); totalString += strings[0]; for (int a = 0; a < grapBetweenStrings; a++) { totalString += ' '; } strings.pop_front(); highlights.pop_front(); } //Combine the custom-highlightings return totalHighlighting; } diff --git a/plugins/quickopen/quickopenmodel.cpp b/plugins/quickopen/quickopenmodel.cpp index 12ec6017fc..df59a3b801 100644 --- a/plugins/quickopen/quickopenmodel.cpp +++ b/plugins/quickopen/quickopenmodel.cpp @@ -1,491 +1,491 @@ /* This file is part of the KDE libraries Copyright (C) 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 "quickopenmodel.h" #include "debug.h" #include #include -#include +#include #include #include "expandingtree/expandingtree.h" #include "projectfilequickopen.h" #include "duchainitemquickopen.h" #define QUICKOPEN_USE_ITEM_CACHING using namespace KDevelop; QuickOpenModel::QuickOpenModel(QWidget* parent) : ExpandingWidgetModel(parent) , m_treeView(nullptr) , m_expandingWidgetHeightIncrease(0) , m_resetBehindRow(0) { m_resetTimer = new QTimer(this); m_resetTimer->setSingleShot(true); connect(m_resetTimer, &QTimer::timeout, this, &QuickOpenModel::resetTimer); } void QuickOpenModel::setExpandingWidgetHeightIncrease(int pixels) { m_expandingWidgetHeightIncrease = pixels; } QStringList QuickOpenModel::allScopes() const { QStringList scopes; for (const ProviderEntry& provider : m_providers) { foreach (const QString& scope, provider.scopes) { if (!scopes.contains(scope)) { scopes << scope; } } } return scopes; } QStringList QuickOpenModel::allTypes() const { QSet types; for (const ProviderEntry& provider : m_providers) { types += provider.types; } return types.toList(); } void QuickOpenModel::registerProvider(const QStringList& scopes, const QStringList& types, KDevelop::QuickOpenDataProviderBase* provider) { ProviderEntry e; e.scopes = QSet::fromList(scopes); e.types = QSet::fromList(types); e.provider = provider; m_providers << e; //.insert( types, e ); connect(provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed); restart(true); } bool QuickOpenModel::removeProvider(KDevelop::QuickOpenDataProviderBase* provider) { bool ret = false; for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { if ((*it).provider == provider) { m_providers.erase(it); disconnect(provider, &QuickOpenDataProviderBase::destroyed, this, &QuickOpenModel::destroyed); ret = true; break; } } restart(true); return ret; } void QuickOpenModel::enableProviders(const QStringList& _items, const QStringList& _scopes) { QSet items = QSet::fromList(_items); QSet scopes = QSet::fromList(_scopes); if (m_enabledItems == items && m_enabledScopes == scopes && !items.isEmpty() && !scopes.isEmpty()) { return; } m_enabledItems = items; m_enabledScopes = scopes; qCDebug(PLUGIN_QUICKOPEN) << "params " << items << " " << scopes; //We use 2 iterations here: In the first iteration, all providers that implement QuickOpenFileSetInterface are initialized, then the other ones. //The reason is that the second group can refer to the first one. for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { if (!dynamic_cast((*it).provider)) { continue; } qCDebug(PLUGIN_QUICKOPEN) << "comparing" << (*it).scopes << (*it).types; if ((scopes.isEmpty() || !(scopes & (*it).scopes).isEmpty()) && (!(items & (*it).types).isEmpty() || items.isEmpty())) { qCDebug(PLUGIN_QUICKOPEN) << "enabling " << (*it).types << " " << (*it).scopes; (*it).enabled = true; (*it).provider->enableData(_items, _scopes); } else { qCDebug(PLUGIN_QUICKOPEN) << "disabling " << (*it).types << " " << (*it).scopes; (*it).enabled = false; if ((scopes.isEmpty() || !(scopes & (*it).scopes).isEmpty())) { (*it).provider->enableData(_items, _scopes); //The provider may still provide files } } } for (ProviderList::iterator it = m_providers.begin(); it != m_providers.end(); ++it) { if (dynamic_cast((*it).provider)) { continue; } qCDebug(PLUGIN_QUICKOPEN) << "comparing" << (*it).scopes << (*it).types; if ((scopes.isEmpty() || !(scopes & (*it).scopes).isEmpty()) && (!(items & (*it).types).isEmpty() || items.isEmpty())) { qCDebug(PLUGIN_QUICKOPEN) << "enabling " << (*it).types << " " << (*it).scopes; (*it).enabled = true; (*it).provider->enableData(_items, _scopes); } else { qCDebug(PLUGIN_QUICKOPEN) << "disabling " << (*it).types << " " << (*it).scopes; (*it).enabled = false; } } restart(true); } void QuickOpenModel::textChanged(const QString& str) { if (m_filterText == str) { return; } beginResetModel(); m_filterText = str; foreach (const ProviderEntry& provider, m_providers) { if (provider.enabled) { provider.provider->setFilterText(str); } } m_cachedData.clear(); clearExpanding(); //Get the 50 first items, so the data-providers notice changes without ui-glitches due to resetting for (int a = 0; a < 50 && a < rowCount(QModelIndex()); ++a) { getItem(a, true); } endResetModel(); } void QuickOpenModel::restart(bool keepFilterText) { // make sure we do not restart recursivly which could lead to // recursive loading of provider plugins e.g. (happened for the cpp plugin) QMetaObject::invokeMethod(this, "restart_internal", Qt::QueuedConnection, Q_ARG(bool, keepFilterText)); } void QuickOpenModel::restart_internal(bool keepFilterText) { if (!keepFilterText) { m_filterText.clear(); } bool anyEnabled = false; foreach (const ProviderEntry& e, m_providers) { anyEnabled |= e.enabled; } if (!anyEnabled) { return; } foreach (const ProviderEntry& provider, m_providers) { if (!dynamic_cast(provider.provider)) { continue; } ///Always reset providers that implement QuickOpenFileSetInterface and have some matchign scopes, because they may be needed by other providers. if (m_enabledScopes.isEmpty() || !(m_enabledScopes & provider.scopes).isEmpty()) { provider.provider->reset(); } } foreach (const ProviderEntry& provider, m_providers) { if (dynamic_cast(provider.provider)) { continue; } if (provider.enabled && provider.provider) { provider.provider->reset(); } } if (keepFilterText) { textChanged(m_filterText); } else { beginResetModel(); m_cachedData.clear(); clearExpanding(); endResetModel(); } } void QuickOpenModel::destroyed(QObject* obj) { removeProvider(static_cast(obj)); } QModelIndex QuickOpenModel::index(int row, int column, const QModelIndex& /*parent*/) const { if (column >= columnCount() || row >= rowCount(QModelIndex())) { return QModelIndex(); } if (row < 0 || column < 0) { return QModelIndex(); } return createIndex(row, column); } QModelIndex QuickOpenModel::parent(const QModelIndex&) const { return QModelIndex(); } int QuickOpenModel::rowCount(const QModelIndex& i) const { if (i.isValid()) { return 0; } int count = 0; for (const ProviderEntry& provider : m_providers) { if (provider.enabled) { count += provider.provider->itemCount(); } } return count; } int QuickOpenModel::unfilteredRowCount() const { int count = 0; for (const ProviderEntry& provider : m_providers) { if (provider.enabled) { count += provider.provider->unfilteredItemCount(); } } return count; } int QuickOpenModel::columnCount() const { return 2; } int QuickOpenModel::columnCount(const QModelIndex& index) const { if (index.parent().isValid()) { return 0; } else { return columnCount(); } } QVariant QuickOpenModel::data(const QModelIndex& index, int role) const { QuickOpenDataPointer d = getItem(index.row()); if (!d) { return QVariant(); } switch (role) { case KTextEditor::CodeCompletionModel::ItemSelected: { QString desc = d->htmlDescription(); if (desc.isEmpty()) { return QVariant(); } else { return desc; } } case KTextEditor::CodeCompletionModel::IsExpandable: return d->isExpandable(); case KTextEditor::CodeCompletionModel::ExpandingWidget: { QVariant v; QWidget* w = d->expandingWidget(); if (w && m_expandingWidgetHeightIncrease) { w->resize(w->width(), w->height() + m_expandingWidgetHeightIncrease); } v.setValue(w); return v; } case ExpandingTree::ProjectPathRole: // TODO: put this into the QuickOpenDataBase API // we cannot do this in 5.0, cannot change ABI if (auto projectFile = dynamic_cast(d.constData())) { return QVariant::fromValue(projectFile->projectPath()); } else if (auto duchainItem = dynamic_cast(d.constData())) { return QVariant::fromValue(duchainItem->projectPath()); } } if (index.column() == 1) { //This column contains the actual content switch (role) { case Qt::DecorationRole: return d->icon(); case Qt::DisplayRole: return d->text(); case KTextEditor::CodeCompletionModel::HighlightingMethod: return KTextEditor::CodeCompletionModel::CustomHighlighting; case KTextEditor::CodeCompletionModel::CustomHighlight: return d->highlighting(); } } else if (index.column() == 0) { //This column only contains the expanded/not expanded icon switch (role) { case Qt::DecorationRole: { if (isExpandable(index)) { //Show the expanded/unexpanded handles cacheIcons(); if (isExpanded(index)) { return m_expandedIcon; } else { return m_collapsedIcon; } } } } } return ExpandingWidgetModel::data(index, role); } void QuickOpenModel::resetTimer() { int currentRow = treeView() ? mapToSource(treeView()->currentIndex()).row() : -1; beginResetModel(); //Remove all cached data behind row m_resetBehindRow for (DataList::iterator it = m_cachedData.begin(); it != m_cachedData.end(); ) { if (it.key() > m_resetBehindRow) { it = m_cachedData.erase(it); } else { ++it; } } endResetModel(); if (currentRow != -1) { treeView()->setCurrentIndex(mapFromSource(index(currentRow, 0, QModelIndex()))); //Preserve the current index } m_resetBehindRow = 0; } QuickOpenDataPointer QuickOpenModel::getItem(int row, bool noReset) const { ///@todo mix all the models alphabetically here. For now, they are simply ordered. ///@todo Deal with unexpected item-counts, like for example in the case of overloaded function-declarations #ifdef QUICKOPEN_USE_ITEM_CACHING if (m_cachedData.contains(row)) { return m_cachedData[row]; } #endif int rowOffset = 0; Q_ASSERT(row < rowCount(QModelIndex())); for (const ProviderEntry& provider : m_providers) { if (!provider.enabled) { continue; } uint itemCount = provider.provider->itemCount(); if (( uint )row < itemCount) { QuickOpenDataPointer item = provider.provider->data(row); if (!noReset && provider.provider->itemCount() != itemCount) { qCDebug(PLUGIN_QUICKOPEN) << "item-count in provider has changed, resetting model"; m_resetTimer->start(0); m_resetBehindRow = rowOffset + row; //Don't reset everything, only everything behind this position } #ifdef QUICKOPEN_USE_ITEM_CACHING m_cachedData[row + rowOffset] = item; #endif return item; } else { row -= provider.provider->itemCount(); rowOffset += provider.provider->itemCount(); } } // qWarning() << "No item for row " << row; return QuickOpenDataPointer(); } QSet QuickOpenModel::fileSet() const { QSet merged; for (const ProviderEntry& provider : m_providers) { if (m_enabledScopes.isEmpty() || !(m_enabledScopes & provider.scopes).isEmpty()) { if (QuickOpenFileSetInterface* iface = dynamic_cast(provider.provider)) { QSet ifiles = iface->files(); //qCDebug(PLUGIN_QUICKOPEN) << "got file-list with" << ifiles.count() << "entries from data-provider" << typeid(*iface).name(); merged += ifiles; } } } return merged; } QTreeView* QuickOpenModel::treeView() const { return m_treeView; } bool QuickOpenModel::indexIsItem(const QModelIndex& index) const { Q_ASSERT(index.model() == this); Q_UNUSED(index); return true; } void QuickOpenModel::setTreeView(QTreeView* view) { m_treeView = view; } int QuickOpenModel::contextMatchQuality(const QModelIndex& /*index*/) const { return -1; } bool QuickOpenModel::execute(const QModelIndex& index, QString& filterText) { qCDebug(PLUGIN_QUICKOPEN) << "executing model"; if (!index.isValid()) { qCWarning(PLUGIN_QUICKOPEN) << "Invalid index executed"; return false; } QuickOpenDataPointer item = getItem(index.row()); if (item) { return item->execute(filterText); } else { qCWarning(PLUGIN_QUICKOPEN) << "Got no item for row " << index.row() << " "; } return false; } diff --git a/plugins/quickopen/quickopenplugin.cpp b/plugins/quickopen/quickopenplugin.cpp index 6e3e42ef99..2e5321202f 100644 --- a/plugins/quickopen/quickopenplugin.cpp +++ b/plugins/quickopen/quickopenplugin.cpp @@ -1,1172 +1,1173 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * 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 "quickopenplugin.h" #include "quickopenwidget.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 "quickopenmodel.h" #include "projectfilequickopen.h" #include "projectitemquickopen.h" #include "declarationlistquickopen.h" #include "documentationquickopenprovider.h" #include "actionsquickopenprovider.h" #include "debug.h" #include -#include #include #include + using namespace KDevelop; const bool noHtmlDestriptionInOutline = true; class QuickOpenWidgetCreator { public: virtual ~QuickOpenWidgetCreator() { } virtual QuickOpenWidget* createWidget() = 0; virtual QString objectNameForLine() = 0; virtual void widgetShown() { } }; class StandardQuickOpenWidgetCreator : public QuickOpenWidgetCreator { public: StandardQuickOpenWidgetCreator(const QStringList& items, const QStringList& scopes) : m_items(items) , m_scopes(scopes) { } QString objectNameForLine() override { return QStringLiteral("Quickopen"); } void setItems(const QStringList& scopes, const QStringList& items) { m_scopes = scopes; m_items = items; } QuickOpenWidget* createWidget() override { QStringList useItems = m_items; if (useItems.isEmpty()) { useItems = QuickOpenPlugin::self()->lastUsedItems; } QStringList useScopes = m_scopes; if (useScopes.isEmpty()) { useScopes = QuickOpenPlugin::self()->lastUsedScopes; } return new QuickOpenWidget(i18n("Quick Open"), QuickOpenPlugin::self()->m_model, QuickOpenPlugin::self()->lastUsedItems, useScopes, false, true); } QStringList m_items; QStringList m_scopes; }; class OutlineFilter : public DUChainUtils::DUChainItemFilter { public: enum OutlineMode { Functions, FunctionsAndClasses }; explicit OutlineFilter(QVector& _items, OutlineMode _mode = FunctionsAndClasses) : items(_items) , mode(_mode) { } bool accept(Declaration* decl) override { if (decl->range().isEmpty()) { return false; } bool collectable = mode == Functions ? decl->isFunctionDeclaration() : (decl->isFunctionDeclaration() || (decl->internalContext() && decl->internalContext()->type() == DUContext::Class)); if (collectable) { DUChainItem item; item.m_item = IndexedDeclaration(decl); item.m_text = decl->toString(); items << item; return true; } else { return false; } } bool accept(DUContext* ctx) override { if (ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper) { return true; } else { return false; } } QVector& items; OutlineMode mode; }; K_PLUGIN_FACTORY_WITH_JSON(KDevQuickOpenFactory, "kdevquickopen.json", registerPlugin(); ) Declaration * cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } KDevelop::DUChainReadLocker lock(DUChain::lock()); return DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); } ///The first definition that belongs to a context that surrounds the current cursor Declaration* cursorContextDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url()); if (!ctx) { return nullptr; } KTextEditor::Cursor cursor(view->cursorPosition()); DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor)); while (subCtx && !subCtx->owner()) subCtx = subCtx->parentContext(); Declaration* definition = nullptr; if (!subCtx || !subCtx->owner()) { definition = DUChainUtils::declarationInLine(cursor, ctx); } else { definition = subCtx->owner(); } if (!definition) { return nullptr; } return definition; } //Returns only the name, no template-parameters or scope QString cursorItemText() { KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { return QString(); } IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { return QString(); } TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return QString(); } AbstractType::Ptr t = decl->abstractType(); IdentifiedType* idType = dynamic_cast(t.data()); if (idType && idType->declaration(context)) { decl = idType->declaration(context); } if (!decl->qualifiedIdentifier().isEmpty()) { return decl->qualifiedIdentifier().last().identifier().str(); } return QString(); } QuickOpenLineEdit* QuickOpenPlugin::createQuickOpenLineWidget() { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(QStringList(), QStringList())); } QuickOpenLineEdit* QuickOpenPlugin::quickOpenLine(const QString& name) { const QList lines = ICore::self()->uiController()->activeMainWindow()->findChildren(name); for (QuickOpenLineEdit* line : lines) { if (line->isVisible()) { return line; } } return nullptr; } static QuickOpenPlugin* staticQuickOpenPlugin = nullptr; QuickOpenPlugin* QuickOpenPlugin::self() { return staticQuickOpenPlugin; } void QuickOpenPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevquickopen.rc"); QAction* quickOpen = actions.addAction(QStringLiteral("quick_open")); quickOpen->setText(i18n("&Quick Open")); quickOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen"))); actions.setDefaultShortcut(quickOpen, Qt::CTRL | Qt::ALT | Qt::Key_Q); connect(quickOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpen); QAction* quickOpenFile = actions.addAction(QStringLiteral("quick_open_file")); quickOpenFile->setText(i18n("Quick Open &File")); quickOpenFile->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file"))); actions.setDefaultShortcut(quickOpenFile, Qt::CTRL | Qt::ALT | Qt::Key_O); connect(quickOpenFile, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFile); QAction* quickOpenClass = actions.addAction(QStringLiteral("quick_open_class")); quickOpenClass->setText(i18n("Quick Open &Class")); quickOpenClass->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-class"))); actions.setDefaultShortcut(quickOpenClass, Qt::CTRL | Qt::ALT | Qt::Key_C); connect(quickOpenClass, &QAction::triggered, this, &QuickOpenPlugin::quickOpenClass); QAction* quickOpenFunction = actions.addAction(QStringLiteral("quick_open_function")); quickOpenFunction->setText(i18n("Quick Open &Function")); quickOpenFunction->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-function"))); actions.setDefaultShortcut(quickOpenFunction, Qt::CTRL | Qt::ALT | Qt::Key_M); connect(quickOpenFunction, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFunction); QAction* quickOpenAlreadyOpen = actions.addAction(QStringLiteral("quick_open_already_open")); quickOpenAlreadyOpen->setText(i18n("Quick Open &Already Open File")); quickOpenAlreadyOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file"))); connect(quickOpenAlreadyOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpenOpenFile); QAction* quickOpenDocumentation = actions.addAction(QStringLiteral("quick_open_documentation")); quickOpenDocumentation->setText(i18n("Quick Open &Documentation")); quickOpenDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-documentation"))); actions.setDefaultShortcut(quickOpenDocumentation, Qt::CTRL | Qt::ALT | Qt::Key_D); connect(quickOpenDocumentation, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDocumentation); QAction* quickOpenActions = actions.addAction(QStringLiteral("quick_open_actions")); quickOpenActions->setText(i18n("Quick Open &Actions")); actions.setDefaultShortcut(quickOpenActions, Qt::CTRL | Qt::ALT | Qt::Key_A); connect(quickOpenActions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenActions); m_quickOpenDeclaration = actions.addAction(QStringLiteral("quick_open_jump_declaration")); m_quickOpenDeclaration->setText(i18n("Jump to Declaration")); m_quickOpenDeclaration->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-declaration"))); actions.setDefaultShortcut(m_quickOpenDeclaration, Qt::CTRL | Qt::Key_Period); connect(m_quickOpenDeclaration, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDeclaration, Qt::QueuedConnection); m_quickOpenDefinition = actions.addAction(QStringLiteral("quick_open_jump_definition")); m_quickOpenDefinition->setText(i18n("Jump to Definition")); m_quickOpenDefinition->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-definition"))); actions.setDefaultShortcut(m_quickOpenDefinition, Qt::CTRL | Qt::Key_Comma); connect(m_quickOpenDefinition, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDefinition, Qt::QueuedConnection); QWidgetAction* quickOpenLine = new QWidgetAction(this); quickOpenLine->setText(i18n("Embedded Quick Open")); // actions.setDefaultShortcut( quickOpenLine, Qt::CTRL | Qt::ALT | Qt::Key_E ); // connect(quickOpenLine, SIGNAL(triggered(bool)), this, SLOT(quickOpenLine(bool))); quickOpenLine->setDefaultWidget(createQuickOpenLineWidget()); actions.addAction(QStringLiteral("quick_open_line"), quickOpenLine); QAction* quickOpenNextFunction = actions.addAction(QStringLiteral("quick_open_next_function")); quickOpenNextFunction->setText(i18n("Next Function")); actions.setDefaultShortcut(quickOpenNextFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageDown); connect(quickOpenNextFunction, &QAction::triggered, this, &QuickOpenPlugin::nextFunction); QAction* quickOpenPrevFunction = actions.addAction(QStringLiteral("quick_open_prev_function")); quickOpenPrevFunction->setText(i18n("Previous Function")); actions.setDefaultShortcut(quickOpenPrevFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageUp); connect(quickOpenPrevFunction, &QAction::triggered, this, &QuickOpenPlugin::previousFunction); QAction* quickOpenNavigateFunctions = actions.addAction(QStringLiteral("quick_open_outline")); quickOpenNavigateFunctions->setText(i18n("Outline")); actions.setDefaultShortcut(quickOpenNavigateFunctions, Qt::CTRL | Qt::ALT | Qt::Key_N); connect(quickOpenNavigateFunctions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenNavigateFunctions); } QuickOpenPlugin::QuickOpenPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevquickopen"), parent) { staticQuickOpenPlugin = this; m_model = new QuickOpenModel(nullptr); KConfigGroup quickopengrp = KSharedConfig::openConfig()->group("QuickOpen"); lastUsedScopes = quickopengrp.readEntry("SelectedScopes", QStringList{ i18n("Project"), i18n("Includes"), i18n("Includers"), i18n("Currently Open")}); lastUsedItems = quickopengrp.readEntry("SelectedItems", QStringList()); { m_openFilesData = new OpenFilesDataProvider(); QStringList scopes, items; scopes << i18n("Currently Open"); items << i18n("Files"); m_model->registerProvider(scopes, items, m_openFilesData); } { m_projectFileData = new ProjectFileDataProvider(); QStringList scopes, items; scopes << i18n("Project"); items << i18n("Files"); m_model->registerProvider(scopes, items, m_projectFileData); } { m_projectItemData = new ProjectItemDataProvider(this); QStringList scopes, items; scopes << i18n("Project"); items << ProjectItemDataProvider::supportedItemTypes(); m_model->registerProvider(scopes, items, m_projectItemData); } { m_documentationItemData = new DocumentationQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Documentation"); m_model->registerProvider(scopes, items, m_documentationItemData); } { m_actionsItemData = new ActionsQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Actions"); m_model->registerProvider(scopes, items, m_actionsItemData); } } QuickOpenPlugin::~QuickOpenPlugin() { freeModel(); delete m_model; delete m_projectFileData; delete m_projectItemData; delete m_openFilesData; delete m_documentationItemData; delete m_actionsItemData; } void QuickOpenPlugin::unload() { } ContextMenuExtension QuickOpenPlugin::contextMenuExtension(Context* context, QWidget* parent) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension(context, parent); KDevelop::DeclarationContext* codeContext = dynamic_cast(context); if (!codeContext) { return menuExt; } DUChainReadLocker readLock; Declaration* decl(codeContext->declaration().data()); if (decl) { const bool isDef = FunctionDefinition::definition(decl); if (codeContext->use().isValid() || !isDef) { menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDeclaration); } if (isDef) { menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDefinition); } } return menuExt; } void QuickOpenPlugin::showQuickOpen(const QStringList& items) { if (!freeModel()) { return; } QStringList initialItems = items; QStringList useScopes = lastUsedScopes; if (!useScopes.contains(i18n("Currently Open"))) { useScopes << i18n("Currently Open"); } showQuickOpenWidget(initialItems, useScopes, false); } void QuickOpenPlugin::showQuickOpen(ModelTypes modes) { if (!freeModel()) { return; } QStringList initialItems; if (modes & Files || modes & OpenFiles) { initialItems << i18n("Files"); } if (modes & Functions) { initialItems << i18n("Functions"); } if (modes & Classes) { initialItems << i18n("Classes"); } QStringList useScopes; if (modes != OpenFiles) { useScopes = lastUsedScopes; } if ((modes & OpenFiles) && !useScopes.contains(i18n("Currently Open"))) { useScopes << i18n("Currently Open"); } bool preselectText = (!(modes & Files) || modes == QuickOpenPlugin::All); showQuickOpenWidget(initialItems, useScopes, preselectText); } void QuickOpenPlugin::showQuickOpenWidget(const QStringList& items, const QStringList& scopes, bool preselectText) { QuickOpenWidgetDialog* dialog = new QuickOpenWidgetDialog(i18n("Quick Open"), m_model, items, scopes); m_currentWidgetHandler = dialog; if (preselectText) { KDevelop::IDocument* currentDoc = core()->documentController()->activeDocument(); if (currentDoc && currentDoc->isTextDocument()) { QString preselected = currentDoc->textSelection().isEmpty() ? currentDoc->textWord() : currentDoc->textDocument()->text(currentDoc->textSelection()); dialog->widget()->setPreselectedText(preselected); } } connect(dialog->widget(), &QuickOpenWidget::scopesChanged, this, &QuickOpenPlugin::storeScopes); //Not connecting itemsChanged to storeItems, as showQuickOpen doesn't use lastUsedItems and so shouldn't store item changes //connect( dialog->widget(), SIGNAL(itemsChanged(QStringList)), this, SLOT(storeItems(QStringList)) ); dialog->widget()->ui.itemsButton->setEnabled(false); if (quickOpenLine()) { quickOpenLine()->showWithWidget(dialog->widget()); dialog->deleteLater(); } else { dialog->run(); } } void QuickOpenPlugin::storeScopes(const QStringList& scopes) { lastUsedScopes = scopes; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry("SelectedScopes", scopes); } void QuickOpenPlugin::storeItems(const QStringList& items) { lastUsedItems = items; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry("SelectedItems", items); } void QuickOpenPlugin::quickOpen() { if (quickOpenLine()) { //Same as clicking on Quick Open quickOpenLine()->setFocus(); } else { showQuickOpen(All); } } void QuickOpenPlugin::quickOpenFile() { showQuickOpen(( ModelTypes )(Files | OpenFiles)); } void QuickOpenPlugin::quickOpenFunction() { showQuickOpen(Functions); } void QuickOpenPlugin::quickOpenClass() { showQuickOpen(Classes); } void QuickOpenPlugin::quickOpenOpenFile() { showQuickOpen(OpenFiles); } void QuickOpenPlugin::quickOpenDocumentation() { showQuickOpenWidget(QStringList(i18n("Documentation")), QStringList(i18n("Includes")), true); } void QuickOpenPlugin::quickOpenActions() { showQuickOpenWidget(QStringList(i18n("Actions")), QStringList(i18n("Includes")), true); } QSet QuickOpenPlugin::fileSet() const { return m_model->fileSet(); } void QuickOpenPlugin::registerProvider(const QStringList& scope, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider) { m_model->registerProvider(scope, type, provider); } bool QuickOpenPlugin::removeProvider(KDevelop::QuickOpenDataProviderBase* provider) { m_model->removeProvider(provider); return true; } void QuickOpenPlugin::quickOpenDeclaration() { if (jumpToSpecialObject()) { return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } decl->activateSpecialization(); IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if (u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } QWidget* QuickOpenPlugin::specialObjectNavigationWidget() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); for (const auto language : languages) { QWidget* w = language->specialLanguageObjectNavigationWidget(url, KTextEditor::Cursor(view->cursorPosition())); if (w) { return w; } } return nullptr; } QPair QuickOpenPlugin::specialObjectJumpPosition() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return qMakePair(QUrl(), KTextEditor::Cursor()); } QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); for (const auto language : languages) { QPair pos = language->specialLanguageObjectJumpCursor(url, KTextEditor::Cursor(view->cursorPosition())); if (pos.second.isValid()) { return pos; } } return qMakePair(QUrl(), KTextEditor::Cursor::invalid()); } bool QuickOpenPlugin::jumpToSpecialObject() { QPair pos = specialObjectJumpPosition(); if (pos.second.isValid()) { if (pos.first.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for special language object"; return false; } ICore::self()->documentController()->openDocument(pos.first, pos.second); return true; } return false; } void QuickOpenPlugin::quickOpenDefinition() { if (jumpToSpecialObject()) { return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if (FunctionDefinition* def = FunctionDefinition::definition(decl)) { def->activateSpecialization(); u = def->url(); c = def->rangeInCurrentRevision().start(); } else { qCDebug(PLUGIN_QUICKOPEN) << "Found no definition for declaration"; decl->activateSpecialization(); } if (u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } bool QuickOpenPlugin::freeModel() { if (m_currentWidgetHandler) { delete m_currentWidgetHandler; } m_currentWidgetHandler = nullptr; return true; } void QuickOpenPlugin::nextFunction() { jumpToNearestFunction(NextFunction); } void QuickOpenPlugin::previousFunction() { jumpToNearestFunction(PreviousFunction); } void QuickOpenPlugin::jumpToNearestFunction(QuickOpenPlugin::FunctionJumpDirection direction) { IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } QVector items; OutlineFilter filter(items, OutlineFilter::Functions); DUChainUtils::collectItems(context, filter); CursorInRevision cursor = context->transformToLocalRevision(KTextEditor::Cursor(doc->cursorPosition())); if (!cursor.isValid()) { return; } Declaration* nearestDeclBefore = nullptr; int distanceBefore = INT_MIN; Declaration* nearestDeclAfter = nullptr; int distanceAfter = INT_MAX; for (int i = 0; i < items.count(); ++i) { Declaration* decl = items[i].m_item.data(); int distance = decl->range().start.line - cursor.line; if (distance < 0 && distance >= distanceBefore) { distanceBefore = distance; nearestDeclBefore = decl; } else if (distance > 0 && distance <= distanceAfter) { distanceAfter = distance; nearestDeclAfter = decl; } } CursorInRevision c = CursorInRevision::invalid(); if (direction == QuickOpenPlugin::NextFunction && nearestDeclAfter) { c = nearestDeclAfter->range().start; } else if (direction == QuickOpenPlugin::PreviousFunction && nearestDeclBefore) { c = nearestDeclBefore->range().start; } KTextEditor::Cursor textCursor = KTextEditor::Cursor::invalid(); if (c.isValid()) { textCursor = context->transformFromLocalRevision(c); } lock.unlock(); if (textCursor.isValid()) { core()->documentController()->openDocument(doc->url(), textCursor); } else { qCDebug(PLUGIN_QUICKOPEN) << "No declaration to jump to"; } } struct CreateOutlineDialog { CreateOutlineDialog() : dialog(nullptr) , cursorDecl(nullptr) , model(nullptr) { } void start() { if (!QuickOpenPlugin::self()->freeModel()) { return; } IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } model = new QuickOpenModel(nullptr); OutlineFilter filter(items); DUChainUtils::collectItems(context, filter); if (noHtmlDestriptionInOutline) { for (int a = 0; a < items.size(); ++a) { items[a].m_noHtmlDestription = true; } } cursorDecl = cursorContextDeclaration(); model->registerProvider(QStringList(), QStringList(), new DeclarationListDataProvider(QuickOpenPlugin::self(), items, true)); dialog = new QuickOpenWidgetDialog(i18n("Outline"), model, QStringList(), QStringList(), true); dialog->widget()->setSortingEnabled(true); model->setParent(dialog->widget()); } void finish() { //Select the declaration that contains the cursor if (cursorDecl && dialog) { int num = 0; foreach (const DUChainItem& item, items) { if (item.m_item.data() == cursorDecl) { QModelIndex index(model->index(num, 0, QModelIndex())); // Need to invoke the scrolling later. If we did it now, then it wouldn't have any effect, // apparently because the widget internals aren't initialized yet properly (although we've // already called 'widget->show()'. auto list = dialog->widget()->ui.list; QMetaObject::invokeMethod(list, "setCurrentIndex", Qt::QueuedConnection, Q_ARG(QModelIndex, index)); QMetaObject::invokeMethod(list, "scrollTo", Qt::QueuedConnection, Q_ARG(QModelIndex, index), Q_ARG(QAbstractItemView::ScrollHint, QAbstractItemView::PositionAtCenter)); } ++num; } } } QPointer dialog; Declaration* cursorDecl; QVector items; QuickOpenModel* model; }; class OutlineQuickopenWidgetCreator : public QuickOpenWidgetCreator { public: OutlineQuickopenWidgetCreator(const QStringList& /*scopes*/, const QStringList& /*items*/) : m_creator(nullptr) { } ~OutlineQuickopenWidgetCreator() override { delete m_creator; } QuickOpenWidget* createWidget() override { delete m_creator; m_creator = new CreateOutlineDialog; m_creator->start(); if (!m_creator->dialog) { return nullptr; } m_creator->dialog->deleteLater(); return m_creator->dialog->widget(); } void widgetShown() override { if (m_creator) { m_creator->finish(); delete m_creator; m_creator = nullptr; } } QString objectNameForLine() override { return QStringLiteral("Outline"); } CreateOutlineDialog* m_creator; }; void QuickOpenPlugin::quickOpenNavigateFunctions() { CreateOutlineDialog create; create.start(); if (!create.dialog) { return; } m_currentWidgetHandler = create.dialog; QuickOpenLineEdit* line = quickOpenLine(QStringLiteral("Outline")); if (!line) { line = quickOpenLine(); } if (line) { line->showWithWidget(create.dialog->widget()); create.dialog->deleteLater(); } else { create.dialog->run(); } create.finish(); } QuickOpenLineEdit::QuickOpenLineEdit(QuickOpenWidgetCreator* creator) : m_widget(nullptr) , m_forceUpdate(false) , m_widgetCreator(creator) { setFont(qApp->font("QToolButton")); setMinimumWidth(200); setMaximumWidth(400); deactivate(); setDefaultText(i18n("Quick Open...")); setToolTip(i18n("Search for files, classes, functions and more," " allowing you to quickly navigate in your source code.")); setObjectName(m_widgetCreator->objectNameForLine()); setFocusPolicy(Qt::ClickFocus); } QuickOpenLineEdit::~QuickOpenLineEdit() { delete m_widget; delete m_widgetCreator; } bool QuickOpenLineEdit::insideThis(QObject* object) { while (object) { qCDebug(PLUGIN_QUICKOPEN) << object; if (object == this || object == m_widget) { return true; } object = object->parent(); } return false; } void QuickOpenLineEdit::widgetDestroyed(QObject* obj) { Q_UNUSED(obj); // need to use a queued connection here, because this function is called in ~QWidget! // => QuickOpenWidget instance is half-destructed => connections are not yet cleared // => clear() will trigger signals which will operate on the invalid QuickOpenWidget // So, just wait until properly destructed QMetaObject::invokeMethod(this, "deactivate", Qt::QueuedConnection); } void QuickOpenLineEdit::showWithWidget(QuickOpenWidget* widget) { connect(widget, &QuickOpenWidget::destroyed, this, &QuickOpenLineEdit::widgetDestroyed); qCDebug(PLUGIN_QUICKOPEN) << "storing widget" << widget; deactivate(); if (m_widget) { qCDebug(PLUGIN_QUICKOPEN) << "deleting" << m_widget; delete m_widget; } m_widget = widget; m_forceUpdate = true; setFocus(); } void QuickOpenLineEdit::focusInEvent(QFocusEvent* ev) { QLineEdit::focusInEvent(ev); // delete m_widget; qCDebug(PLUGIN_QUICKOPEN) << "got focus"; qCDebug(PLUGIN_QUICKOPEN) << "old widget" << m_widget << "force update:" << m_forceUpdate; if (m_widget && !m_forceUpdate) { return; } if (!m_forceUpdate && !QuickOpenPlugin::self()->freeModel()) { deactivate(); return; } m_forceUpdate = false; if (!m_widget) { m_widget = m_widgetCreator->createWidget(); if (!m_widget) { deactivate(); return; } } activate(); m_widget->showStandardButtons(false); m_widget->showSearchField(false); m_widget->setParent(nullptr, Qt::ToolTip); m_widget->setFocusPolicy(Qt::NoFocus); m_widget->setAlternativeSearchField(this); QuickOpenPlugin::self()->m_currentWidgetHandler = m_widget; connect(m_widget.data(), &QuickOpenWidget::ready, this, &QuickOpenLineEdit::deactivate); connect(m_widget.data(), &QuickOpenWidget::scopesChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeScopes); connect(m_widget.data(), &QuickOpenWidget::itemsChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeItems); Q_ASSERT(m_widget->ui.searchLine == this); m_widget->prepareShow(); QRect widgetGeometry = QRect(mapToGlobal(QPoint(0, height())), mapToGlobal(QPoint(width(), height() + 400))); widgetGeometry.setWidth(700); ///@todo Waste less space QRect screenGeom = QApplication::desktop()->screenGeometry(this); if (widgetGeometry.right() > screenGeom.right()) { widgetGeometry.moveRight(screenGeom.right()); } if (widgetGeometry.bottom() > screenGeom.bottom()) { widgetGeometry.moveBottom(mapToGlobal(QPoint(0, 0)).y()); } m_widget->setGeometry(widgetGeometry); m_widget->show(); m_widgetCreator->widgetShown(); } void QuickOpenLineEdit::hideEvent(QHideEvent* ev) { QWidget::hideEvent(ev); if (m_widget) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } // deactivate(); } bool QuickOpenLineEdit::eventFilter(QObject* obj, QEvent* e) { if (!m_widget) { return IQuickOpenLine::eventFilter(obj, e); } switch (e->type()) { case QEvent::KeyPress: case QEvent::ShortcutOverride: if (static_cast(e)->key() == Qt::Key_Escape) { deactivate(); e->accept(); return true; // eat event } break; case QEvent::WindowActivate: case QEvent::WindowDeactivate: QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); break; // handle bug 260657 - "Outline menu doesn't follow main window on its move" case QEvent::Move: { if (QWidget* widget = qobject_cast(obj)) { // close the outline menu in case a parent widget moved if (widget->isAncestorOf(this)) { qCDebug(PLUGIN_QUICKOPEN) << "closing because of parent widget move"; deactivate(); } } break; } case QEvent::FocusIn: if (dynamic_cast(obj)) { QFocusEvent* focusEvent = dynamic_cast(e); Q_ASSERT(focusEvent); //Eat the focus event, keep the focus qCDebug(PLUGIN_QUICKOPEN) << "focus change" << "inside this: " << insideThis(obj) << "this" << this << "obj" << obj; if (obj == this) { break; } qCDebug(PLUGIN_QUICKOPEN) << "reason" << focusEvent->reason(); if (focusEvent->reason() != Qt::MouseFocusReason && focusEvent->reason() != Qt::ActiveWindowFocusReason) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); break; } if (!insideThis(obj)) { deactivate(); } } else if (obj != this) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } break; default: break; } return IQuickOpenLine::eventFilter(obj, e); } void QuickOpenLineEdit::activate() { qCDebug(PLUGIN_QUICKOPEN) << "activating"; setText(QString()); setStyleSheet(QString()); qApp->installEventFilter(this); } void QuickOpenLineEdit::deactivate() { qCDebug(PLUGIN_QUICKOPEN) << "deactivating"; clear(); if (m_widget || hasFocus()) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } if (m_widget) { m_widget->deleteLater(); } m_widget = nullptr; qApp->removeEventFilter(this); } void QuickOpenLineEdit::checkFocus() { qCDebug(PLUGIN_QUICKOPEN) << "checking focus" << m_widget; if (m_widget) { QWidget* focusWidget = QApplication::focusWidget(); bool focusWidgetInsideThis = focusWidget ? insideThis(focusWidget) : false; if (QApplication::focusWindow() && isVisible() && !isHidden() && (!focusWidget || (focusWidget && focusWidgetInsideThis))) { qCDebug(PLUGIN_QUICKOPEN) << "setting focus to line edit"; activateWindow(); setFocus(); } else { qCDebug(PLUGIN_QUICKOPEN) << "deactivating because check failed, focusWidget" << focusWidget << "insideThis" << focusWidgetInsideThis; deactivate(); } } else { if (ICore::self()->documentController()->activeDocument()) { ICore::self()->documentController()->activateDocument(ICore::self()->documentController()->activeDocument()); } //Make sure the focus is somewehre else, even if there is no active document setEnabled(false); setEnabled(true); } } IQuickOpenLine* QuickOpenPlugin::createQuickOpenLine(const QStringList& scopes, const QStringList& type, IQuickOpen::QuickOpenType kind) { if (kind == Outline) { return new QuickOpenLineEdit(new OutlineQuickopenWidgetCreator(scopes, type)); } else { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(scopes, type)); } } #include "quickopenplugin.moc" diff --git a/plugins/standardoutputview/standardoutputviewmetadata.cpp b/plugins/standardoutputview/standardoutputviewmetadata.cpp index a7df11c3bd..3a99548f79 100644 --- a/plugins/standardoutputview/standardoutputviewmetadata.cpp +++ b/plugins/standardoutputview/standardoutputviewmetadata.cpp @@ -1,28 +1,28 @@ /* KDevelop Standard OutputView * * Copyright 2014 Alex Richardson * * 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 "standardoutputview.h" -#include +#include // this is split out to a separate file so that compiling the test doesn't need the json file K_PLUGIN_FACTORY_WITH_JSON(StandardOutputViewFactory, "kdevstandardoutputview.json", registerPlugin(); ) #include "standardoutputviewmetadata.moc" diff --git a/plugins/subversion/svnjobbase.cpp b/plugins/subversion/svnjobbase.cpp index e3e628505d..7e7c287fbf 100644 --- a/plugins/subversion/svnjobbase.cpp +++ b/plugins/subversion/svnjobbase.cpp @@ -1,217 +1,217 @@ /*************************************************************************** * Copyright 2007 Dukju Ahn * * * * 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 "svnjobbase.h" #include -#include +#include #include -#include +#include #include #include #include #include #include #include "svninternaljobbase.h" #include "svnssldialog.h" SvnJobBase::SvnJobBase( KDevSvnPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity ) : VcsJob( parent, verbosity ), m_part( parent ), m_status( KDevelop::VcsJob::JobNotStarted ) { setCapabilities( KJob::Killable ); setTitle( QStringLiteral("Subversion") ); } SvnJobBase::~SvnJobBase() { } void SvnJobBase::startInternalJob() { auto job = internalJob(); connect( job.data(), &SvnInternalJobBase::failed, this, &SvnJobBase::internalJobFailed, Qt::QueuedConnection ); connect( job.data(), &SvnInternalJobBase::done, this, &SvnJobBase::internalJobDone, Qt::QueuedConnection ); connect( job.data(), &SvnInternalJobBase::started, this, &SvnJobBase::internalJobStarted, Qt::QueuedConnection ); // add as shared pointer // the signals "done" & "failed" are emitted when the queue and the executor still // have and use a reference to the job, in the execution thread. // As the this parent job will be deleted in the main/other thread // (due to deleteLater() being called on it in the KJob::exec()) // and the ThreadWeaver queue will release the last reference to the passed // JobInterface pointer only after the JobInterface::execute() method has been left, // the internal threaded job thus needs to get shared memory management via the QSharedPointer. m_part->jobQueue()->stream() << job; } bool SvnJobBase::doKill() { internalJob()->kill(); m_status = VcsJob::JobCanceled; return true; } KDevelop::VcsJob::JobStatus SvnJobBase::status() const { return m_status; } void SvnJobBase::askForLogin( const QString& realm ) { qCDebug(PLUGIN_SVN) << "login"; KPasswordDialog dlg( nullptr, KPasswordDialog::ShowUsernameLine | KPasswordDialog::ShowKeepPassword ); dlg.setPrompt( i18n("Enter Login for: %1", realm ) ); if (dlg.exec()) { // krazy:exclude=crashy internalJob()->m_login_username = dlg.username(); internalJob()->m_login_password = dlg.password(); internalJob()->m_maySave = dlg.keepPassword(); } else { internalJob()->m_login_username.clear(); internalJob()->m_login_password.clear(); } internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::showNotification( const QString& path, const QString& msg ) { Q_UNUSED(path); outputMessage(msg); } void SvnJobBase::askForCommitMessage() { qCDebug(PLUGIN_SVN) << "commit msg"; internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::askForSslServerTrust( const QStringList& failures, const QString& host, const QString& print, const QString& from, const QString& until, const QString& issuer, const QString& realm ) { qCDebug(PLUGIN_SVN) << "servertrust"; SvnSSLTrustDialog dlg; dlg.setCertInfos( host, print, from, until, issuer, realm, failures ); if( dlg.exec() == QDialog::Accepted ) { qCDebug(PLUGIN_SVN) << "accepted with:" << dlg.useTemporarily(); if( dlg.useTemporarily() ) { internalJob()->m_trustAnswer = svn::ContextListener::ACCEPT_TEMPORARILY; }else { internalJob()->m_trustAnswer = svn::ContextListener::ACCEPT_PERMANENTLY; } }else { qCDebug(PLUGIN_SVN) << "didn't accept"; internalJob()->m_trustAnswer = svn::ContextListener::DONT_ACCEPT; } internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::askForSslClientCert( const QString& realm ) { KMessageBox::information( nullptr, realm ); qCDebug(PLUGIN_SVN) << "clientrust"; internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::askForSslClientCertPassword( const QString& ) { qCDebug(PLUGIN_SVN) << "clientpw"; internalJob()->m_guiSemaphore.release( 1 ); } void SvnJobBase::internalJobStarted() { qCDebug(PLUGIN_SVN) << "job started" << static_cast(internalJob().data()); m_status = KDevelop::VcsJob::JobRunning; } void SvnJobBase::internalJobDone() { qCDebug(PLUGIN_SVN) << "job done" << internalJob(); if ( m_status == VcsJob::JobFailed ) { // see: https://bugs.kde.org/show_bug.cgi?id=273759 // this gets also called when the internal job failed // then the emit result in internalJobFailed might trigger // a nested event loop (i.e. error dialog) // during that the internalJobDone gets called and triggers // deleteLater and eventually deletes this job // => havoc // // catching this state here works but I don't like it personally... return; } outputMessage(i18n("Completed")); if( m_status != VcsJob::JobCanceled ) { m_status = KDevelop::VcsJob::JobSucceeded; } emitResult(); } void SvnJobBase::internalJobFailed() { qCDebug(PLUGIN_SVN) << "job failed" << internalJob(); setError( 255 ); QString msg = internalJob()->errorMessage(); if( !msg.isEmpty() ) setErrorText( i18n( "Error executing Job:\n%1", msg ) ); outputMessage(errorText()); qCDebug(PLUGIN_SVN) << "Job failed"; if( m_status != VcsJob::JobCanceled ) { m_status = KDevelop::VcsJob::JobFailed; } emitResult(); } KDevelop::IPlugin* SvnJobBase::vcsPlugin() const { return m_part; } void SvnJobBase::outputMessage(const QString& message) { if (!model()) return; if (verbosity() == KDevelop::OutputJob::Silent) return; QStandardItemModel *m = qobject_cast(model()); QStandardItem *previous = m->item(m->rowCount()-1); if (message == QLatin1String(".") && previous && previous->text().contains(QRegExp("\\.+"))) previous->setText(previous->text() + message); else m->appendRow(new QStandardItem(message)); KDevelop::IPlugin* i = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IOutputView")); if( i ) { KDevelop::IOutputView* view = i->extension(); if( view ) { view->raiseOutput( outputId() ); } } } diff --git a/plugins/switchtobuddy/switchtobuddyplugin.cpp b/plugins/switchtobuddy/switchtobuddyplugin.cpp index a30e6c67b8..1c32c5ff8d 100644 --- a/plugins/switchtobuddy/switchtobuddyplugin.cpp +++ b/plugins/switchtobuddy/switchtobuddyplugin.cpp @@ -1,309 +1,309 @@ /* * This file is part of KDevelop * Copyright 2012 André Stein * Copyright 2014 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 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 "switchtobuddyplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include - -#include -#include #include +#include +#include + using namespace KDevelop; namespace { KTextEditor::Cursor normalizeCursor(KTextEditor::Cursor c) { c.setColumn(0); return c; } ///Tries to find a definition for the declaration at given cursor-position and document-url. DUChain must be locked. Declaration* definitionForCursorDeclaration(const KTextEditor::Cursor& cursor, const QUrl& url) { const QList topContexts = DUChain::self()->chainsForDocument(url); for (TopDUContext* ctx : topContexts) { Declaration* decl = DUChainUtils::declarationInLine(cursor, ctx); if (!decl) { continue; } if (auto definition = FunctionDefinition::definition(decl)) { return definition; } } return nullptr; } QString findSwitchCandidate(const QUrl& docUrl) { QMimeDatabase db; IBuddyDocumentFinder* finder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(docUrl).name()); if (finder) { // get the first entry that exists, use that as candidate foreach(const QUrl& buddyUrl, finder->potentialBuddies(docUrl)) { if (!QFile::exists(buddyUrl.toLocalFile())) { continue; } return buddyUrl.toLocalFile(); } } return QString(); } } K_PLUGIN_FACTORY_WITH_JSON(SwitchToBuddyPluginFactory, "kdevswitchtobuddy.json", registerPlugin(); ) SwitchToBuddyPlugin::SwitchToBuddyPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( QStringLiteral("kdevswitchtobuddy"), parent ) { setXMLFile(QStringLiteral("kdevswitchtobuddy.rc")); } SwitchToBuddyPlugin::~SwitchToBuddyPlugin() { } ContextMenuExtension SwitchToBuddyPlugin::contextMenuExtension(Context* context, QWidget* parent) { EditorContext* ctx = dynamic_cast(context); if (!ctx) { return ContextMenuExtension(); } QUrl currentUrl = ctx->url(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(QMimeDatabase().mimeTypeForUrl(currentUrl).name()); if (!buddyFinder) return ContextMenuExtension(); // Get all potential buddies for the current document and add a switch-to action // for each buddy who really exists in the file system. Note: if no buddies could be calculated // no extension actions are generated. const QVector& potentialBuddies = buddyFinder->potentialBuddies(currentUrl); ContextMenuExtension extension; for (const QUrl& url : potentialBuddies) { if (!QFile::exists(url.toLocalFile())) { continue; } QAction* action = new QAction(i18n("Switch to '%1'", url.fileName()), parent); const QString surl = url.toLocalFile(); connect(action, &QAction::triggered, this, [this, surl](){ switchToBuddy(surl); }, Qt::QueuedConnection); extension.addAction(ContextMenuExtension::NavigationGroup, action); } return extension; } void SwitchToBuddyPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = this->xmlFile(); QAction* switchDefinitionDeclaration = actions.addAction(QStringLiteral("switch_definition_declaration")); switchDefinitionDeclaration->setText(i18n("&Switch Definition/Declaration")); actions.setDefaultShortcut(switchDefinitionDeclaration, Qt::CTRL | Qt::SHIFT | Qt::Key_C); connect(switchDefinitionDeclaration, &QAction::triggered, this, &SwitchToBuddyPlugin::switchDefinitionDeclaration); QAction* switchHeaderSource = actions.addAction(QStringLiteral("switch_header_source")); switchHeaderSource->setText(i18n("Switch Header/Source")); actions.setDefaultShortcut(switchHeaderSource, Qt::CTRL | Qt::Key_Slash); connect(switchHeaderSource, &QAction::triggered, this, &SwitchToBuddyPlugin::switchHeaderSource); } void SwitchToBuddyPlugin::switchHeaderSource() { qCDebug(PLUGIN_SWITCHTOBUDDY) << "switching header/source"; auto doc = ICore::self()->documentController()->activeDocument(); if (!doc) return; QString buddyUrl = findSwitchCandidate(doc->url()); if (!buddyUrl.isEmpty()) switchToBuddy(buddyUrl); } void SwitchToBuddyPlugin::switchToBuddy(const QString& url) { KDevelop::ICore::self()->documentController()->openDocument(QUrl::fromLocalFile(url)); } void SwitchToBuddyPlugin::switchDefinitionDeclaration() { qCDebug(PLUGIN_SWITCHTOBUDDY) << "switching definition/declaration"; QUrl docUrl; KTextEditor::Cursor cursor; ///Step 1: Find the current top-level context of type DUContext::Other(the highest code-context). ///-- If it belongs to a function-declaration or definition, it can be retrieved through owner(), and we are in a definition. ///-- If no such context could be found, search for a declaration on the same line as the cursor, and switch to the according definition { auto view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "No active document"; return; } docUrl = view->document()->url(); cursor = view->cursorPosition(); } QString switchCandidate = findSwitchCandidate(docUrl); if(!switchCandidate.isEmpty()) { DUChainReadLocker lock; //If the file has not been parsed yet, update it TopDUContext* ctx = DUChainUtils::standardContextForUrl(docUrl); //At least 'VisibleDeclarationsAndContexts' is required so we can do a switch if (!ctx || (ctx->parsingEnvironmentFile() && !ctx->parsingEnvironmentFile()->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses))) { lock.unlock(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "Parsing switch-candidate before switching" << switchCandidate; ReferencedTopDUContext updatedContext = DUChain::self()->waitForUpdate(IndexedString(switchCandidate), TopDUContext::AllDeclarationsContextsAndUses); if (!updatedContext) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Failed to update document:" << switchCandidate; return; } } } qCDebug(PLUGIN_SWITCHTOBUDDY) << "Document:" << docUrl; DUChainReadLocker lock; TopDUContext* standardCtx = DUChainUtils::standardContextForUrl(docUrl); bool wasSignal = false; if (standardCtx) { Declaration* definition = nullptr; DUContext* ctx = standardCtx->findContext(standardCtx->transformToLocalRevision(cursor)); if (!ctx) { ctx = standardCtx; } while (ctx && ctx->parentContext() && (ctx->parentContext()->type() == DUContext::Other || ctx->parentContext()->type() == DUContext::Function)) { ctx = ctx->parentContext(); } if (ctx && ctx->owner() && (ctx->type() == DUContext::Other || ctx->type() == DUContext::Function) && ctx->owner()->isDefinition()) { definition = ctx->owner(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition while traversing:" << definition->toString(); } if (!definition && ctx) { definition = DUChainUtils::declarationInLine(cursor, ctx); } if (ClassFunctionDeclaration* cDef = dynamic_cast(definition)) { if (cDef->isSignal()) { qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition is a signal, not switching to .moc implementation"; definition = nullptr; wasSignal = true; } } FunctionDefinition* def = dynamic_cast(definition); if (def && def->declaration()) { Declaration* declaration = def->declaration(); KTextEditor::Range targetRange = declaration->rangeInCurrentRevision(); const auto url = declaration->url().toUrl(); qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition that has declaration: " << definition->toString() << "range" << targetRange << "url" << url; lock.unlock(); auto view = ICore::self()->documentController()->activeTextDocumentView(); if (view && !targetRange.contains(view->cursorPosition())) { const auto pos = normalizeCursor(targetRange.start()); ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); } else { ICore::self()->documentController()->openDocument(url); } return; } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Definition has no assigned declaration"; } qCDebug(PLUGIN_SWITCHTOBUDDY) << "Could not get definition/declaration from context"; } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Got no context for the current document"; } Declaration* def = nullptr; if (!wasSignal) { def = definitionForCursorDeclaration(cursor, docUrl); } if (def) { const auto url = def->url().toUrl(); KTextEditor::Range targetRange = def->rangeInCurrentRevision(); if (def->internalContext()) { targetRange.end() = def->internalContext()->rangeInCurrentRevision().end(); } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Declaration does not have internal context"; } lock.unlock(); auto view = ICore::self()->documentController()->activeTextDocumentView(); if (view && !targetRange.contains(view->cursorPosition())) { KTextEditor::Cursor pos(normalizeCursor(targetRange.start())); ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); } else { //The cursor is already in the target range, only open the document ICore::self()->documentController()->openDocument(url); } return; } else if (!wasSignal) { qCWarning(PLUGIN_SWITCHTOBUDDY) << "Found no definition assigned to cursor position"; } lock.unlock(); ///- If no definition/declaration could be found to switch to, just switch the document using normal header/source heuristic by file-extension if (!switchCandidate.isEmpty()) { ICore::self()->documentController()->openDocument(QUrl::fromUserInput(switchCandidate)); } else { qCDebug(PLUGIN_SWITCHTOBUDDY) << "Found no source/header candidate to switch"; } } #include "switchtobuddyplugin.moc"