aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/coreplugin/editormanager/editormanager.cpp8
-rw-r--r--src/plugins/coreplugin/editormanager/editormanager.h1
-rw-r--r--src/plugins/languageclient/LanguageClient.json.in20
-rw-r--r--src/plugins/languageclient/baseclient.cpp746
-rw-r--r--src/plugins/languageclient/baseclient.h190
-rw-r--r--src/plugins/languageclient/dynamiccapabilities.cpp65
-rw-r--r--src/plugins/languageclient/dynamiccapabilities.h80
-rw-r--r--src/plugins/languageclient/languageclient.pro20
-rw-r--r--src/plugins/languageclient/languageclient.qbs31
-rw-r--r--src/plugins/languageclient/languageclient_dependencies.pri10
-rw-r--r--src/plugins/languageclient/languageclient_global.h37
-rw-r--r--src/plugins/languageclient/languageclientcodeassist.cpp354
-rw-r--r--src/plugins/languageclient/languageclientcodeassist.h54
-rw-r--r--src/plugins/languageclient/languageclientmanager.cpp322
-rw-r--r--src/plugins/languageclient/languageclientmanager.h95
-rw-r--r--src/plugins/languageclient/languageclientplugin.cpp41
-rw-r--r--src/plugins/languageclient/languageclientplugin.h51
-rw-r--r--src/plugins/languageclient/languageclientsettings.cpp408
-rw-r--r--src/plugins/languageclient/languageclientsettings.h66
-rw-r--r--src/plugins/plugins.pro3
-rw-r--r--src/plugins/plugins.qbs1
-rw-r--r--src/plugins/pythoneditor/pythoneditor.cpp5
-rw-r--r--src/plugins/texteditor/codeassist/codeassistant.cpp7
-rw-r--r--src/plugins/texteditor/plaintexteditorfactory.cpp5
-rw-r--r--src/plugins/texteditor/textdocument.h1
-rw-r--r--src/plugins/texteditor/texteditor.cpp15
-rw-r--r--src/plugins/texteditor/texteditor.h4
27 files changed, 2630 insertions, 10 deletions
diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp
index f33143bc40..5c8f39747d 100644
--- a/src/plugins/coreplugin/editormanager/editormanager.cpp
+++ b/src/plugins/coreplugin/editormanager/editormanager.cpp
@@ -2166,8 +2166,10 @@ bool EditorManagerPrivate::saveDocument(IDocument *document)
success = DocumentManager::saveDocument(document);
}
- if (success)
+ if (success) {
addDocumentToRecentFiles(document);
+ emit m_instance->saved(document);
+ }
return success;
}
@@ -2200,8 +2202,10 @@ bool EditorManagerPrivate::saveDocumentAs(IDocument *document)
// a good way out either (also the undo stack would be lost). Perhaps the best is to
// re-think part of the editors design.
- if (success)
+ if (success) {
addDocumentToRecentFiles(document);
+ emit m_instance->saved(document);
+ }
updateActions();
return success;
diff --git a/src/plugins/coreplugin/editormanager/editormanager.h b/src/plugins/coreplugin/editormanager/editormanager.h
index 30023e2bc7..6cd9dd88a1 100644
--- a/src/plugins/coreplugin/editormanager/editormanager.h
+++ b/src/plugins/coreplugin/editormanager/editormanager.h
@@ -179,6 +179,7 @@ signals:
void findOnFileSystemRequest(const QString &path);
void openFileProperties(const Utils::FileName &path);
void aboutToSave(IDocument *document);
+ void saved(IDocument *document);
void autoSaved();
void currentEditorAboutToChange(Core::IEditor *editor);
diff --git a/src/plugins/languageclient/LanguageClient.json.in b/src/plugins/languageclient/LanguageClient.json.in
new file mode 100644
index 0000000000..c48a74c937
--- /dev/null
+++ b/src/plugins/languageclient/LanguageClient.json.in
@@ -0,0 +1,20 @@
+{
+ \"Name\" : \"LanguageClient\",
+ \"Version\" : \"$$QTCREATOR_VERSION\",
+ \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
+ \"Experimental\" : true,
+ \"Vendor\" : \"The Qt Company Ltd\",
+ \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
+ \"License\" : [ \"Commercial Usage\",
+ \"\",
+ \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
+ \"\",
+ \"GNU General Public License Usage\",
+ \"\",
+ \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\"
+ ],
+ \"Category\" : \"Other Languages\",
+ \"Description\" : \"Language Server Protocol client component. See https://microsoft.github.io/language-server-protocol/overview for an overview on Language Servers.\",
+ \"Url\" : \"http://www.qt.io\",
+ $$dependencyList
+}
diff --git a/src/plugins/languageclient/baseclient.cpp b/src/plugins/languageclient/baseclient.cpp
new file mode 100644
index 0000000000..3bac57b2b2
--- /dev/null
+++ b/src/plugins/languageclient/baseclient.cpp
@@ -0,0 +1,746 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "baseclient.h"
+#include "languageclientcodeassist.h"
+#include "languageclientmanager.h"
+
+#include <coreplugin/icore.h>
+#include <coreplugin/idocument.h>
+#include <coreplugin/messagemanager.h>
+#include <languageserverprotocol/diagnostics.h>
+#include <languageserverprotocol/languagefeatures.h>
+#include <languageserverprotocol/messages.h>
+#include <languageserverprotocol/workspace.h>
+#include <texteditor/semantichighlighter.h>
+#include <texteditor/textdocument.h>
+#include <texteditor/texteditor.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/session.h>
+#include <utils/mimetypes/mimedatabase.h>
+#include <utils/synchronousprocess.h>
+
+#include <QDebug>
+#include <QLoggingCategory>
+#include <QMessageBox>
+#include <QPointer>
+#include <QTextBlock>
+#include <QTextCursor>
+#include <QTextDocument>
+
+using namespace LanguageServerProtocol;
+using namespace Utils;
+
+namespace LanguageClient {
+
+static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client");
+static Q_LOGGING_CATEGORY(LOGLSPCLIENTV, "qtc.languageclient.messages");
+
+BaseClient::BaseClient()
+ : m_id(Core::Id::fromString(QUuid::createUuid().toString()))
+{
+ m_buffer.open(QIODevice::ReadWrite | QIODevice::Append);
+ m_contentHandler.insert(JsonRpcMessageHandler::jsonRpcMimeType(),
+ &JsonRpcMessageHandler::parseContent);
+}
+
+BaseClient::~BaseClient()
+{
+ m_buffer.close();
+}
+
+void BaseClient::initialize()
+{
+ using namespace ProjectExplorer;
+ QTC_ASSERT(m_state == Uninitialized, return);
+ qCDebug(LOGLSPCLIENT) << "initializing language server " << m_displayName;
+ auto initRequest = new InitializeRequest();
+ if (auto startupProject = SessionManager::startupProject()) {
+ auto params = initRequest->params().value_or(InitializeParams());
+ params.setRootUri(DocumentUri::fromFileName(startupProject->projectDirectory()));
+ initRequest->setParams(params);
+ params.setWorkSpaceFolders(Utils::transform(SessionManager::projects(), [](Project *pro){
+ return WorkSpaceFolder(pro->projectDirectory().toString(), pro->displayName());
+ }));
+ }
+ initRequest->setResponseCallback([this](const InitializeResponse &initResponse){
+ intializeCallback(initResponse);
+ });
+ // directly send data otherwise the state check would fail;
+ initRequest->registerResponseHandler(&m_responseHandlers);
+ sendData(initRequest->toBaseMessage().toData());
+ m_state = InitializeRequested;
+}
+
+void BaseClient::shutdown()
+{
+ QTC_ASSERT(m_state == Initialized, emit finished(); return);
+ qCDebug(LOGLSPCLIENT) << "shutdown language server " << m_displayName;
+ ShutdownRequest shutdown;
+ shutdown.setResponseCallback([this](const ShutdownResponse &shutdownResponse){
+ shutDownCallback(shutdownResponse);
+ });
+ sendContent(shutdown);
+ m_state = ShutdownRequested;
+}
+
+BaseClient::State BaseClient::state() const
+{
+ return m_state;
+}
+
+void BaseClient::openDocument(Core::IDocument *document)
+{
+ using namespace TextEditor;
+ const QString languageId = TextDocumentItem::mimeTypeToLanguageId(document->mimeType());
+ if (!isSupportedLanguage(languageId))
+ return;
+ const FileName &filePath = document->filePath();
+ const QString method(DidOpenTextDocumentNotification::methodName);
+ if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
+ if (!registered.value())
+ return;
+ const TextDocumentRegistrationOptions option(
+ m_dynamicCapabilities.option(method).toObject());
+ if (option.isValid(nullptr)
+ && !option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType()))) {
+ return;
+ }
+ } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync
+ = m_serverCapabilities.textDocumentSync()) {
+ if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) {
+ if (!options->openClose().value_or(true))
+ return;
+ }
+ }
+ auto textDocument = qobject_cast<TextDocument *>(document);
+ TextDocumentItem item;
+ item.setLanguageId(languageId);
+ item.setUri(DocumentUri::fromFileName(filePath));
+ item.setText(QString::fromUtf8(document->contents()));
+ item.setVersion(textDocument ? textDocument->document()->revision() : 0);
+
+ connect(document, &Core::IDocument::contentsChanged, this,
+ [this, document](){
+ documentContentsChanged(document);
+ });
+ if (textDocument) {
+ textDocument->setCompletionAssistProvider(new LanguageClientCompletionAssistProvider(this));
+ if (BaseTextEditor *editor = BaseTextEditor::textEditorForDocument(textDocument)) {
+ if (TextEditorWidget *widget = editor->editorWidget()) {
+ connect(widget, &TextEditorWidget::cursorPositionChanged, this, [this, widget](){
+ cursorPositionChanged(widget);
+ });
+ }
+ }
+ }
+
+ m_openedDocument.append(document->filePath());
+ sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
+ if (textDocument)
+ requestDocumentSymbols(textDocument);
+}
+
+void BaseClient::sendContent(const IContent &content)
+{
+ QTC_ASSERT(m_state == Initialized, return);
+ content.registerResponseHandler(&m_responseHandlers);
+ QString error;
+ if (!QTC_GUARD(content.isValid(&error)))
+ Core::MessageManager::write(error);
+ sendData(content.toBaseMessage().toData());
+}
+
+void BaseClient::sendContent(const DocumentUri &uri, const IContent &content)
+{
+ if (!m_openedDocument.contains(uri.toFileName()))
+ return;
+ sendContent(content);
+}
+
+void BaseClient::cancelRequest(const MessageId &id)
+{
+ m_responseHandlers.remove(id);
+ sendContent(CancelRequest(CancelParameter(id)));
+}
+
+void BaseClient::closeDocument(const DidCloseTextDocumentParams &params)
+{
+ sendContent(params.textDocument().uri(), DidCloseTextDocumentNotification(params));
+}
+
+void BaseClient::documentContentsSaved(Core::IDocument *document)
+{
+ if (!m_openedDocument.contains(document->filePath()))
+ return;
+ bool sendMessage = true;
+ bool includeText = false;
+ const QString method(DidSaveTextDocumentNotification::methodName);
+ if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
+ sendMessage = registered.value();
+ if (sendMessage) {
+ const TextDocumentSaveRegistrationOptions option(
+ m_dynamicCapabilities.option(method).toObject());
+ if (option.isValid(nullptr)) {
+ sendMessage = option.filterApplies(document->filePath(),
+ Utils::mimeTypeForName(document->mimeType()));
+ includeText = option.includeText().value_or(includeText);
+ }
+ }
+ } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync
+ = m_serverCapabilities.textDocumentSync()) {
+ if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) {
+ if (Utils::optional<SaveOptions> saveOptions = options->save())
+ includeText = saveOptions.value().includeText().value_or(includeText);
+ }
+ }
+ if (!sendMessage)
+ return;
+ DidSaveTextDocumentParams params(
+ TextDocumentIdentifier(DocumentUri::fromFileName(document->filePath())));
+ if (includeText)
+ params.setText(QString::fromUtf8(document->contents()));
+ sendContent(DidSaveTextDocumentNotification(params));
+}
+
+void BaseClient::documentWillSave(Core::IDocument *document)
+{
+ const FileName &filePath = document->filePath();
+ if (!m_openedDocument.contains(filePath))
+ return;
+ bool sendMessage = true;
+ const QString method(WillSaveTextDocumentNotification::methodName);
+ if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
+ sendMessage = registered.value();
+ if (sendMessage) {
+ const TextDocumentRegistrationOptions option(m_dynamicCapabilities.option(method));
+ if (option.isValid(nullptr)) {
+ sendMessage = option.filterApplies(filePath,
+ Utils::mimeTypeForName(document->mimeType()));
+ }
+ }
+ } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync
+ = m_serverCapabilities.textDocumentSync()) {
+ if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value()))
+ sendMessage = options->willSave().value_or(sendMessage);
+ }
+ if (!sendMessage)
+ return;
+ const WillSaveTextDocumentParams params(
+ TextDocumentIdentifier(DocumentUri::fromFileName(document->filePath())));
+ sendContent(WillSaveTextDocumentNotification(params));
+}
+
+void BaseClient::documentContentsChanged(Core::IDocument *document)
+{
+ if (!m_openedDocument.contains(document->filePath()))
+ return;
+ const QString method(DidChangeTextDocumentNotification::methodName);
+ TextDocumentSyncKind syncKind = m_serverCapabilities.textDocumentSyncKindHelper();
+ if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
+ syncKind = registered.value() ? TextDocumentSyncKind::None : TextDocumentSyncKind::Full;
+ if (syncKind != TextDocumentSyncKind::None) {
+ const TextDocumentChangeRegistrationOptions option(
+ m_dynamicCapabilities.option(method).toObject());
+ syncKind = option.isValid(nullptr) ? option.syncKind() : syncKind;
+ }
+ }
+ auto textDocument = qobject_cast<TextEditor::TextDocument *>(document);
+ if (syncKind != TextDocumentSyncKind::None) {
+ const auto uri = DocumentUri::fromFileName(document->filePath());
+ VersionedTextDocumentIdentifier docId(uri);
+ docId.setVersion(textDocument ? textDocument->document()->revision() : 0);
+ const DidChangeTextDocumentParams params(docId, QString::fromUtf8(document->contents()));
+ sendContent(DidChangeTextDocumentNotification(params));
+ }
+ if (textDocument)
+ requestDocumentSymbols(textDocument);
+}
+
+void BaseClient::registerCapabilities(const QList<Registration> &registrations)
+{
+ m_dynamicCapabilities.registerCapability(registrations);
+}
+
+void BaseClient::unregisterCapabilities(const QList<Unregistration> &unregistrations)
+{
+ m_dynamicCapabilities.unregisterCapability(unregistrations);
+}
+
+bool BaseClient::findLinkAt(GotoDefinitionRequest &request)
+{
+ bool sendMessage = m_dynamicCapabilities.isRegistered(
+ GotoDefinitionRequest::methodName).value_or(false);
+ if (sendMessage) {
+ const TextDocumentRegistrationOptions option(
+ m_dynamicCapabilities.option(GotoDefinitionRequest::methodName));
+ if (option.isValid(nullptr))
+ sendMessage = option.filterApplies(Utils::FileName::fromString(QUrl(request.params()->textDocument().uri()).adjusted(QUrl::PreferLocalFile).toString()));
+ } else {
+ sendMessage = m_serverCapabilities.definitionProvider().value_or(sendMessage);
+ }
+ if (sendMessage)
+ sendContent(request);
+ return sendMessage;
+}
+
+TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation &info)
+{
+ if (!info.isValid(nullptr))
+ return {};
+ const Position &start = info.location().range().start();
+ return TextEditor::HighlightingResult(start.line() + 1, start.character() + 1,
+ info.name().length(), info.kind());
+}
+
+void BaseClient::requestDocumentSymbols(TextEditor::TextDocument *document)
+{
+ // TODO: Do not use this information for highlighting but the overview model
+ return;
+ const FileName &filePath = document->filePath();
+ bool sendMessage = m_dynamicCapabilities.isRegistered(DocumentSymbolsRequest::methodName).value_or(false);
+ if (sendMessage) {
+ const TextDocumentRegistrationOptions option(m_dynamicCapabilities.option(DocumentSymbolsRequest::methodName));
+ if (option.isValid(nullptr))
+ sendMessage = option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType()));
+ } else {
+ sendMessage = m_serverCapabilities.documentSymbolProvider().value_or(false);
+ }
+ if (!sendMessage)
+ return;
+ DocumentSymbolsRequest request(
+ DocumentSymbolParams(TextDocumentIdentifier(DocumentUri::fromFileName(filePath))));
+ request.setResponseCallback(
+ [doc = QPointer<TextEditor::TextDocument>(document)]
+ (Response<DocumentSymbolsResult, LanguageClientNull> response){
+ if (!doc)
+ return;
+ const DocumentSymbolsResult result = response.result().value_or(DocumentSymbolsResult());
+ if (!holds_alternative<QList<SymbolInformation>>(result))
+ return;
+ const auto &symbols = get<QList<SymbolInformation>>(result);
+
+ QFutureInterface<TextEditor::HighlightingResult> future;
+ for (const SymbolInformation &symbol : symbols)
+ future.reportResult(createHighlightingResult(symbol));
+
+ const TextEditor::FontSettings &fs = doc->fontSettings();
+ QHash<int, QTextCharFormat> formatMap;
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::File )]
+ = fs.toTextCharFormat(TextEditor::C_STRING);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Module )]
+ = fs.toTextCharFormat(TextEditor::C_STRING);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Namespace )]
+ = fs.toTextCharFormat(TextEditor::C_STRING);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Package )]
+ = fs.toTextCharFormat(TextEditor::C_STRING);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Class )]
+ = fs.toTextCharFormat(TextEditor::C_TYPE);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Method )]
+ = fs.toTextCharFormat(TextEditor::C_FUNCTION);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Property )]
+ = fs.toTextCharFormat(TextEditor::C_FIELD);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Field )]
+ = fs.toTextCharFormat(TextEditor::C_FIELD);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Constructor )]
+ = fs.toTextCharFormat(TextEditor::C_FUNCTION);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Enum )]
+ = fs.toTextCharFormat(TextEditor::C_TYPE);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Interface )]
+ = fs.toTextCharFormat(TextEditor::C_TYPE);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Function )]
+ = fs.toTextCharFormat(TextEditor::C_FUNCTION);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Variable )]
+ = fs.toTextCharFormat(TextEditor::C_LOCAL);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Constant )]
+ = fs.toTextCharFormat(TextEditor::C_LOCAL);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::String )]
+ = fs.toTextCharFormat(TextEditor::C_STRING);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Number )]
+ = fs.toTextCharFormat(TextEditor::C_NUMBER);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Boolean )]
+ = fs.toTextCharFormat(TextEditor::C_KEYWORD);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Array )]
+ = fs.toTextCharFormat(TextEditor::C_STRING);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Object )]
+ = fs.toTextCharFormat(TextEditor::C_LOCAL);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Key )]
+ = fs.toTextCharFormat(TextEditor::C_LOCAL);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Null )]
+ = fs.toTextCharFormat(TextEditor::C_KEYWORD);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::EnumMember )]
+ = fs.toTextCharFormat(TextEditor::C_ENUMERATION);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Struct )]
+ = fs.toTextCharFormat(TextEditor::C_TYPE);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Event )]
+ = fs.toTextCharFormat(TextEditor::C_STRING);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Operator )]
+ = fs.toTextCharFormat(TextEditor::C_OPERATOR);
+ formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::TypeParameter)]
+ = fs.toTextCharFormat(TextEditor::C_LOCAL);
+
+ TextEditor::SemanticHighlighter::incrementalApplyExtraAdditionalFormats(
+ doc->syntaxHighlighter(), future.future(), 0, future.resultCount() - 1,
+ formatMap);
+ });
+ sendContent(request);
+}
+
+void BaseClient::cursorPositionChanged(TextEditor::TextEditorWidget *widget)
+{
+ const auto uri = DocumentUri::fromFileName(widget->textDocument()->filePath());
+ if (m_dynamicCapabilities.isRegistered(DocumentHighlightsRequest::methodName).value_or(false)) {
+ TextDocumentRegistrationOptions option(
+ m_dynamicCapabilities.option(DocumentHighlightsRequest::methodName));
+ if (!option.filterApplies(widget->textDocument()->filePath()))
+ return;
+ } else if (!m_serverCapabilities.documentHighlightProvider().value_or(false)) {
+ return;
+ }
+
+ auto runningRequest = m_highlightRequests.find(uri);
+ if (runningRequest != m_highlightRequests.end())
+ cancelRequest(runningRequest.value());
+
+ DocumentHighlightsRequest request(TextDocumentPositionParams(uri, widget->textCursor()));
+ request.setResponseCallback(
+ [widget = QPointer<TextEditor::TextEditorWidget>(widget), this, uri]
+ (Response<DocumentHighlightsResult, LanguageClientNull> response)
+ {
+ m_highlightRequests.remove(uri);
+ if (!widget)
+ return;
+
+ QList<QTextEdit::ExtraSelection> selections;
+ const DocumentHighlightsResult result = response.result().value_or(DocumentHighlightsResult());
+ if (!holds_alternative<QList<DocumentHighlight>>(result)) {
+ widget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections);
+ return;
+ }
+
+ const QTextCharFormat &format =
+ widget->textDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES);
+ QTextDocument *document = widget->document();
+ for (const auto &highlight : get<QList<DocumentHighlight>>(result)) {
+ QTextEdit::ExtraSelection selection{widget->textCursor(), format};
+ const int &start = highlight.range().start().toPositionInDocument(document);
+ const int &end = highlight.range().end().toPositionInDocument(document);
+ if (start < 0 || end < 0)
+ continue;
+ selection.cursor.setPosition(start);
+ selection.cursor.setPosition(end, QTextCursor::KeepAnchor);
+ selections << selection;
+ }
+ widget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections);
+ });
+ m_highlightRequests[uri] = request.id();
+ sendContent(request);
+}
+
+void BaseClient::projectOpened(ProjectExplorer::Project *project)
+{
+ if (!sendWorkspceFolderChanges())
+ return;
+ WorkspaceFoldersChangeEvent event;
+ event.setAdded({WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())});
+ DidChangeWorkspaceFoldersParams params;
+ params.setEvent(event);
+ DidChangeWorkspaceFoldersNotification change(params);
+ sendContent(change);
+}
+
+void BaseClient::projectClosed(ProjectExplorer::Project *project)
+{
+ if (!sendWorkspceFolderChanges())
+ return;
+ WorkspaceFoldersChangeEvent event;
+ event.setRemoved({WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())});
+ DidChangeWorkspaceFoldersParams params;
+ params.setEvent(event);
+ DidChangeWorkspaceFoldersNotification change(params);
+ sendContent(change);
+}
+
+void BaseClient::setSupportedLanguages(const QStringList &supportedLanguages)
+{
+ m_supportedLanguageIds = supportedLanguages;
+}
+
+bool BaseClient::isSupportedLanguage(const QString &language) const
+{
+ return m_supportedLanguageIds.isEmpty() || m_supportedLanguageIds.contains(language);
+}
+
+void BaseClient::reset()
+{
+ m_state = Uninitialized;
+ m_responseHandlers.clear();
+ m_buffer.close();
+ m_buffer.setData(nullptr);
+ m_buffer.open(QIODevice::ReadWrite | QIODevice::Append);
+ m_openedDocument.clear();
+ m_serverCapabilities = ServerCapabilities();
+ m_dynamicCapabilities.reset();
+}
+
+void BaseClient::setError(const QString &message)
+{
+ log(message);
+ m_state = Error;
+}
+
+void BaseClient::log(const QString &message, Core::MessageManager::PrintToOutputPaneFlag flag)
+{
+ Core::MessageManager::write(QString("LanguageClient %1: %2").arg(name(), message), flag);
+}
+
+void BaseClient::log(LogMessageParams &message, Core::MessageManager::PrintToOutputPaneFlag flag)
+{
+ log(message.toString(), flag);
+}
+
+void BaseClient::handleResponse(const MessageId &id, const QByteArray &content, QTextCodec *codec)
+{
+ if (auto handler = m_responseHandlers[id])
+ handler(content, codec);
+}
+
+void BaseClient::handleMethod(const QString &method, MessageId id, const IContent *content)
+{
+ QStringList error;
+ bool paramsValid = true;
+ if (method == PublishDiagnosticsNotification::methodName) {
+ auto params = dynamic_cast<const PublishDiagnosticsNotification *>(content)->params().value_or(PublishDiagnosticsParams());
+ paramsValid = params.isValid(&error);
+ if (paramsValid)
+ LanguageClientManager::publishDiagnostics(m_id, params);
+ } else if (method == LogMessageNotification::methodName) {
+ auto params = dynamic_cast<const LogMessageNotification *>(content)->params().value_or(LogMessageParams());
+ paramsValid = params.isValid(&error);
+ if (paramsValid)
+ log(params);
+ } else if (method == RegisterCapabilityRequest::methodName) {
+ auto params = dynamic_cast<const RegisterCapabilityRequest *>(content)->params().value_or(RegistrationParams());
+ paramsValid = params.isValid(&error);
+ if (paramsValid)
+ m_dynamicCapabilities.registerCapability(params.registrations());
+ } else if (method == UnregisterCapabilityRequest::methodName) {
+ auto params = dynamic_cast<const UnregisterCapabilityRequest *>(content)->params().value_or(UnregistrationParams());
+ paramsValid = params.isValid(&error);
+ if (paramsValid)
+ m_dynamicCapabilities.unregisterCapability(params.unregistrations());
+ } else if (id.isValid(&error)) {
+ Response<JsonObject, JsonObject> response;
+ response.setId(id);
+ ResponseError<JsonObject> error;
+ error.setCode(ResponseError<JsonObject>::MethodNotFound);
+ response.setError(error);
+ sendContent(response);
+ }
+ std::reverse(error.begin(), error.end());
+ if (!paramsValid) {
+ log(tr("Invalid parameter in \"%1\": %2").arg(method, error.join("->")),
+ Core::MessageManager::Flash);
+ }
+ delete content;
+}
+
+void BaseClient::intializeCallback(const InitializeResponse &initResponse)
+{
+ QTC_ASSERT(m_state == InitializeRequested, return);
+ if (optional<ResponseError<InitializeError>> error = initResponse.error()) {
+ if (error.value().data().has_value()
+ && error.value().data().value().retry().value_or(false)) {
+ const QString title(tr("Language Server \"%1\" initialize error"));
+ auto result = QMessageBox::warning(Core::ICore::dialogParent(),
+ title,
+ error.value().message(),
+ QMessageBox::Retry | QMessageBox::Cancel,
+ QMessageBox::Retry);
+ if (result == QMessageBox::Retry) {
+ m_state = Uninitialized;
+ initialize();
+ return;
+ }
+ }
+ setError(tr("Initialize error: ") + error.value().message());
+ emit finished();
+ return;
+ }
+ const optional<InitializeResult> &_result = initResponse.result();
+ if (!_result.has_value()) {// continue on ill formed result
+ log(tr("No initialize result."));
+ } else {
+ const InitializeResult &result = _result.value();
+ QStringList error;
+ if (!result.isValid(&error)) // continue on ill formed result
+ log(tr("Initialize result is not valid: ") + error.join("->"));
+
+ m_serverCapabilities = result.capabilities().value_or(ServerCapabilities());
+ }
+ qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " initialized";
+ m_state = Initialized;
+ sendContent(InitializeNotification());
+ emit initialized(m_serverCapabilities);
+ for (auto openedDocument : Core::DocumentModel::openedDocuments())
+ openDocument(openedDocument);
+}
+
+void BaseClient::shutDownCallback(const ShutdownResponse &shutdownResponse)
+{
+ QTC_ASSERT(m_state == ShutdownRequested, return);
+ optional<ResponseError<JsonObject>> errorValue = shutdownResponse.error();
+ if (errorValue.has_value()) {
+ ResponseError<JsonObject> error = errorValue.value();
+ qDebug() << error;
+ return;
+ }
+ // directly send data otherwise the state check would fail;
+ sendData(ExitNotification().toBaseMessage().toData());
+ qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " shutdown";
+ m_state = Shutdown;
+}
+
+bool BaseClient::sendWorkspceFolderChanges() const
+{
+ if (m_dynamicCapabilities.isRegistered(
+ DidChangeWorkspaceFoldersNotification::methodName).value_or(false)) {
+ return true;
+ }
+ if (auto workspace = m_serverCapabilities.workspace()) {
+ if (auto folder = workspace.value().workspaceFolders()) {
+ if (folder.value().supported().value_or(false)) {
+ // holds either the Id for deregistration or whether it is registered
+ auto notification = folder.value().changeNotifications().value_or(false);
+ return holds_alternative<QString>(notification)
+ || (holds_alternative<bool>(notification) && get<bool>(notification));
+ }
+ }
+ }
+ return false;
+}
+
+void BaseClient::parseData(const QByteArray &data)
+{
+ const qint64 preWritePosition = m_buffer.pos();
+ m_buffer.write(data);
+ m_buffer.seek(preWritePosition);
+ while (!m_buffer.atEnd()) {
+ QString parseError;
+ BaseMessage::parse(&m_buffer, parseError, m_currentMessage);
+ if (!parseError.isEmpty())
+ log(parseError);
+ if (!m_currentMessage.isComplete())
+ break;
+ if (auto handler = m_contentHandler[m_currentMessage.mimeType]){
+ QString parseError;
+ handler(m_currentMessage.content, m_currentMessage.codec, parseError,
+ [this](MessageId id, const QByteArray &content, QTextCodec *codec){
+ this->handleResponse(id, content, codec);
+ },
+ [this](const QString &method, MessageId id, const IContent *content){
+ this->handleMethod(method, id, content);
+ });
+ if (!parseError.isEmpty())
+ log(parseError);
+ } else {
+ log(tr("Cannot handle content of type: %1").arg(QLatin1String(m_currentMessage.mimeType)));
+ }
+ m_currentMessage = BaseMessage();
+ }
+}
+
+StdIOClient::StdIOClient(const QString &command, const QStringList &args)
+{
+ connect(&m_process, &QProcess::readyReadStandardError,
+ this, &StdIOClient::readError);
+ connect(&m_process, &QProcess::readyReadStandardOutput,
+ this, &StdIOClient::readOutput);
+ connect(&m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
+ this, &StdIOClient::onProcessFinished);
+
+ m_process.setArguments(args);
+ m_process.setProgram(command);
+}
+
+StdIOClient::~StdIOClient()
+{
+ Utils::SynchronousProcess::stopProcess(m_process);
+}
+
+bool StdIOClient::start()
+{
+ m_process.start();
+ if (!m_process.waitForStarted() && m_process.state() != QProcess::Running) {
+ setError(m_process.errorString());
+ return false;
+ }
+ return true;
+}
+
+void StdIOClient::setWorkingDirectory(const QString &workingDirectory)
+{
+ m_process.setWorkingDirectory(workingDirectory);
+}
+
+bool StdIOClient::matches(const LanguageClientSettings &setting)
+{
+ return setting.m_executable == m_process.program()
+ && setting.m_arguments == m_process.arguments();
+}
+
+void StdIOClient::sendData(const QByteArray &data)
+{
+ if (m_process.state() != QProcess::Running) {
+ log(tr("Cannot send data to unstarted server %1").arg(m_process.program()));
+ return;
+ }
+ qCDebug(LOGLSPCLIENTV) << "StdIOClient send data:";
+ qCDebug(LOGLSPCLIENTV).noquote() << data;
+ m_process.write(data);
+}
+
+void StdIOClient::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ if (exitStatus == QProcess::CrashExit)
+ setError(tr("Crashed with exit code %1 : %2").arg(exitCode).arg(m_process.error()));
+ emit finished();
+}
+
+void StdIOClient::readError()
+{
+ qCDebug(LOGLSPCLIENTV) << "StdIOClient std err:\n";
+ qCDebug(LOGLSPCLIENTV).noquote() << m_process.readAllStandardError();
+}
+
+void StdIOClient::readOutput()
+{
+ const QByteArray &out = m_process.readAllStandardOutput();
+ qDebug(LOGLSPCLIENTV) << "StdIOClient std out:\n";
+ qDebug(LOGLSPCLIENTV).noquote() << out;
+ parseData(out);
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/baseclient.h b/src/plugins/languageclient/baseclient.h
new file mode 100644
index 0000000000..211b4b7a15
--- /dev/null
+++ b/src/plugins/languageclient/baseclient.h
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "dynamiccapabilities.h"
+#include "languageclientsettings.h"
+
+#include <coreplugin/id.h>
+#include <coreplugin/messagemanager.h>
+#include <utils/link.h>
+
+#include <languageserverprotocol/initializemessages.h>
+#include <languageserverprotocol/shutdownmessages.h>
+#include <languageserverprotocol/textsynchronization.h>
+#include <languageserverprotocol/messages.h>
+#include <languageserverprotocol/client.h>
+#include <languageserverprotocol/languagefeatures.h>
+
+#include <QBuffer>
+#include <QHash>
+#include <QProcess>
+#include <QJsonDocument>
+#include <QTextCursor>
+
+namespace Core { class IDocument; }
+namespace ProjectExplorer { class Project; }
+namespace TextEditor
+{
+ class TextDocument;
+ class TextEditorWidget;
+}
+
+namespace LanguageClient {
+
+class BaseClient : public QObject
+{
+ Q_OBJECT
+
+public:
+ BaseClient();
+ ~BaseClient() override;
+
+ BaseClient(const BaseClient &) = delete;
+ BaseClient(BaseClient &&) = delete;
+ BaseClient &operator=(const BaseClient &) = delete;
+ BaseClient &operator=(BaseClient &&) = delete;
+
+ enum State {
+ Uninitialized,
+ InitializeRequested,
+ Initialized,
+ ShutdownRequested,
+ Shutdown,
+ Error
+ };
+
+ void initialize();
+ void shutdown();
+ State state() const;
+ bool reachable() const { return m_state == Initialized; }
+
+ // document synchronization
+ void openDocument(Core::IDocument *document);
+ void closeDocument(const LanguageServerProtocol::DidCloseTextDocumentParams &params);
+ bool documentOpen(const LanguageServerProtocol::DocumentUri &uri) const;
+ void documentContentsSaved(Core::IDocument *document);
+ void documentWillSave(Core::IDocument *document);
+ void documentContentsChanged(Core::IDocument *document);
+ void registerCapabilities(const QList<LanguageServerProtocol::Registration> &registrations);
+ void unregisterCapabilities(const QList<LanguageServerProtocol::Unregistration> &unregistrations);
+ bool findLinkAt(LanguageServerProtocol::GotoDefinitionRequest &request);
+ void requestDocumentSymbols(TextEditor::TextDocument *document);
+ void cursorPositionChanged(TextEditor::TextEditorWidget *widget);
+
+ // workspace control
+ void projectOpened(ProjectExplorer::Project *project);
+ void projectClosed(ProjectExplorer::Project *project);
+
+ void sendContent(const LanguageServerProtocol::IContent &content);
+ void sendContent(const LanguageServerProtocol::DocumentUri &uri,
+ const LanguageServerProtocol::IContent &content);
+ void cancelRequest(const LanguageServerProtocol::MessageId &id);
+
+ void setSupportedLanguages(const QStringList &supportedLanguages);
+ bool isSupportedLanguage(const QString &language) const;
+
+ void setName(const QString &name) { m_displayName = name; }
+ QString name() const { return m_displayName; }
+
+ Core::Id id() const { return m_id; }
+
+ virtual bool start() { return true; }
+ virtual bool matches(const LanguageClientSettings &/*setting*/) { return false; }
+ virtual void reset();
+
+ void log(const QString &message,
+ Core::MessageManager::PrintToOutputPaneFlag flag = Core::MessageManager::NoModeSwitch);
+ void log(LanguageServerProtocol::LogMessageParams &message,
+ Core::MessageManager::PrintToOutputPaneFlag flag = Core::MessageManager::NoModeSwitch);
+
+signals:
+ void initialized(LanguageServerProtocol::ServerCapabilities capabilities);
+ void finished();
+
+protected:
+ void setError(const QString &message);
+ virtual void sendData(const QByteArray &data) = 0;
+ void parseData(const QByteArray &data);
+
+private:
+ void handleResponse(const LanguageServerProtocol::MessageId &id, const QByteArray &content,
+ QTextCodec *codec);
+ void handleMethod(const QString &method, LanguageServerProtocol::MessageId id,
+ const LanguageServerProtocol::IContent *content);
+
+ void intializeCallback(const LanguageServerProtocol::InitializeResponse &initResponse);
+ void shutDownCallback(const LanguageServerProtocol::ShutdownResponse &shutdownResponse);
+ bool sendWorkspceFolderChanges() const;
+
+ using ContentHandler = std::function<void(const QByteArray &, QTextCodec *, QString &,
+ LanguageServerProtocol::ResponseHandlers,
+ LanguageServerProtocol::MethodHandler)>;
+
+ State m_state = Uninitialized;
+ QHash<LanguageServerProtocol::MessageId, LanguageServerProtocol::ResponseHandler> m_responseHandlers;
+ QHash<QByteArray, ContentHandler> m_contentHandler;
+ QBuffer m_buffer;
+ QString m_displayName;
+ QStringList m_supportedLanguageIds;
+ QList<Utils::FileName> m_openedDocument;
+ Core::Id m_id;
+ LanguageServerProtocol::ServerCapabilities m_serverCapabilities;
+ DynamicCapabilities m_dynamicCapabilities;
+ LanguageServerProtocol::BaseMessage m_currentMessage;
+ QHash<LanguageServerProtocol::DocumentUri, LanguageServerProtocol::MessageId> m_highlightRequests;
+};
+
+class StdIOClient : public BaseClient
+{
+ Q_OBJECT
+public:
+ StdIOClient(const QString &command, const QStringList &args = QStringList());
+ ~StdIOClient() override;
+
+ StdIOClient() = delete;
+ StdIOClient(const StdIOClient &) = delete;
+ StdIOClient(StdIOClient &&) = delete;
+ StdIOClient &operator=(const StdIOClient &) = delete;
+ StdIOClient &operator=(StdIOClient &&) = delete;
+
+ bool start() override;
+
+ void setWorkingDirectory(const QString &workingDirectory);
+
+ bool matches(const LanguageClientSettings &setting) override;
+
+protected:
+ void sendData(const QByteArray &data) final;
+ QProcess m_process;
+
+private:
+ void readError();
+ void readOutput();
+ void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
+};
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/dynamiccapabilities.cpp b/src/plugins/languageclient/dynamiccapabilities.cpp
new file mode 100644
index 0000000000..1b0aef037f
--- /dev/null
+++ b/src/plugins/languageclient/dynamiccapabilities.cpp
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "dynamiccapabilities.h"
+
+using namespace LanguageServerProtocol;
+
+namespace LanguageClient {
+
+void DynamicCapabilities::registerCapability(const QList<Registration> &registrations)
+{
+ for (const Registration& registration : registrations) {
+ const QString &method = registration.method();
+ m_capability[method].enable(registration.id(), registration.registerOptions());
+ m_methodForId.insert(registration.id(), method);
+ }
+}
+
+void DynamicCapabilities::unregisterCapability(const QList<Unregistration> &unregistrations)
+{
+ for (const Unregistration& unregistration : unregistrations) {
+ QString method = unregistration.method();
+ if (method.isEmpty())
+ method = m_methodForId[unregistration.id()];
+ m_capability[method].disable();
+ m_methodForId.remove(unregistration.id());
+ }
+}
+
+Utils::optional<bool> DynamicCapabilities::isRegistered(const QString &method) const
+{
+ if (!m_capability.contains(method))
+ return Utils::nullopt;
+ return m_capability[method].enabled();
+}
+
+void DynamicCapabilities::reset()
+{
+ m_capability.clear();
+ m_methodForId.clear();
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/dynamiccapabilities.h b/src/plugins/languageclient/dynamiccapabilities.h
new file mode 100644
index 0000000000..6270c0699d
--- /dev/null
+++ b/src/plugins/languageclient/dynamiccapabilities.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <languageserverprotocol/client.h>
+
+namespace LanguageClient {
+
+class DynamicCapability
+{
+public:
+ DynamicCapability() = default;
+ void enable(QString id, QJsonValue options)
+ {
+ QTC_CHECK(!m_enabled);
+ m_enabled = true;
+ m_id = id;
+ m_options = options;
+ }
+
+ void disable()
+ {
+ m_enabled = true;
+ m_id.clear();
+ m_options = QJsonValue();
+ }
+
+ bool enabled() const { return m_enabled; }
+
+ QJsonValue options() const { return m_options; }
+
+private:
+ bool m_enabled = false;
+ QString m_id;
+ QJsonValue m_options;
+
+};
+
+class DynamicCapabilities
+{
+public:
+ DynamicCapabilities() = default;
+
+ void registerCapability(const QList<LanguageServerProtocol::Registration> &registrations);
+ void unregisterCapability(const QList<LanguageServerProtocol::Unregistration> &unregistrations);
+
+ Utils::optional<bool> isRegistered(const QString &method) const;
+ QJsonValue option(const QString &method) const { return m_capability[method].options(); }
+
+ void reset();
+
+private:
+ QHash<QString, DynamicCapability> m_capability;
+ QHash<QString, QString> m_methodForId;
+};
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclient.pro b/src/plugins/languageclient/languageclient.pro
new file mode 100644
index 0000000000..e34f11ddd6
--- /dev/null
+++ b/src/plugins/languageclient/languageclient.pro
@@ -0,0 +1,20 @@
+include(../../qtcreatorplugin.pri)
+
+DEFINES += LANGUAGECLIENT_LIBRARY
+
+HEADERS += \
+ baseclient.h \
+ dynamiccapabilities.h \
+ languageclient_global.h \
+ languageclientcodeassist.h \
+ languageclientmanager.h \
+ languageclientplugin.h \
+ languageclientsettings.h
+
+SOURCES += \
+ baseclient.cpp \
+ dynamiccapabilities.cpp \
+ languageclientcodeassist.cpp \
+ languageclientmanager.cpp \
+ languageclientplugin.cpp \
+ languageclientsettings.cpp
diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs
new file mode 100644
index 0000000000..3b426ceb49
--- /dev/null
+++ b/src/plugins/languageclient/languageclient.qbs
@@ -0,0 +1,31 @@
+import qbs 1.0
+
+QtcPlugin {
+
+ name: "LanguageClient"
+
+ Depends { name: "Qt.core" }
+
+ Depends { name: "Utils" }
+ Depends { name: "ProjectExplorer" }
+ Depends { name: "LanguageServerProtocol" }
+
+ Depends { name: "Core" }
+ Depends { name: "TextEditor" }
+
+ files: [
+ "baseclient.cpp",
+ "baseclient.h",
+ "dynamiccapabilities.cpp",
+ "dynamiccapabilities.h",
+ "languageclient_global.h",
+ "languageclientcodeassist.cpp",
+ "languageclientcodeassist.h",
+ "languageclientmanager.cpp",
+ "languageclientmanager.h",
+ "languageclientplugin.cpp",
+ "languageclientplugin.h",
+ "languageclientsettings.cpp",
+ "languageclientsettings.h",
+ ]
+}
diff --git a/src/plugins/languageclient/languageclient_dependencies.pri b/src/plugins/languageclient/languageclient_dependencies.pri
new file mode 100644
index 0000000000..2b2a4211bf
--- /dev/null
+++ b/src/plugins/languageclient/languageclient_dependencies.pri
@@ -0,0 +1,10 @@
+QTC_PLUGIN_NAME = LanguageClient
+
+QTC_LIB_DEPENDS += \
+ utils \
+ languageserverprotocol
+
+QTC_PLUGIN_DEPENDS += \
+ coreplugin \
+ projectexplorer \
+ texteditor
diff --git a/src/plugins/languageclient/languageclient_global.h b/src/plugins/languageclient/languageclient_global.h
new file mode 100644
index 0000000000..a4cd255d90
--- /dev/null
+++ b/src/plugins/languageclient/languageclient_global.h
@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtGlobal>
+
+namespace LanguageClient {
+namespace Constants {
+
+const char LANGUAGECLIENT_SETTINGS_CATEGORY[] = "ZY.LanguageClient";
+const char LANGUAGECLIENT_SETTINGS_TR[] = QT_TRANSLATE_NOOP("LanguageClient", "Language Client");
+
+} // namespace Constants
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientcodeassist.cpp b/src/plugins/languageclient/languageclientcodeassist.cpp
new file mode 100644
index 0000000000..16c1d18e16
--- /dev/null
+++ b/src/plugins/languageclient/languageclientcodeassist.cpp
@@ -0,0 +1,354 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "languageclientcodeassist.h"
+#include "baseclient.h"
+
+#include <languageserverprotocol/completion.h>
+#include <texteditor/codeassist/assistinterface.h>
+#include <texteditor/codeassist/assistproposalitem.h>
+#include <texteditor/codeassist/iassistprocessor.h>
+#include <texteditor/codeassist/genericproposal.h>
+#include <texteditor/codeassist/genericproposalmodel.h>
+#include <utils/algorithm.h>
+#include <utils/textutils.h>
+#include <utils/utilsicons.h>
+
+#include <QDebug>
+#include <QLoggingCategory>
+#include <QRegExp>
+#include <QTextBlock>
+#include <QTextDocument>
+#include <QTime>
+
+static Q_LOGGING_CATEGORY(LOGLSPCOMPLETION, "qtc.languageclient.completion");
+
+using namespace LanguageServerProtocol;
+
+namespace LanguageClient {
+
+class LanguageClientCompletionItem : public TextEditor::AssistProposalItemInterface
+{
+public:
+ LanguageClientCompletionItem(CompletionItem item);
+
+ // AssistProposalItemInterface interface
+ QString text() const override;
+ bool implicitlyApplies() const override;
+ bool prematurelyApplies(const QChar &typedCharacter) const override;
+ void apply(TextEditor::TextDocumentManipulatorInterface &manipulator, int basePosition) const override;
+ QIcon icon() const override;
+ QString detail() const override;
+ bool isSnippet() const override;
+ bool isValid() const override;
+ quint64 hash() const override;
+
+ const QString &sortText() const;
+
+ bool operator <(const LanguageClientCompletionItem &other) const;
+
+private:
+ CompletionItem m_item;
+ mutable QString m_sortText;
+};
+
+LanguageClientCompletionItem::LanguageClientCompletionItem(CompletionItem item)
+ : m_item(std::move(item))
+{ }
+
+QString LanguageClientCompletionItem::text() const
+{ return m_item.label(); }
+
+bool LanguageClientCompletionItem::implicitlyApplies() const
+{ return true; }
+
+bool LanguageClientCompletionItem::prematurelyApplies(const QChar &/*typedCharacter*/) const
+{ return false; }
+
+static void applyTextEdit(TextEditor::TextDocumentManipulatorInterface &manipulator,
+ const TextEdit &edit)
+{
+ using namespace Utils::Text;
+ const Range range = edit.range();
+ const QTextDocument *doc = manipulator.textCursorAt(manipulator.currentPosition()).document();
+ const int start = positionInText(doc, range.start().line() + 1, range.start().character() + 1);
+ const int end = positionInText(doc, range.end().line() + 1, range.end().character() + 1);
+ manipulator.replace(start, end - start, edit.newText());
+}
+
+void LanguageClientCompletionItem::apply(TextEditor::TextDocumentManipulatorInterface &manipulator,
+ int /*basePosition*/) const
+{
+ const int pos = manipulator.currentPosition();
+ if (auto edit = m_item.textEdit()) {
+ applyTextEdit(manipulator, *edit);
+ } else {
+ const QString textToInsert(m_item.insertText().value_or(text()));
+ int length = 0;
+ for (auto it = textToInsert.crbegin(); it != textToInsert.crend(); ++it) {
+ auto ch = *it;
+ if (ch == manipulator.characterAt(pos - length - 1))
+ ++length;
+ else if (length != 0)
+ length = 0;
+ }
+ manipulator.replace(pos - length, length, textToInsert);
+ }
+
+ if (auto additionalEdits = m_item.additionalTextEdits()) {
+ for (const auto &edit : *additionalEdits)
+ applyTextEdit(manipulator, edit);
+ }
+}
+
+QIcon LanguageClientCompletionItem::icon() const
+{
+ QIcon icon;
+ using namespace Utils::CodeModelIcon;
+ const int kind = m_item.kind().value_or(CompletionItemKind::Text);
+ switch (kind) {
+ case CompletionItemKind::Method:
+ case CompletionItemKind::Function:
+ case CompletionItemKind::Constructor: icon = iconForType(FuncPublic); break;
+ case CompletionItemKind::Field: icon = iconForType(VarPublic); break;
+ case CompletionItemKind::Variable: icon = iconForType(VarPublic); break;
+ case CompletionItemKind::Class: icon = iconForType(Class); break;
+ case CompletionItemKind::Module: icon = iconForType(Namespace); break;
+ case CompletionItemKind::Property: icon = iconForType(Property); break;
+ case CompletionItemKind::Enum: icon = iconForType(Enum); break;
+ case CompletionItemKind::Keyword: icon = iconForType(Keyword); break;
+ case CompletionItemKind::Snippet: icon = QIcon(":/texteditor/images/snippet.png"); break;
+ case CompletionItemKind::EnumMember: icon = iconForType(Enumerator); break;
+ case CompletionItemKind::Struct: icon = iconForType(Struct); break;
+ default:
+ break;
+ }
+ return icon;
+}
+
+QString LanguageClientCompletionItem::detail() const
+{
+ if (auto _doc = m_item.documentation()) {
+ auto doc = *_doc;
+ QString detailDocText;
+ if (Utils::holds_alternative<QString>(doc)) {
+ detailDocText = Utils::get<QString>(doc);
+ } else if (Utils::holds_alternative<MarkupContent>(doc)) {
+ // TODO markdown parser?
+ detailDocText = Utils::get<MarkupContent>(doc).content();
+ }
+ if (!detailDocText.isEmpty())
+ return detailDocText;
+ }
+ return m_item.detail().value_or(text());
+}
+
+bool LanguageClientCompletionItem::isSnippet() const
+{
+ // FIXME add lsp > creator snippet converter
+ // return m_item.insertTextFormat().value_or(CompletionItem::PlainText);
+ return false;
+}
+
+bool LanguageClientCompletionItem::isValid() const
+{
+ return m_item.isValid(nullptr);
+}
+
+quint64 LanguageClientCompletionItem::hash() const
+{
+ return qHash(m_item.label()); // TODO: naaaa
+}
+
+const QString &LanguageClientCompletionItem::sortText() const
+{
+ if (m_sortText.isEmpty())
+ m_sortText = m_item.sortText().has_value() ? *m_item.sortText() : m_item.label();
+ return m_sortText;
+}
+
+bool LanguageClientCompletionItem::operator <(const LanguageClientCompletionItem &other) const
+{
+ return sortText() < other.sortText();
+}
+
+class LanguageClientCompletionModel : public TextEditor::GenericProposalModel
+{
+public:
+ // GenericProposalModel interface
+ bool isSortable(const QString &/*prefix*/) const override { return true; }
+ void sort(const QString &/*prefix*/) override;
+ bool supportsPrefixExpansion() const override { return false; }
+};
+
+void LanguageClientCompletionModel::sort(const QString &/*prefix*/)
+{
+ using namespace TextEditor;
+ std::sort(m_currentItems.begin(), m_currentItems.end(),
+ [] (AssistProposalItemInterface *a, AssistProposalItemInterface *b){
+ return *(dynamic_cast<LanguageClientCompletionItem *>(a)) < *(
+ dynamic_cast<LanguageClientCompletionItem *>(b));
+ });
+}
+
+class LanguageClientCompletionAssistProcessor : public TextEditor::IAssistProcessor
+{
+public:
+ LanguageClientCompletionAssistProcessor(BaseClient *client);
+ TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override;
+ bool running() override;
+
+private:
+ void handleCompletionResponse(const Response<CompletionResult, LanguageClientNull> &response);
+
+ QPointer<BaseClient> m_client;
+ bool m_running = false;
+ int m_pos = -1;
+};
+
+LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(BaseClient *client)
+ : m_client(client)
+{ }
+
+static QString assistReasonString(TextEditor::AssistReason reason)
+{
+ switch (reason) {
+ case TextEditor::IdleEditor: return QString("idle editor");
+ case TextEditor::ActivationCharacter: return QString("activation character");
+ case TextEditor::ExplicitlyInvoked: return QString("explicitly invoking");
+ }
+ return QString("unknown reason");
+}
+
+TextEditor::IAssistProposal *LanguageClientCompletionAssistProcessor::perform(
+ const TextEditor::AssistInterface *interface)
+{
+ QTC_ASSERT(m_client, return nullptr);
+ m_pos = interface->position();
+// const QRegExp regexp("[_a-zA-Z][_a-zA-Z0-9]*"); // FIXME
+// int delta = 0;
+// while (m_pos - delta > 0 && regexp.exactMatch(interface->textAt(m_pos - delta - 1, delta + 1)))
+// ++delta;
+// m_pos -= delta;
+ CompletionRequest completionRequest;
+ CompletionParams::CompletionContext context;
+ context.setTriggerKind(interface->reason() == TextEditor::ActivationCharacter
+ ? CompletionParams::TriggerCharacter
+ : CompletionParams::Invoked);
+ auto params = completionRequest.params().value_or(CompletionParams());
+ int line;
+ int column;
+ if (!Utils::Text::convertPosition(interface->textDocument(), m_pos, &line, &column))
+ return nullptr;
+ --line; // line is 0 based in the protocol
+ params.setPosition({line, column});
+ params.setTextDocument(
+ DocumentUri::fromFileName(Utils::FileName::fromString(interface->fileName())));
+ completionRequest.setResponseCallback([this](auto response) {
+ this->handleCompletionResponse(response);
+ });
+ completionRequest.setParams(params);
+ m_client->sendContent(completionRequest);
+ m_running = true;
+ qCDebug(LOGLSPCOMPLETION) << QTime::currentTime()
+ << " : request completions at " << m_pos
+ << " by " << assistReasonString(interface->reason());
+ return nullptr;
+}
+
+bool LanguageClientCompletionAssistProcessor::running()
+{
+ return m_running;
+}
+
+void LanguageClientCompletionAssistProcessor::handleCompletionResponse(
+ const Response<CompletionResult, LanguageClientNull> &response)
+{
+ using namespace TextEditor;
+ qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : got completions";
+ m_running = false;
+ QTC_ASSERT(m_client, return);
+ if (auto error = response.error()) {
+ m_client->log(error.value().message());
+ return;
+ }
+ const Utils::optional<CompletionResult> &result = response.result();
+ if (!result || Utils::holds_alternative<std::nullptr_t>(*result))
+ return;
+
+ QList<CompletionItem> items;
+ if (Utils::holds_alternative<CompletionList>(*result)) {
+ const auto &list = Utils::get<CompletionList>(*result);
+ items = list.items().value_or(QList<CompletionItem>());
+ } else if (Utils::holds_alternative<QList<CompletionItem>>(*result)) {
+ items = Utils::get<QList<CompletionItem>>(*result);
+ }
+ auto model = new LanguageClientCompletionModel();
+ model->loadContent(Utils::transform(items, [](const CompletionItem &item){
+ return static_cast<AssistProposalItemInterface *>(new LanguageClientCompletionItem(item));
+ }));
+ auto proposal = new GenericProposal(m_pos, GenericProposalModelPtr(model));
+ proposal->setFragile(true);
+ setAsyncProposalAvailable(proposal);
+ qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : "
+ << items.count() << " completions handled";
+}
+
+LanguageClientCompletionAssistProvider::LanguageClientCompletionAssistProvider(BaseClient *client)
+ : m_client(client)
+{ }
+
+TextEditor::IAssistProcessor *LanguageClientCompletionAssistProvider::createProcessor() const
+{
+ return new LanguageClientCompletionAssistProcessor(m_client);
+}
+
+TextEditor::IAssistProvider::RunType LanguageClientCompletionAssistProvider::runType() const
+{
+ return TextEditor::IAssistProvider::Asynchronous;
+}
+
+int LanguageClientCompletionAssistProvider::activationCharSequenceLength() const
+{
+ return m_activationCharSequenceLength;
+}
+
+bool LanguageClientCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const
+{
+ return Utils::anyOf(m_triggerChars, [sequence](const QString &trigger){
+ return trigger.endsWith(sequence);
+ });
+}
+
+void LanguageClientCompletionAssistProvider::setTriggerCharacters(QList<QString> triggerChars)
+{
+ m_triggerChars = triggerChars;
+ for (const QString &trigger : triggerChars) {
+ if (trigger.length() > m_activationCharSequenceLength)
+ m_activationCharSequenceLength = trigger.length();
+ }
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientcodeassist.h b/src/plugins/languageclient/languageclientcodeassist.h
new file mode 100644
index 0000000000..14af748e72
--- /dev/null
+++ b/src/plugins/languageclient/languageclientcodeassist.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <texteditor/codeassist/completionassistprovider.h>
+
+namespace LanguageClient {
+
+class BaseClient;
+
+class LanguageClientCompletionAssistProvider : public TextEditor::CompletionAssistProvider
+{
+public:
+ LanguageClientCompletionAssistProvider(BaseClient *client);
+
+ TextEditor::IAssistProcessor *createProcessor() const override;
+ RunType runType() const override;
+
+ int activationCharSequenceLength() const override;
+ bool isActivationCharSequence(const QString &sequence) const override;
+ bool isContinuationChar(const QChar &) const override { return true; }
+
+ void setTriggerCharacters(QList<QString> triggerChars);
+
+private:
+ QList<QString> m_triggerChars;
+ int m_activationCharSequenceLength = 0;
+ BaseClient *m_client;
+};
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp
new file mode 100644
index 0000000000..6b9e54d114
--- /dev/null
+++ b/src/plugins/languageclient/languageclientmanager.cpp
@@ -0,0 +1,322 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "languageclientmanager.h"
+
+#include <coreplugin/documentmanager.h>
+#include <coreplugin/editormanager/documentmodel.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/editormanager/ieditor.h>
+#include <languageserverprotocol/messages.h>
+#include <projectexplorer/session.h>
+#include <projectexplorer/project.h>
+#include <texteditor/texteditor.h>
+#include <texteditor/textmark.h>
+#include <texteditor/textdocument.h>
+#include <utils/mimetypes/mimedatabase.h>
+#include <utils/theme/theme.h>
+#include <utils/utilsicons.h>
+
+#include <QTimer>
+
+using namespace LanguageServerProtocol;
+
+namespace LanguageClient {
+
+static LanguageClientManager *managerInstance = nullptr;
+
+class LanguageClientMark : public TextEditor::TextMark
+{
+public:
+ LanguageClientMark(const Utils::FileName &fileName, const Diagnostic &diag)
+ : TextEditor::TextMark(fileName, diag.range().start().line() + 1, "lspmark")
+ {
+ using namespace Utils;
+ setLineAnnotation(diag.message());
+ setToolTip(diag.message());
+ const bool isError
+ = diag.severity().value_or(DiagnosticSeverity::Hint) == DiagnosticSeverity::Error;
+ setColor(isError ? Theme::CodeModel_Error_TextMarkColor
+ : Theme::CodeModel_Warning_TextMarkColor);
+
+ setIcon(isError ? Icons::CODEMODEL_ERROR.icon()
+ : Icons::CODEMODEL_WARNING.icon());
+ }
+
+ void removedFromEditor() override
+ {
+ LanguageClientManager::removeMark(this);
+ }
+};
+
+LanguageClientManager::LanguageClientManager()
+{
+ JsonRpcMessageHandler::registerMessageProvider("textDocument/publishDiagnostics",
+ [](const QJsonObject &object){
+ return new PublishDiagnosticsNotification(object);
+ });
+ JsonRpcMessageHandler::registerMessageProvider(LogMessageNotification::methodName,
+ [](const QJsonObject &object){
+ return new LogMessageNotification(object);
+ });
+ managerInstance = this;
+}
+
+LanguageClientManager::~LanguageClientManager()
+{
+ for (auto interface : Utils::filtered(m_clients, &BaseClient::reachable))
+ interface->shutdown();
+}
+
+void LanguageClientManager::init()
+{
+ using namespace Core;
+ using namespace ProjectExplorer;
+ QTC_ASSERT(managerInstance, return);
+ connect(EditorManager::instance(), &EditorManager::editorOpened,
+ managerInstance, &LanguageClientManager::editorOpened);
+ connect(EditorManager::instance(), &EditorManager::editorsClosed,
+ managerInstance, &LanguageClientManager::editorsClosed);
+ connect(EditorManager::instance(), &EditorManager::saved,
+ managerInstance, &LanguageClientManager::documentContentsSaved);
+ connect(EditorManager::instance(), &EditorManager::aboutToSave,
+ managerInstance, &LanguageClientManager::documentWillSave);
+ connect(SessionManager::instance(), &SessionManager::projectAdded,
+ managerInstance, &LanguageClientManager::projectAdded);
+ connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ managerInstance, &LanguageClientManager::projectRemoved);
+}
+
+void LanguageClientManager::publishDiagnostics(const Core::Id &id,
+ const PublishDiagnosticsParams &params)
+{
+ const Utils::FileName filePath = params.uri().toFileName();
+ auto doc = qobject_cast<TextEditor::TextDocument *>(
+ Core::DocumentModel::documentForFilePath(filePath.toString()));
+ if (!doc)
+ return;
+
+ removeMarks(filePath, id);
+ managerInstance->m_marks[filePath][id].reserve(params.diagnostics().size());
+ for (const Diagnostic& diagnostic : params.diagnostics()) {
+ auto mark = new LanguageClientMark(filePath, diagnostic);
+ managerInstance->m_marks[filePath][id].append(mark);
+ doc->addMark(mark);
+ }
+}
+
+void LanguageClientManager::removeMark(LanguageClientMark *mark)
+{
+ for (auto &marks : managerInstance->m_marks[mark->fileName()])
+ marks.removeAll(mark);
+ delete mark;
+}
+
+void LanguageClientManager::removeMarks(const Utils::FileName &fileName)
+{
+ auto doc = qobject_cast<TextEditor::TextDocument *>(
+ Core::DocumentModel::documentForFilePath(fileName.toString()));
+ if (!doc)
+ return;
+
+ for (auto marks : managerInstance->m_marks[fileName]) {
+ for (TextEditor::TextMark *mark : marks) {
+ doc->removeMark(mark);
+ delete mark;
+ }
+ }
+ managerInstance->m_marks[fileName].clear();
+}
+
+void LanguageClientManager::removeMarks(const Utils::FileName &fileName, const Core::Id &id)
+{
+ auto doc = qobject_cast<TextEditor::TextDocument *>(
+ Core::DocumentModel::documentForFilePath(fileName.toString()));
+ if (!doc)
+ return;
+
+ for (TextEditor::TextMark *mark : managerInstance->m_marks[fileName][id]) {
+ doc->removeMark(mark);
+ delete mark;
+ }
+ managerInstance->m_marks[fileName][id].clear();
+}
+
+void LanguageClientManager::removeMarks(const Core::Id &id)
+{
+ for (const Utils::FileName &fileName : managerInstance->m_marks.keys())
+ removeMarks(fileName, id);
+}
+
+void LanguageClientManager::startClient(BaseClient *client)
+{
+ managerInstance->m_clients.append(client);
+ connect(client, &BaseClient::finished, managerInstance, [client](){
+ managerInstance->clientFinished(client);
+ });
+ if (client->start())
+ client->initialize();
+ else
+ managerInstance->clientFinished(client);
+}
+
+QVector<BaseClient *> LanguageClientManager::clients()
+{
+ return managerInstance->m_clients;
+}
+
+void LanguageClientManager::addExclusiveRequest(const MessageId &id, BaseClient *client)
+{
+ managerInstance->m_exclusiveRequests[id] << client;
+}
+
+void LanguageClientManager::reportFinished(const MessageId &id, BaseClient *byClient)
+{
+ for (BaseClient *client : managerInstance->m_exclusiveRequests[id]) {
+ if (client != byClient)
+ client->cancelRequest(id);
+ }
+ managerInstance->m_exclusiveRequests.remove(id);
+}
+
+QVector<BaseClient *> LanguageClientManager::reachableClients()
+{
+ return Utils::filtered(m_clients, &BaseClient::reachable);
+}
+
+static void sendToInterfaces(const IContent &content, const QVector<BaseClient *> &interfaces)
+{
+ for (BaseClient *interface : interfaces)
+ interface->sendContent(content);
+}
+
+void LanguageClientManager::sendToAllReachableServers(const IContent &content)
+{
+ sendToInterfaces(content, reachableClients());
+}
+
+void LanguageClientManager::clientFinished(BaseClient *client)
+{
+ constexpr int restartTimeoutS = 5;
+ const bool unexpectedFinish = client->state() != BaseClient::Shutdown
+ && client->state() != BaseClient::ShutdownRequested;
+ managerInstance->removeMarks(client->id());
+ managerInstance->m_clients.removeAll(client);
+ if (unexpectedFinish) {
+ client->log(tr("Unexpectedly finished. Restarting in %1 seconds.").arg(restartTimeoutS),
+ Core::MessageManager::Flash);
+ client->reset();
+ QTimer::singleShot(restartTimeoutS * 1000, this, [client](){ startClient(client); });
+ } else {
+ delete client;
+ }
+}
+
+void LanguageClientManager::editorOpened(Core::IEditor *iEditor)
+{
+ using namespace TextEditor;
+ Core::IDocument *document = iEditor->document();
+ for (BaseClient *interface : reachableClients())
+ interface->openDocument(document);
+
+ if (auto textDocument = qobject_cast<TextDocument *>(document)) {
+ if (BaseTextEditor *editor = BaseTextEditor::textEditorForDocument(textDocument)) {
+ if (TextEditorWidget *widget = editor->editorWidget()) {
+ connect(widget, &TextEditorWidget::requestLinkAt, this,
+ [this, filePath = document->filePath()]
+ (const QTextCursor &cursor, Utils::ProcessLinkCallback &callback){
+ findLinkAt(filePath, cursor, callback);
+ });
+ }
+ }
+ }
+}
+
+void LanguageClientManager::editorsClosed(const QList<Core::IEditor *> editors)
+{
+ for (auto iEditor : editors) {
+ if (auto editor = qobject_cast<TextEditor::BaseTextEditor *>(iEditor)) {
+ removeMarks(editor->document()->filePath());
+ const DidCloseTextDocumentParams params(TextDocumentIdentifier(
+ DocumentUri::fromFileName(editor->document()->filePath())));
+ for (BaseClient *interface : reachableClients())
+ interface->closeDocument(params);
+ }
+ }
+}
+
+void LanguageClientManager::documentContentsSaved(Core::IDocument *document)
+{
+ for (BaseClient *interface : reachableClients())
+ interface->documentContentsSaved(document);
+}
+
+void LanguageClientManager::documentWillSave(Core::IDocument *document)
+{
+ for (BaseClient *interface : reachableClients())
+ interface->documentContentsSaved(document);
+}
+
+void LanguageClientManager::findLinkAt(const Utils::FileName &filePath,
+ const QTextCursor &cursor,
+ Utils::ProcessLinkCallback callback)
+{
+ const DocumentUri uri = DocumentUri::fromFileName(filePath);
+ const TextDocumentIdentifier document(uri);
+ const Position pos(cursor);
+ TextDocumentPositionParams params(document, pos);
+ GotoDefinitionRequest request(params);
+ request.setResponseCallback([callback](const Response<GotoResult, LanguageClientNull> &response){
+ if (Utils::optional<GotoResult> _result = response.result()) {
+ const GotoResult result = _result.value();
+ if (Utils::holds_alternative<std::nullptr_t>(result))
+ return;
+ if (auto ploc = Utils::get_if<Location>(&result)) {
+ callback(ploc->toLink());
+ } else if (auto plloc = Utils::get_if<QList<Location>>(&result)) {
+ if (!plloc->isEmpty())
+ callback(plloc->value(0).toLink());
+ }
+ }
+ });
+ for (BaseClient *interface : reachableClients()) {
+ if (interface->findLinkAt(request))
+ m_exclusiveRequests[request.id()] << interface;
+ }
+}
+
+void LanguageClientManager::projectAdded(ProjectExplorer::Project *project)
+{
+ for (BaseClient *interface : reachableClients())
+ interface->projectOpened(project);
+}
+
+void LanguageClientManager::projectRemoved(ProjectExplorer::Project *project)
+{
+ for (BaseClient *interface : reachableClients())
+ interface->projectClosed(project);
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h
new file mode 100644
index 0000000000..5c115643fd
--- /dev/null
+++ b/src/plugins/languageclient/languageclientmanager.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "baseclient.h"
+
+#include <coreplugin/id.h>
+
+#include <languageserverprotocol/diagnostics.h>
+#include <languageserverprotocol/languagefeatures.h>
+#include <languageserverprotocol/textsynchronization.h>
+
+namespace Core {
+class IEditor;
+class IDocument;
+}
+
+namespace ProjectExplorer { class Project; }
+
+namespace LanguageClient {
+
+class LanguageClientMark;
+
+class LanguageClientManager : public QObject
+{
+ Q_OBJECT
+public:
+ ~LanguageClientManager() override;
+
+ static void init();
+
+ static void publishDiagnostics(const Core::Id &id,
+ const LanguageServerProtocol::PublishDiagnosticsParams &params);
+
+ static void removeMark(LanguageClientMark *mark);
+ static void removeMarks(const Utils::FileName &fileName);
+ static void removeMarks(const Utils::FileName &fileName, const Core::Id &id);
+ static void removeMarks(const Core::Id &id);
+
+ static void startClient(BaseClient *client);
+ static QVector<BaseClient *> clients();
+
+ static void addExclusiveRequest(const LanguageServerProtocol::MessageId &id, BaseClient *client);
+ static void reportFinished(const LanguageServerProtocol::MessageId &id, BaseClient *byClient);
+
+private:
+ LanguageClientManager();
+ LanguageClientManager(const LanguageClientManager &other) = delete;
+ LanguageClientManager(LanguageClientManager &&other) = delete;
+
+ void editorOpened(Core::IEditor *editor);
+ void editorsClosed(const QList<Core::IEditor *> editors);
+ void documentContentsSaved(Core::IDocument *document);
+ void documentWillSave(Core::IDocument *document);
+ void findLinkAt(const Utils::FileName &filePath, const QTextCursor &cursor,
+ Utils::ProcessLinkCallback callback);
+
+ void projectAdded(ProjectExplorer::Project *project);
+ void projectRemoved(ProjectExplorer::Project *project);
+
+ QVector<BaseClient *> reachableClients();
+ void sendToAllReachableServers(const LanguageServerProtocol::IContent &content);
+
+ void clientFinished(BaseClient *client);
+
+ QVector<BaseClient *> m_clients;
+ QHash<Utils::FileName, QHash<Core::Id, QVector<LanguageClientMark *>>> m_marks;
+ QHash<LanguageServerProtocol::MessageId, QList<BaseClient *>> m_exclusiveRequests;
+
+ friend class LanguageClientPlugin;
+};
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientplugin.cpp b/src/plugins/languageclient/languageclientplugin.cpp
new file mode 100644
index 0000000000..4fd3cedfcf
--- /dev/null
+++ b/src/plugins/languageclient/languageclientplugin.cpp
@@ -0,0 +1,41 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "languageclientplugin.h"
+
+namespace LanguageClient {
+
+bool LanguageClientPlugin::initialize(const QStringList & /*arguments*/, QString * /*errorString*/)
+{
+ return true;
+}
+
+void LanguageClientPlugin::extensionsInitialized()
+{
+ LanguageClientManager::init();
+ LanguageClientSettings::init();
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientplugin.h b/src/plugins/languageclient/languageclientplugin.h
new file mode 100644
index 0000000000..31a4c1ae99
--- /dev/null
+++ b/src/plugins/languageclient/languageclientplugin.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "languageclientmanager.h"
+#include "languageclientsettings.h"
+
+#include <extensionsystem/iplugin.h>
+
+namespace LanguageClient {
+
+class LanguageClientPlugin : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "LanguageClient.json")
+public:
+ LanguageClientPlugin() = default;
+
+ // IPlugin interface
+private:
+ bool initialize(const QStringList &arguments, QString *errorString) override;
+ void extensionsInitialized() override;
+
+private:
+ LanguageClientManager m_clientManager;
+};
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientsettings.cpp b/src/plugins/languageclient/languageclientsettings.cpp
new file mode 100644
index 0000000000..e1470edf05
--- /dev/null
+++ b/src/plugins/languageclient/languageclientsettings.cpp
@@ -0,0 +1,408 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "languageclientmanager.h"
+#include "languageclientsettings.h"
+#include "languageclient_global.h"
+
+#include <coreplugin/icore.h>
+#include <utils/algorithm.h>
+#include <utils/delegates.h>
+#include <languageserverprotocol/lsptypes.h>
+
+#include <QBoxLayout>
+#include <QComboBox>
+#include <QCoreApplication>
+#include <QFileInfo>
+#include <QHeaderView>
+#include <QPushButton>
+#include <QSettings>
+#include <QStyledItemDelegate>
+#include <QTreeView>
+
+constexpr char nameKey[] = "name";
+constexpr char enabledKey[] = "enabled";
+constexpr char languageKey[] = "language";
+constexpr char executableKey[] = "executable";
+constexpr char argumentsKey[] = "arguments";
+constexpr char settingsGroupKey[] = "LanguageClient";
+constexpr char clientsKey[] = "clients";
+
+namespace LanguageClient {
+
+class LanguageClientSettingsModel : public QAbstractTableModel
+{
+public:
+ LanguageClientSettingsModel() = default;
+
+ // QAbstractItemModel interface
+ int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const override { return m_settings.count(); }
+ int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const override { return ColumnCount; }
+ QVariant data(const QModelIndex &index, int role) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+ bool removeRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()) override;
+ bool insertRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()) override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+ void toSettings(QSettings *settings) const;
+ void fromSettings(QSettings *settings);
+
+ void applyChanges();
+
+ enum Columns {
+ DisplayNameColumn = 0,
+ EnabledColumn,
+ LanguageColumn,
+ ExecutableColumn,
+ ArgumentsColumn,
+ ColumnCount
+ };
+
+private:
+ QList<LanguageClientSettings> m_settings;
+};
+
+class LanguageClientSettingsPageWidget : public QWidget
+{
+public:
+ LanguageClientSettingsPageWidget(LanguageClientSettingsModel &settings);
+
+private:
+ LanguageClientSettingsModel &m_settings;
+ QTreeView *m_view;
+
+ void addItem();
+ void deleteItem();
+};
+
+class LanguageClientSettingsPage : public Core::IOptionsPage
+{
+public:
+ LanguageClientSettingsPage();
+ ~LanguageClientSettingsPage() override;
+
+ void init();
+
+ // IOptionsPage interface
+ QWidget *widget() override;
+ void apply() override;
+ void finish() override;
+
+private:
+ LanguageClientSettingsModel m_settings;
+ QPointer<LanguageClientSettingsPageWidget> m_widget;
+};
+
+class LanguageChooseDelegate : public QStyledItemDelegate
+{
+public:
+ QWidget *createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+ void setEditorData(QWidget *editor, const QModelIndex &index) const override;
+};
+
+QWidget *LanguageChooseDelegate::createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ Q_UNUSED(option);
+ Q_UNUSED(index);
+ auto editor = new QComboBox(parent);
+ editor->addItem(noLanguageFilter);
+ editor->addItems(LanguageServerProtocol::languageIds().values());
+ return editor;
+}
+
+void LanguageChooseDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+ if (auto comboBox = qobject_cast<QComboBox*>(editor))
+ comboBox->setCurrentText(index.data().toString());
+}
+
+LanguageClientSettingsPageWidget::LanguageClientSettingsPageWidget(LanguageClientSettingsModel &settings)
+ : m_settings(settings)
+ , m_view(new QTreeView())
+{
+ auto layout = new QHBoxLayout();
+ m_view->setModel(&m_settings);
+ m_view->header()->setStretchLastSection(true);
+ m_view->setRootIsDecorated(false);
+ m_view->setItemsExpandable(false);
+ m_view->setItemDelegateForColumn(LanguageClientSettingsModel::LanguageColumn, new LanguageChooseDelegate());
+ auto executableDelegate = new Utils::PathChooserDelegate();
+ executableDelegate->setExpectedKind(Utils::PathChooser::File);
+ executableDelegate->setHistoryCompleter("LanguageClient.ServerPathHistory");
+ m_view->setItemDelegateForColumn(LanguageClientSettingsModel::ExecutableColumn, executableDelegate);
+ auto buttonLayout = new QVBoxLayout();
+ auto addButton = new QPushButton(tr("&Add"));
+ connect(addButton, &QPushButton::pressed, this, &LanguageClientSettingsPageWidget::addItem);
+ auto deleteButton = new QPushButton(tr("&Delete"));
+ connect(deleteButton, &QPushButton::pressed, this, &LanguageClientSettingsPageWidget::deleteItem);
+
+ setLayout(layout);
+ layout->addWidget(m_view);
+ layout->addLayout(buttonLayout);
+ buttonLayout->addWidget(addButton);
+ buttonLayout->addWidget(deleteButton);
+ buttonLayout->addStretch(10);
+}
+
+void LanguageClientSettingsPageWidget::addItem()
+{
+ const int row = m_settings.rowCount();
+ m_settings.insertRows(row);
+}
+
+void LanguageClientSettingsPageWidget::deleteItem()
+{
+ auto index = m_view->currentIndex();
+ if (index.isValid())
+ m_settings.removeRows(index.row());
+}
+
+LanguageClientSettingsPage::LanguageClientSettingsPage()
+{
+ setId("LanguageClient.General");
+ setDisplayName(tr("General"));
+ setCategory(Constants::LANGUAGECLIENT_SETTINGS_CATEGORY);
+ setDisplayCategory(QCoreApplication::translate("LanguageClient",
+ Constants::LANGUAGECLIENT_SETTINGS_TR));
+ //setCategoryIcon( /* TODO */ );
+}
+
+LanguageClientSettingsPage::~LanguageClientSettingsPage()
+{
+ if (m_widget)
+ delete m_widget;
+}
+
+void LanguageClientSettingsPage::init()
+{
+ m_settings.fromSettings(Core::ICore::settings());
+ m_settings.applyChanges();
+}
+
+QWidget *LanguageClientSettingsPage::widget()
+{
+ if (!m_widget)
+ m_widget = new LanguageClientSettingsPageWidget(m_settings);
+ return m_widget;
+}
+
+void LanguageClientSettingsPage::apply()
+{
+ m_settings.toSettings(Core::ICore::settings());
+ m_settings.applyChanges();
+}
+
+void LanguageClientSettingsPage::finish()
+{
+ m_settings.fromSettings(Core::ICore::settings());
+}
+
+QVariant LanguageClientSettingsModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+ LanguageClientSettings setting = m_settings[index.row()];
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ switch (index.column()) {
+ case DisplayNameColumn: return setting.m_name;
+ case LanguageColumn: return setting.m_language;
+ case ExecutableColumn: return setting.m_executable;
+ case ArgumentsColumn: return setting.m_arguments.join(' ');
+ }
+ } else if (role == Qt::CheckStateRole && index.column() == EnabledColumn) {
+ return setting.m_enabled ? Qt::Checked : Qt::Unchecked;
+ }
+ return QVariant();
+}
+
+QVariant LanguageClientSettingsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
+ return QVariant();
+
+ switch (section) {
+ case DisplayNameColumn: return tr("Name");
+ case EnabledColumn: return tr("Enabled");
+ case LanguageColumn: return tr("Language");
+ case ExecutableColumn: return tr("Executable");
+ case ArgumentsColumn: return tr("Arguments");
+ }
+ return QVariant();
+}
+
+bool LanguageClientSettingsModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (row >= int(m_settings.size()))
+ return false;
+ const auto first = m_settings.begin() + row;
+ const int end = qMin(row + count - 1, int(m_settings.size()) - 1);
+ beginRemoveRows(parent, row, end);
+ m_settings.erase(first, first + count);
+ endRemoveRows();
+ return true;
+}
+
+bool LanguageClientSettingsModel::insertRows(int row, int count, const QModelIndex &parent)
+{
+ if (row > m_settings.size() || row < 0)
+ return false;
+ beginInsertRows(parent, row, row + count - 1);
+ for (int i = 0; i < count; ++i)
+ m_settings.insert(row + i, {});
+ endInsertRows();
+ return true;
+}
+
+bool LanguageClientSettingsModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid())
+ return false;
+ LanguageClientSettings &setting = m_settings[index.row()];
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ switch (index.column()) {
+ case DisplayNameColumn: setting.m_name = value.toString(); break;
+ case LanguageColumn: setting.m_language = value.toString(); break;
+ case ExecutableColumn: setting.m_executable = value.toString(); break;
+ case ArgumentsColumn: setting.m_arguments = value.toString().split(' '); break;
+ default:
+ return false;
+ }
+ emit dataChanged(index, index, { Qt::EditRole, Qt::DisplayRole });
+ return true;
+ }
+ if (role == Qt::CheckStateRole && index.column() == EnabledColumn) {
+ setting.m_enabled = value.toBool();
+ emit dataChanged(index, index, { Qt::CheckStateRole });
+ return true;
+ }
+ return false;
+}
+
+Qt::ItemFlags LanguageClientSettingsModel::flags(const QModelIndex &index) const
+{
+ const auto defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
+ if (index.column() == EnabledColumn)
+ return defaultFlags | Qt::ItemIsUserCheckable;
+ return defaultFlags;
+}
+
+void LanguageClientSettingsModel::toSettings(QSettings *settings) const
+{
+ settings->beginGroup(settingsGroupKey);
+ settings->setValue(clientsKey, Utils::transform(m_settings,
+ [](const LanguageClientSettings & setting){
+ return QVariant(setting.toMap());
+ }));
+ settings->endGroup();
+}
+
+void LanguageClientSettingsModel::fromSettings(QSettings *settings)
+{
+ settings->beginGroup(settingsGroupKey);
+ auto variants = settings->value(clientsKey).toList();
+ m_settings.reserve(variants.size());
+ m_settings = Utils::transform(variants, [](const QVariant& var){
+ return LanguageClientSettings::fromMap(var.toMap());
+ });
+ settings->endGroup();
+}
+
+void LanguageClientSettingsModel::applyChanges()
+{
+ const QVector<BaseClient *> interfaces(LanguageClientManager::clients());
+ QVector<BaseClient *> toShutdown;
+ QList<LanguageClientSettings> toStart = m_settings;
+ // check currently registered interfaces
+ for (auto interface : interfaces) {
+ auto setting = Utils::findOr(m_settings, LanguageClientSettings(), [interface](const LanguageClientSettings &setting){
+ return interface->matches(setting);
+ });
+ if (setting.isValid() && setting.m_enabled) {
+ toStart.removeAll(setting);
+ if (!interface->isSupportedLanguage(setting.m_language))
+ interface->setSupportedLanguages({setting.m_language});
+ } else {
+ toShutdown << interface;
+ }
+ }
+ for (auto interface : toShutdown)
+ interface->shutdown();
+ for (auto setting : toStart) {
+ if (setting.isValid() && setting.m_enabled) {
+ auto client = new StdIOClient(setting.m_executable, setting.m_arguments);
+ client->setName(setting.m_name);
+ if (setting.m_language != noLanguageFilter)
+ client->setSupportedLanguages({setting.m_language});
+ LanguageClientManager::startClient(client);
+ }
+ }
+}
+
+bool LanguageClientSettings::isValid()
+{
+ return !m_name.isEmpty() && !m_executable.isEmpty() && QFile::exists(m_executable);
+}
+
+bool LanguageClientSettings::operator==(const LanguageClientSettings &other) const
+{
+ return m_name == other.m_name
+ && m_enabled == other.m_enabled
+ && m_language == other.m_language
+ && m_executable == other.m_executable
+ && m_arguments == other.m_arguments;
+}
+
+QVariantMap LanguageClientSettings::toMap() const
+{
+ QVariantMap map;
+ map.insert(nameKey, m_name);
+ map.insert(enabledKey, m_enabled);
+ map.insert(languageKey, m_language);
+ map.insert(executableKey, m_executable);
+ map.insert(argumentsKey, m_arguments);
+ return map;
+}
+
+LanguageClientSettings LanguageClientSettings::fromMap(const QVariantMap &map)
+{
+ return { map[nameKey].toString(),
+ map[enabledKey].toBool(),
+ map[languageKey].toString(),
+ map[executableKey].toString(),
+ map[argumentsKey].toStringList() };
+}
+
+void LanguageClientSettings::init()
+{
+ static LanguageClientSettingsPage settingsPage;
+ settingsPage.init();
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientsettings.h b/src/plugins/languageclient/languageclientsettings.h
new file mode 100644
index 0000000000..d06481e763
--- /dev/null
+++ b/src/plugins/languageclient/languageclientsettings.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <coreplugin/dialogs/ioptionspage.h>
+
+#include <QAbstractItemModel>
+#include <QPointer>
+#include <QWidget>
+
+namespace LanguageClient {
+
+constexpr char noLanguageFilter[] = "No Filter";
+
+class LanguageClientSettings
+{
+public:
+ LanguageClientSettings() = default;
+ LanguageClientSettings(const QString &name, bool enabled, const QString &language,
+ const QString &executable, const QStringList &arguments)
+ : m_name(name)
+ , m_enabled(enabled)
+ , m_language(language)
+ , m_executable(executable)
+ , m_arguments(arguments)
+ {}
+ QString m_name = QString("New Language Server");
+ bool m_enabled = true;
+ QString m_language = QLatin1String(noLanguageFilter);
+ QString m_executable;
+ QStringList m_arguments;
+
+ bool isValid();
+
+ bool operator==(const LanguageClientSettings &other) const;
+
+ QVariantMap toMap() const;
+ static LanguageClientSettings fromMap(const QVariantMap &map);
+ static void init();
+};
+
+
+} // namespace LanguageClient
diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro
index a540713d7a..1f1cf99648 100644
--- a/src/plugins/plugins.pro
+++ b/src/plugins/plugins.pro
@@ -55,7 +55,8 @@ SUBDIRS = \
updateinfo \
scxmleditor \
welcome \
- silversearcher
+ silversearcher \
+ languageclient
qtHaveModule(serialport) {
SUBDIRS += serialterminal
diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs
index 17af43d2ba..9217c9e284 100644
--- a/src/plugins/plugins.qbs
+++ b/src/plugins/plugins.qbs
@@ -39,6 +39,7 @@ Project {
"help/help.qbs",
"imageviewer/imageviewer.qbs",
"ios/ios.qbs",
+ "languageclient/languageclient.qbs",
"macros/macros.qbs",
"mercurial/mercurial.qbs",
"modeleditor/modeleditor.qbs",
diff --git a/src/plugins/pythoneditor/pythoneditor.cpp b/src/plugins/pythoneditor/pythoneditor.cpp
index c3a608e8d3..4c8b95ad92 100644
--- a/src/plugins/pythoneditor/pythoneditor.cpp
+++ b/src/plugins/pythoneditor/pythoneditor.cpp
@@ -49,8 +49,9 @@ PythonEditorFactory::PythonEditorFactory()
addMimeType(Constants::C_PY_MIMETYPE);
setEditorActionHandlers(TextEditorActionHandler::Format
- | TextEditorActionHandler::UnCommentSelection
- | TextEditorActionHandler::UnCollapseAll);
+ | TextEditorActionHandler::UnCommentSelection
+ | TextEditorActionHandler::UnCollapseAll
+ | TextEditorActionHandler::FollowSymbolUnderCursor);
setDocumentCreator([] { return new TextDocument(Constants::C_PYTHONEDITOR_ID); });
setIndenterCreator([] { return new PythonIndenter; });
diff --git a/src/plugins/texteditor/codeassist/codeassistant.cpp b/src/plugins/texteditor/codeassist/codeassistant.cpp
index 0c57dab5c7..cede626ea3 100644
--- a/src/plugins/texteditor/codeassist/codeassistant.cpp
+++ b/src/plugins/texteditor/codeassist/codeassistant.cpp
@@ -207,6 +207,7 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason,
return;
m_assistKind = kind;
+ m_requestProvider = provider;
IAssistProcessor *processor = provider->createProcessor();
switch (provider->runType()) {
@@ -220,7 +221,6 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason,
if (IAssistProposal *newProposal = processor->immediateProposal(assistInterface))
displayProposal(newProposal, reason);
- m_requestProvider = provider;
m_requestRunner = new ProcessorRunner;
m_runnerConnection = connect(m_requestRunner, &ProcessorRunner::finished,
this, [this, reason](){
@@ -433,10 +433,13 @@ void CodeAssistantPrivate::notifyChange()
QTC_ASSERT(m_proposal, return);
if (m_editorWidget->position() < m_proposal->basePosition()) {
destroyContext();
- } else {
+ } else if (!m_proposal->isFragile()) {
m_proposalWidget->updateProposal(
m_editorWidget->textAt(m_proposal->basePosition(),
m_editorWidget->position() - m_proposal->basePosition()));
+ } else {
+ destroyContext();
+ requestProposal(ExplicitlyInvoked, m_assistKind, m_requestProvider);
}
}
}
diff --git a/src/plugins/texteditor/plaintexteditorfactory.cpp b/src/plugins/texteditor/plaintexteditorfactory.cpp
index b0932bf3b5..3e39cf810a 100644
--- a/src/plugins/texteditor/plaintexteditorfactory.cpp
+++ b/src/plugins/texteditor/plaintexteditorfactory.cpp
@@ -69,8 +69,9 @@ PlainTextEditorFactory::PlainTextEditorFactory()
setUseGenericHighlighter(true);
setEditorActionHandlers(TextEditorActionHandler::Format |
- TextEditorActionHandler::UnCommentSelection |
- TextEditorActionHandler::UnCollapseAll);
+ TextEditorActionHandler::UnCommentSelection |
+ TextEditorActionHandler::UnCollapseAll |
+ TextEditorActionHandler::FollowSymbolUnderCursor);
}
PlainTextEditorFactory *PlainTextEditorFactory::instance()
diff --git a/src/plugins/texteditor/textdocument.h b/src/plugins/texteditor/textdocument.h
index 8a4e05a4f7..3b526c24a7 100644
--- a/src/plugins/texteditor/textdocument.h
+++ b/src/plugins/texteditor/textdocument.h
@@ -29,6 +29,7 @@
#include <coreplugin/id.h>
#include <coreplugin/textdocument.h>
+#include <utils/link.h>
#include <QList>
#include <QMap>
diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp
index 5e3d952db7..9e02bd440c 100644
--- a/src/plugins/texteditor/texteditor.cpp
+++ b/src/plugins/texteditor/texteditor.cpp
@@ -6187,8 +6187,12 @@ void TextEditorWidget::zoomReset()
showZoomIndicator(this, 100);
}
-void TextEditorWidget::findLinkAt(const QTextCursor &, Utils::ProcessLinkCallback &&, bool, bool)
+void TextEditorWidget::findLinkAt(const QTextCursor &cursor,
+ Utils::ProcessLinkCallback &&callback,
+ bool resolveTarget,
+ bool inNextSplit)
{
+ emit requestLinkAt(cursor, callback, resolveTarget, inNextSplit);
}
bool TextEditorWidget::openLink(const Utils::Link &link, bool inNextSplit)
@@ -8394,6 +8398,15 @@ BaseTextEditor *BaseTextEditor::currentTextEditor()
return qobject_cast<BaseTextEditor *>(EditorManager::currentEditor());
}
+BaseTextEditor *BaseTextEditor::textEditorForDocument(TextDocument *textDocument)
+{
+ for (IEditor *editor : Core::DocumentModel::editorsForDocument(textDocument)) {
+ if (auto textEditor = qobject_cast<BaseTextEditor *>(editor))
+ return textEditor;
+ }
+ return nullptr;
+}
+
TextEditorWidget *BaseTextEditor::editorWidget() const
{
QTC_ASSERT(qobject_cast<TextEditorWidget *>(m_widget.data()), return nullptr);
diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h
index 8025f41147..e11517c530 100644
--- a/src/plugins/texteditor/texteditor.h
+++ b/src/plugins/texteditor/texteditor.h
@@ -108,6 +108,7 @@ public:
virtual void finalizeInitialization() {}
static BaseTextEditor *currentTextEditor();
+ static BaseTextEditor *textEditorForDocument(TextDocument *textDocument);
TextEditorWidget *editorWidget() const;
TextDocument *textDocument() const;
@@ -472,6 +473,9 @@ signals:
void requestBlockUpdate(const QTextBlock &);
+ void requestLinkAt(const QTextCursor &cursor, Utils::ProcessLinkCallback &callback,
+ bool resolveTarget, bool inNextSplit);
+
protected:
QTextBlock blockForVisibleRow(int row) const;
QTextBlock blockForVerticalOffset(int offset) const;