diff options
author | David Schulz <david.schulz@qt.io> | 2019-03-15 11:25:48 +0100 |
---|---|---|
committer | David Schulz <david.schulz@qt.io> | 2019-04-29 09:52:02 +0000 |
commit | 0ea840c2bda03251318263aa96c5b1bc9b4a94e1 (patch) | |
tree | d6c33d6456c89da2fffbfad81fcc0d4a3ecd4ecf /src/plugins/languageclient | |
parent | 132d813343bd8dece5a39ed86db845f10f9dce47 (diff) |
LanguageClient: add option to start client per project
Some server like rust or haskell require a project at server startup.
Add option that postpones the server start until we can assign a project
to an open file.
Change-Id: Iaa475289e833b27a730a90ea228f5d85ecc30338
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Diffstat (limited to 'src/plugins/languageclient')
-rw-r--r-- | src/plugins/languageclient/client.cpp | 26 | ||||
-rw-r--r-- | src/plugins/languageclient/client.h | 3 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientmanager.cpp | 106 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientmanager.h | 7 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientsettings.cpp | 95 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientsettings.h | 20 |
6 files changed, 199 insertions, 58 deletions
diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index dd544f37e9f..c84bc048666 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -215,10 +215,10 @@ void Client::initialize() QTC_ASSERT(m_state == Uninitialized, return); qCDebug(LOGLSPCLIENT) << "initializing language server " << m_displayName; auto initRequest = new InitializeRequest(); - if (auto startupProject = SessionManager::startupProject()) { + if (m_project) { auto params = initRequest->params().value_or(InitializeParams()); params.setCapabilities(generateClientCapabilities()); - params.setRootUri(DocumentUri::fromFileName(startupProject->projectDirectory())); + params.setRootUri(DocumentUri::fromFileName(m_project->projectDirectory())); initRequest->setParams(params); params.setWorkSpaceFolders(Utils::transform(SessionManager::projects(), [](Project *pro){ return WorkSpaceFolder(pro->projectDirectory().toString(), pro->displayName()); @@ -730,6 +730,16 @@ void Client::executeCommand(const Command &command) sendContent(request); } +const ProjectExplorer::Project *Client::project() const +{ + return m_project; +} + +void Client::setCurrentProject(ProjectExplorer::Project *project) +{ + m_project = project; +} + void Client::projectOpened(ProjectExplorer::Project *project) { if (!sendWorkspceFolderChanges()) @@ -744,10 +754,19 @@ void Client::projectOpened(ProjectExplorer::Project *project) void Client::projectClosed(ProjectExplorer::Project *project) { + if (project == m_project) { + if (m_state == Initialized) { + shutdown(); + } else { + m_state = Shutdown; // otherwise the manager would try to restart this server + emit finished(); + } + } if (!sendWorkspceFolderChanges()) return; WorkspaceFoldersChangeEvent event; - event.setRemoved({WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())}); + event.setRemoved( + {WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())}); DidChangeWorkspaceFoldersParams params; params.setEvent(event); DidChangeWorkspaceFoldersNotification change(params); @@ -811,6 +830,7 @@ bool Client::reset() m_openedDocument.clear(); m_serverCapabilities = ServerCapabilities(); m_dynamicCapabilities.reset(); + m_project = nullptr; for (const DocumentUri &uri : m_diagnostics.keys()) removeDiagnostics(uri); return true; diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index f2f92cbfe6a..9769d4d664c 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -112,6 +112,8 @@ public: void executeCommand(const LanguageServerProtocol::Command &command); // workspace control + void setCurrentProject(ProjectExplorer::Project *project); + const ProjectExplorer::Project *project() const; void projectOpened(ProjectExplorer::Project *project); void projectClosed(ProjectExplorer::Project *project); @@ -200,6 +202,7 @@ private: QScopedPointer<BaseClientInterface> m_clientInterface; QMap<LanguageServerProtocol::DocumentUri, QList<TextMark *>> m_diagnostics; DocumentSymbolCache m_documentSymbolCache; + const ProjectExplorer::Project *m_project = nullptr; }; } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index 6f3249b9ec0..75fae98545b 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -112,15 +112,16 @@ void LanguageClientManager::startClient(Client *client) managerInstance->clientFinished(client); } -void LanguageClientManager::startClient(BaseSettings *setting) +void LanguageClientManager::startClient(BaseSettings *setting, ProjectExplorer::Project *project) { QTC_ASSERT(managerInstance, return); QTC_ASSERT(setting, return); QTC_ASSERT(setting->isValid(), return); Client *client = setting->createClient(); QTC_ASSERT(client, return); + client->setCurrentProject(project); startClient(client); - managerInstance->m_clientsForSetting[setting->m_id] = client; + managerInstance->m_clientsForSetting[setting->m_id].append(QPointer<Client>(client)); } QVector<Client *> LanguageClientManager::clients() @@ -207,14 +208,42 @@ void LanguageClientManager::applySettings() }); for (BaseSettings *setting : restarts) { - if (auto client = clientForSetting(setting)) { + for (const QPointer<Client> &client : clientForSetting(setting)) { if (client->reachable()) client->shutdown(); else deleteClient(client); } - if (setting->canStartClient()) + if (!setting->isValid() || !setting->m_enabled) + continue; + switch (setting->m_startBehavior) { + case BaseSettings::AlwaysOn: startClient(setting); + break; + case BaseSettings::RequiresFile: + if (Utils::anyOf(Core::DocumentModel::openedDocuments(), + [filter = setting->m_languageFilter](Core::IDocument *doc) { + return filter.isSupported(doc); + })) { + startClient(setting); + } + break; + case BaseSettings::RequiresProject: { + for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) { + if (setting->m_languageFilter.isSupported(doc)) { + const Utils::FileName filePath = doc->filePath(); + for (ProjectExplorer::Project *project : + ProjectExplorer::SessionManager::projects()) { + if (project->isKnownFile(filePath)) + startClient(setting, project); + } + } + } + break; + } + default: + break; + } } } @@ -224,10 +253,26 @@ QList<BaseSettings *> LanguageClientManager::currentSettings() return managerInstance->m_currentSettings; } -Client *LanguageClientManager::clientForSetting(const BaseSettings *setting) +QVector<QPointer<Client>> LanguageClientManager::clientForSetting(const BaseSettings *setting) +{ + QTC_ASSERT(managerInstance, return {}); + return managerInstance->m_clientsForSetting.value(setting->m_id); +} + +const BaseSettings *LanguageClientManager::settingForClient(Client *client) { QTC_ASSERT(managerInstance, return nullptr); - return managerInstance->m_clientsForSetting.value(setting->m_id, nullptr); + for (const QString &id : managerInstance->m_clientsForSetting.keys()) { + for (const QPointer<Client> &settingClient : managerInstance->m_clientsForSetting[id]) { + if (settingClient == client) { + return Utils::findOrDefault(managerInstance->m_currentSettings, + [id](BaseSettings *setting) { + return setting->m_id == id; + }); + } + } + } + return nullptr; } QVector<Client *> LanguageClientManager::reachableClients() @@ -255,7 +300,7 @@ void LanguageClientManager::clientFinished(Client *client) client->disconnect(this); client->log(tr("Unexpectedly finished. Restarting in %1 seconds.").arg(restartTimeoutS), Core::MessageManager::Flash); - QTimer::singleShot(restartTimeoutS * 1000, client, [client](){ startClient(client); }); + QTimer::singleShot(restartTimeoutS * 1000, client, [client]() { startClient(client); }); } else { if (unexpectedFinish && !m_shuttingDown) client->log(tr("Unexpectedly finished."), Core::MessageManager::Flash); @@ -299,9 +344,32 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor) void LanguageClientManager::documentOpened(Core::IDocument *document) { + // check whether we have to start servers for this document for (BaseSettings *setting : LanguageClientSettings::currentPageSettings()) { - if (clientForSetting(setting) == nullptr && setting->canStartClient()) - startClient(setting); + const QVector<QPointer<Client>> clients = clientForSetting(setting); + if (setting->isValid() && setting->m_enabled + && setting->m_languageFilter.isSupported(document)) { + if (setting->m_startBehavior == BaseSettings::RequiresProject) { + const Utils::FileName filePath = document->filePath(); + for (ProjectExplorer::Project *project : + ProjectExplorer::SessionManager::projects()) { + // check whether file is part of this project + if (!project->isKnownFile(filePath)) + continue; + + // check whether we already have a client running for this project + if (Utils::findOrDefault(clients, + [project](QPointer<Client> client) { + return client->project() == project; + })) { + continue; + } + startClient(setting, project); + } + } else if (setting->m_startBehavior == BaseSettings::RequiresFile && clients.isEmpty()) { + startClient(setting); + } + } } for (Client *interface : reachableClients()) interface->openDocument(document); @@ -427,13 +495,31 @@ void LanguageClientManager::findUsages(const Utils::FileName &filePath, const QT void LanguageClientManager::projectAdded(ProjectExplorer::Project *project) { + for (BaseSettings *setting : m_currentSettings) { + if (setting->isValid() + && setting->m_enabled + && setting->m_startBehavior == BaseSettings::RequiresProject) { + if (Utils::findOrDefault(clientForSetting(setting), + [project](QPointer<Client> client) { + return client->project() == project; + }) + .isNull()) { + for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) { + if (setting->m_languageFilter.isSupported(doc)) { + if (project->isKnownFile(doc->filePath())) + startClient(setting, project); + } + } + } + } + } for (Client *interface : reachableClients()) interface->projectOpened(project); } void LanguageClientManager::projectRemoved(ProjectExplorer::Project *project) { - for (Client *interface : reachableClients()) + for (Client *interface : m_clients) interface->projectClosed(project); } diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index 93273e1595a..0252c996eeb 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -56,7 +56,7 @@ public: static void init(); static void startClient(Client *client); - static void startClient(BaseSettings *setting); + static void startClient(BaseSettings *setting, ProjectExplorer::Project *project = nullptr); static QVector<Client *> clients(); static void addExclusiveRequest(const LanguageServerProtocol::MessageId &id, Client *client); @@ -72,7 +72,8 @@ public: static void applySettings(); static QList<BaseSettings *> currentSettings(); - static Client *clientForSetting(const BaseSettings *setting); + static QVector<QPointer<Client> > clientForSetting(const BaseSettings *setting); + static const BaseSettings *settingForClient(Client *setting); signals: void shutdownFinished(); @@ -100,7 +101,7 @@ private: bool m_shuttingDown = false; QVector<Client *> m_clients; QList<BaseSettings *> m_currentSettings; // owned - QMap<QString, QPointer<Client>> m_clientsForSetting; + QMap<QString, QVector<QPointer<Client>>> m_clientsForSetting; QHash<LanguageServerProtocol::MessageId, QList<Client *>> m_exclusiveRequests; }; } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientsettings.cpp b/src/plugins/languageclient/languageclientsettings.cpp index f39ccade062..77702484eee 100644 --- a/src/plugins/languageclient/languageclientsettings.cpp +++ b/src/plugins/languageclient/languageclientsettings.cpp @@ -34,6 +34,8 @@ #include <coreplugin/icore.h> #include <coreplugin/idocument.h> #include <coreplugin/variablechooser.h> +#include <projectexplorer/project.h> +#include <projectexplorer/session.h> #include <utils/algorithm.h> #include <utils/delegates.h> #include <utils/fancylineedit.h> @@ -41,7 +43,6 @@ #include <utils/jsontreeitem.h> #include <QBoxLayout> -#include <QCheckBox> #include <QComboBox> #include <QCompleter> #include <QCoreApplication> @@ -61,7 +62,7 @@ constexpr char nameKey[] = "name"; constexpr char idKey[] = "id"; constexpr char enabledKey[] = "enabled"; -constexpr char alwaysOnKey[] = "alwaysOn"; +constexpr char startupBehaviorKey[] = "startupBehavior"; constexpr char mimeTypeKey[] = "mimeType"; constexpr char filePatternKey[] = "filePattern"; constexpr char executableKey[] = "executable"; @@ -261,7 +262,7 @@ void LanguageClientSettingsPage::apply() LanguageClientManager::applySettings(); for (BaseSettings *setting : m_model.removed()) { - if (Client *client = LanguageClientManager::clientForSetting(setting)) { + for (Client *client : LanguageClientManager::clientForSetting(setting)) { if (client->reachable()) client->shutdown(); else @@ -374,7 +375,7 @@ void BaseSettings::applyFromSettingsWidget(QWidget *widget) if (auto settingsWidget = qobject_cast<BaseSettingsWidget *>(widget)) { m_name = settingsWidget->name(); m_languageFilter = settingsWidget->filter(); - m_alwaysOn = settingsWidget->alwaysOn(); + m_startBehavior = settingsWidget->startupBehavior(); } } @@ -385,18 +386,14 @@ QWidget *BaseSettings::createSettingsWidget(QWidget *parent) const bool BaseSettings::needsRestart() const { - Client *client = LanguageClientManager::clientForSetting(this); - return client ? !m_enabled || client->needsRestart(this) : m_enabled; -} - -bool BaseSettings::canStartClient() const -{ - using namespace Core; - if (!isValid() || !m_enabled) - return false; - return m_alwaysOn || Utils::anyOf(DocumentModel::openedDocuments(), [this](IDocument *doc) { - return m_languageFilter.isSupported(doc); - }); + const QVector<QPointer<Client>> clients = LanguageClientManager::clientForSetting(this); + if (clients.isEmpty()) + return m_enabled; + if (!m_enabled) + return true; + return Utils::anyOf(clients, [this](const QPointer<Client> &client) { + return client->needsRestart(this); + }); } bool BaseSettings::isValid() const @@ -422,7 +419,7 @@ QVariantMap BaseSettings::toMap() const map.insert(nameKey, m_name); map.insert(idKey, m_id); map.insert(enabledKey, m_enabled); - map.insert(alwaysOnKey, m_alwaysOn); + map.insert(startupBehaviorKey, m_startBehavior); map.insert(mimeTypeKey, m_languageFilter.mimeTypes); map.insert(filePatternKey, m_languageFilter.filePattern); return map; @@ -433,7 +430,8 @@ void BaseSettings::fromMap(const QVariantMap &map) m_name = map[nameKey].toString(); m_id = map.value(idKey, QUuid::createUuid().toString()).toString(); m_enabled = map[enabledKey].toBool(); - m_alwaysOn = map.value(alwaysOnKey, false).toBool(); + m_startBehavior = BaseSettings::StartBehavior( + map.value(startupBehaviorKey, BaseSettings::RequiresFile).toInt()); m_languageFilter.mimeTypes = map[mimeTypeKey].toStringList(); m_languageFilter.filePattern = map[filePatternKey].toStringList(); } @@ -496,11 +494,13 @@ bool StdIOSettings::needsRestart() const { if (BaseSettings::needsRestart()) return true; - if (Client *client = LanguageClientManager::clientForSetting(this)) - if (auto stdIOInterface = qobject_cast<const StdIOClientInterface *>( - client->clientInterface())) - return stdIOInterface->needsRestart(this); - return false; + return Utils::anyOf(LanguageClientManager::clientForSetting(this), + [this](QPointer<Client> client) { + if (auto stdIOInterface = qobject_cast<const StdIOClientInterface *>( + client->clientInterface())) + return stdIOInterface->needsRestart(this); + return false; + }); } bool StdIOSettings::isValid() const @@ -533,10 +533,9 @@ BaseClientInterface *StdIOSettings::createInterface() const return new StdIOClientInterface(m_executable, arguments()); } -static QWidget *createCapabilitiesView( - const LanguageServerProtocol::ServerCapabilities &capabilities) +static QWidget *createCapabilitiesView(const QJsonValue &capabilities) { - auto root = new Utils::JsonTreeItem("Capabilities", QJsonValue(capabilities)); + auto root = new Utils::JsonTreeItem("Capabilities", capabilities); if (root->canFetchMore()) root->fetchMore(); @@ -551,12 +550,28 @@ static QWidget *createCapabilitiesView( return capabilitiesView; } +static QString startupBehaviorString(BaseSettings::StartBehavior behavior) +{ + switch (behavior) { + case BaseSettings::AlwaysOn: + return QCoreApplication::translate("LanguageClient::BaseSettings", "Always On"); + case BaseSettings::RequiresFile: + return QCoreApplication::translate("LanguageClient::BaseSettings", "Requires an Open File"); + case BaseSettings::RequiresProject: + return QCoreApplication::translate("LanguageClient::BaseSettings", + "Start Server per Project"); + default: + break; + } + return {}; +} + BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *parent) : QWidget(parent) , m_name(new QLineEdit(settings->m_name, this)) , m_mimeTypes(new QLabel(settings->m_languageFilter.mimeTypes.join(filterSeparator), this)) , m_filePattern(new QLineEdit(settings->m_languageFilter.filePattern.join(filterSeparator), this)) - , m_alwaysOn(new QCheckBox()) + , m_startupBehavior(new QComboBox) { int row = 0; auto *mainLayout = new QGridLayout; @@ -573,9 +588,12 @@ BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *pa mainLayout->addLayout(mimeLayout, row, 1); m_filePattern->setPlaceholderText(tr("File pattern")); mainLayout->addWidget(m_filePattern, ++row, 1); - mainLayout->addWidget(new QLabel(tr("Always On:")), ++row, 0); - mainLayout->addWidget(m_alwaysOn, row, 1); - m_alwaysOn->setChecked(settings->m_alwaysOn); + mainLayout->addWidget(new QLabel(tr("Startup Behavior:")), ++row, 0); + for (int behavior = 0; behavior < BaseSettings::LastSentinel ; ++behavior) + m_startupBehavior->addItem(startupBehaviorString(BaseSettings::StartBehavior(behavior))); + m_startupBehavior->setCurrentIndex(settings->m_startBehavior); + mainLayout->addWidget(m_startupBehavior, row, 1); + connect(addMimeTypeButton, &QPushButton::pressed, this, &BaseSettingsWidget::showAddMimeTypeDialog); @@ -585,9 +603,13 @@ BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *pa }; mainLayout->addWidget(new QLabel(tr("Capabilities:")), ++row, 0, Qt::AlignTop); - if (Client *client = LanguageClientManager::clientForSetting(settings)) { + QVector<QPointer<Client> > clients = LanguageClientManager::clientForSetting(settings); + if (clients.isEmpty()) { + mainLayout->addWidget(createInfoLabel()); + } else { // TODO move the capabilities view into a new widget outside of the settings + Client *client = clients.first(); if (client->state() == Client::Initialized) - mainLayout->addWidget(createCapabilitiesView(client->capabilities())); + mainLayout->addWidget(createCapabilitiesView(QJsonValue(client->capabilities()))); else mainLayout->addWidget(createInfoLabel(), row, 1); connect(client, &Client::finished, mainLayout, [mainLayout, row, createInfoLabel]() { @@ -598,12 +620,9 @@ BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *pa [mainLayout, row]( const LanguageServerProtocol::ServerCapabilities &capabilities) { delete mainLayout->itemAtPosition(row, 1)->widget(); - mainLayout->addWidget(createCapabilitiesView(capabilities), row, 1); + mainLayout->addWidget(createCapabilitiesView(QJsonValue(capabilities)), row, 1); }); - } else { - mainLayout->addWidget(createInfoLabel()); } - setLayout(mainLayout); } @@ -618,9 +637,9 @@ LanguageFilter BaseSettingsWidget::filter() const m_filePattern->text().split(filterSeparator)}; } -bool BaseSettingsWidget::alwaysOn() const +BaseSettings::StartBehavior BaseSettingsWidget::startupBehavior() const { - return m_alwaysOn->isChecked(); + return BaseSettings::StartBehavior(m_startupBehavior->currentIndex()); } class MimeTypeModel : public QStringListModel diff --git a/src/plugins/languageclient/languageclientsettings.h b/src/plugins/languageclient/languageclientsettings.h index ac6d2e23c0f..aed2bf585a4 100644 --- a/src/plugins/languageclient/languageclientsettings.h +++ b/src/plugins/languageclient/languageclientsettings.h @@ -34,7 +34,7 @@ #include <QWidget> QT_BEGIN_NAMESPACE -class QCheckBox; +class QComboBox; class QLineEdit; QT_END_NAMESPACE @@ -44,6 +44,7 @@ class PathChooser; } // namespace Utils namespace Core { class IDocument; } +namespace ProjectExplorer { class Project; } namespace LanguageClient { @@ -67,17 +68,23 @@ public: virtual ~BaseSettings() = default; + enum StartBehavior { + AlwaysOn = 0, + RequiresFile, + RequiresProject, + LastSentinel + }; + QString m_name = QString("New Language Server"); QString m_id = QUuid::createUuid().toString(); bool m_enabled = true; - bool m_alwaysOn = false; + StartBehavior m_startBehavior = RequiresFile; LanguageFilter m_languageFilter; virtual void applyFromSettingsWidget(QWidget *widget); virtual QWidget *createSettingsWidget(QWidget *parent = nullptr) const; virtual BaseSettings *copy() const { return new BaseSettings(*this); } virtual bool needsRestart() const; - virtual bool canStartClient() const; virtual bool isValid() const; Client *createClient(); virtual QVariantMap toMap() const; @@ -90,6 +97,9 @@ protected: BaseSettings(BaseSettings &&other) = default; BaseSettings &operator=(const BaseSettings &other) = default; BaseSettings &operator=(BaseSettings &&other) = default; + +private: + bool canStart(QList<const Core::IDocument *> documents) const; }; class StdIOSettings : public BaseSettings @@ -137,7 +147,9 @@ public: QString name() const; LanguageFilter filter() const; + BaseSettings::StartBehavior startupBehavior() const; bool alwaysOn() const; + bool requiresProject() const; private: void showAddMimeTypeDialog(); @@ -145,7 +157,7 @@ private: QLineEdit *m_name = nullptr; QLabel *m_mimeTypes = nullptr; QLineEdit *m_filePattern = nullptr; - QCheckBox *m_alwaysOn = nullptr; + QComboBox *m_startupBehavior = nullptr; static constexpr char filterSeparator = ';'; }; |