diff --git a/app/main.cpp b/app/main.cpp --- a/app/main.cpp +++ b/app/main.cpp @@ -29,9 +29,10 @@ #include +#include "urlinfo.h" + #include #include - #include #include #include @@ -81,7 +82,30 @@ using namespace KDevelop; -class KDevelopApplication: +namespace { + +QString serializeOpenFilesMessage(const QVector &infos) +{ + QByteArray message; + QDataStream stream(&message, QIODevice::WriteOnly); + stream << QByteArrayLiteral("open"); + stream << infos; + return QString::fromLatin1(message.toHex()); +} + +void openFiles(const QVector& infos) +{ + foreach (const UrlInfo& file, infos) { + qWarning() << "FILE" << file.url << file.cursor; + if (!ICore::self()->documentController()->openDocument(file.url, file.cursor)) { + qWarning() << i18n("Could not open %1", file.url.toDisplayString(QUrl::PreferLocalFile)); + } + } +} + +} + +class KDevelopApplication: #if KDEVELOP_SINGLE_APP public SharedTools::QtSingleApplication #else @@ -96,9 +120,41 @@ : QApplication(argc, argv, GUIenabled) #endif { +#if KDEVELOP_SINGLE_APP + Q_UNUSED(GUIenabled); +#endif + connect(this, &QGuiApplication::saveStateRequest, this, &KDevelopApplication::saveState); } +#if KDEVELOP_SINGLE_APP +public Q_SLOTS: + void remoteArguments(const QString &message, QObject *socket) + { + Q_UNUSED(socket); + + QByteArray ba = QByteArray::fromHex(message.toLatin1()); + QDataStream stream(ba); + QByteArray command; + stream >> command; + + qCDebug(APP) << "Received remote command: " << command; + + if (command == "open") { + QVector infos; + stream >> infos; + openFiles(infos); + } else { + qCWarning(APP) << "Unknown remote command: " << command; + } + } + + void fileOpenRequested(const QString &file) + { + openFiles({UrlInfo(file)}); + } +#end + private Q_SLOTS: void saveState( QSessionManager& sm ) { if (KDevelop::Core::self() && KDevelop::Core::self()->sessionController()) { @@ -129,39 +185,6 @@ return 0; } - -// Represents a file to be opened, consisting of its URL and the cursor to jump to. -struct UrlInfo -{ - // Parses a file path argument and determines its line number and column and full path - UrlInfo(QString path = {}) - : cursor(KTextEditor::Cursor::invalid()) - { - if (!path.startsWith(QLatin1Char('/')) && QFileInfo(path).isRelative()) { - path = QDir::currentPath() + QLatin1Char('/') + path; - } - url = QUrl::fromUserInput(path); - - if (url.isLocalFile() && !QFile::exists(path)) { - // Allow opening specific lines in documents, like mydoc.cpp:10 - // also supports columns, i.e. mydoc.cpp:10:42 - static const QRegularExpression pattern(QStringLiteral(":(\\d+)(?::(\\d+))?$")); - const auto match = pattern.match(path); - if (match.isValid()) { - path.chop(match.capturedLength()); - int line = match.captured(1).toInt() - 1; - // don't use an invalid column when the line is valid - int column = qMax(0, match.captured(2).toInt() - 1); - url = QUrl::fromLocalFile(path); - cursor = {line, column}; - } - } - } - - QUrl url; - KTextEditor::Cursor cursor; -}; - /// 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) @@ -463,23 +486,35 @@ foreach (const QString &file, parser.positionalArguments()) { initialFiles.append(UrlInfo(file)); } - if ( ! initialFiles.isEmpty() && ! parser.isSet("new-session") ) - { - qint64 pid = -1; - if (parser.isSet("open-session")) { - const QString session = findSessionId(parser.value("open-session")); - if (session.isEmpty()) { - return 1; - } else if (KDevelop::SessionController::isSessionRunning(session)) { - pid = findSessionPid(session); + + if (!parser.isSet("new-session")) { +#if KDEVELOP_SINGLE_APP + if (app.isRunning()) { + bool success = app.sendMessage(serializeOpenFilesMessage(initialFiles)); + if (success) { + return 0; } - } else { - pid = getRunningSessionPid(); } - if ( pid > 0 ) { - return openFilesInRunningInstance(initialFiles, pid); +#endif + + if ( ! initialFiles.isEmpty() ) + { + qint64 pid = -1; + if (parser.isSet("open-session")) { + const QString session = findSessionId(parser.value("open-session")); + if (session.isEmpty()) { + return 1; + } else if (KDevelop::SessionController::isSessionRunning(session)) { + pid = findSessionPid(session); + } + } else { + pid = getRunningSessionPid(); + } + if ( pid > 0 ) { + return openFilesInRunningInstance(initialFiles, pid); + } + // else there are no running sessions, and the generated list of files will be opened below. } - // else there are no running sessions, and the generated list of files will be opened below. } // if empty, restart kdevelop with last active session, see SessionController::defaultSessionId @@ -700,13 +735,19 @@ core->runControllerInternal()->execute("debug", launch); } else { - foreach ( const UrlInfo& file, initialFiles ) { - if(!core->documentController()->openDocument(file.url, file.cursor)) { - qWarning() << i18n("Could not open %1", file.url.toDisplayString(QUrl::PreferLocalFile)); - } - } + openFiles(initialFiles); } +#if KDEVELOP_SINGLE_APP + // Set up remote arguments. + QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived, + &app, &KDevelopApplication::remoteArguments); + + QObject::connect(&app, &SharedTools::QtSingleApplication::fileOpenRequest, + &app, &KDevelopApplication::fileOpenRequested); +#endif + + #ifdef WITH_WELCOMEPAGE // make it possible to disable the welcome page, useful for valgrind runs e.g. if (!qEnvironmentVariableIsSet("KDEV_DISABLE_WELCOMEPAGE")) { diff --git a/app/urlinfo.h b/app/urlinfo.h new file mode 100644 --- /dev/null +++ b/app/urlinfo.h @@ -0,0 +1,127 @@ +/* This file is part of the KDE project + Copyright (C) 2015 Milian Wolff + + 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 URLINFO_H +#define URLINFO_H + +#include + +#include +#include +#include +#include +#include + +/** + * Represents a file to be opened, consisting of its URL and the cursor to jump to. + */ +class UrlInfo +{ +public: + /** + * Parses a file path argument and determines its line number and column and full path + * @param path path passed on e.g. command line to parse into an URL + */ + UrlInfo(QString path = QString()) + : cursor(KTextEditor::Cursor::invalid()) + { + /** + * construct url: + * - make relative paths absolute using the current working directory + * - prefer local file, if in doubt! + */ + url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); + + /** + * in some cases, this will fail, e.g. if you have line/column specs like test.c:10:1 + * => fallback: assume a local file and just convert it to an url + */ + if (!url.isValid()) { + /** + * create absolute file path, we will e.g. pass this over dbus to other processes + */ + url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(path)); + } + + /** + * Allow opening specific lines in documents, like mydoc.cpp:10 + * also supports columns, i.e. mydoc.cpp:10:42 + * ignores trailing colons, as compile errors often use that format + */ + if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) { + /** + * update path from url, might have been file://... + */ + path = url.toLocalFile(); + + /** + * try to match the line/colum spec, else we are done here + */ + const auto match = QRegularExpression(QStringLiteral(":(\\d+)(?::(\\d+))?:?$")).match(path); + if (!match.isValid()) + return; + + /** + * cut away the line/column specification from the path and update the url + */ + path.chop(match.capturedLength()); + url = QUrl::fromLocalFile(path); + + /** + * set right cursor position + */ + int line = match.captured(1).toInt() - 1; + // don't use an invalid column when the line is valid + int column = qMax(0, match.captured(2).toInt() - 1); + cursor = {line, column}; + } + } + + /** + * url computed out of the passed path + */ + QUrl url; + + /** + * initial cursor position, if any found inside the path as line/colum specification at the end + */ + KTextEditor::Cursor cursor; +}; + +QDataStream& operator<<(QDataStream& stream, const UrlInfo& info) +{ + stream << info.url; + stream << info.cursor.line(); + stream << info.cursor.column(); + return stream; +} + +QDataStream& operator>>(QDataStream& stream, UrlInfo& info) +{ + stream >> info.url; + int line, column; + stream >> line; + stream >> column; + info.cursor.setLine(line); + info.cursor.setColumn(column); + return stream; +} + +#endif // URLINFO_H