aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/languageclient
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/languageclient')
-rw-r--r--src/plugins/languageclient/CMakeLists.txt21
-rw-r--r--src/plugins/languageclient/LanguageClient.json.in1
-rw-r--r--src/plugins/languageclient/client.cpp294
-rw-r--r--src/plugins/languageclient/client.h26
-rw-r--r--src/plugins/languageclient/documentsymbolcache.cpp81
-rw-r--r--src/plugins/languageclient/documentsymbolcache.h58
-rw-r--r--src/plugins/languageclient/images/languageclient.pngbin0 -> 199 bytes
-rw-r--r--src/plugins/languageclient/images/languageclient@2x.pngbin0 -> 360 bytes
-rw-r--r--src/plugins/languageclient/languageclient.pro12
-rw-r--r--src/plugins/languageclient/languageclient.qbs8
-rw-r--r--src/plugins/languageclient/languageclient.qrc2
-rw-r--r--src/plugins/languageclient/languageclient_global.h9
-rw-r--r--src/plugins/languageclient/languageclientcompletionassist.cpp37
-rw-r--r--src/plugins/languageclient/languageclientfunctionhint.cpp144
-rw-r--r--src/plugins/languageclient/languageclientfunctionhint.h53
-rw-r--r--src/plugins/languageclient/languageclienthoverhandler.cpp148
-rw-r--r--src/plugins/languageclient/languageclienthoverhandler.h59
-rw-r--r--src/plugins/languageclient/languageclientinterface.cpp13
-rw-r--r--src/plugins/languageclient/languageclientmanager.cpp270
-rw-r--r--src/plugins/languageclient/languageclientmanager.h26
-rw-r--r--src/plugins/languageclient/languageclientoutline.cpp88
-rw-r--r--src/plugins/languageclient/languageclientplugin.cpp23
-rw-r--r--src/plugins/languageclient/languageclientplugin.h6
-rw-r--r--src/plugins/languageclient/languageclientquickfix.cpp2
-rw-r--r--src/plugins/languageclient/languageclientsettings.cpp253
-rw-r--r--src/plugins/languageclient/languageclientsettings.h52
-rw-r--r--src/plugins/languageclient/languageclientutils.cpp86
-rw-r--r--src/plugins/languageclient/languageclientutils.h4
-rw-r--r--src/plugins/languageclient/locatorfilter.cpp316
-rw-r--r--src/plugins/languageclient/locatorfilter.h119
30 files changed, 1918 insertions, 293 deletions
diff --git a/src/plugins/languageclient/CMakeLists.txt b/src/plugins/languageclient/CMakeLists.txt
new file mode 100644
index 00000000000..36040c1f5ce
--- /dev/null
+++ b/src/plugins/languageclient/CMakeLists.txt
@@ -0,0 +1,21 @@
+add_qtc_plugin(LanguageClient
+ DEPENDS LanguageServerProtocol Qt5::Core
+ PLUGIN_DEPENDS ProjectExplorer Core TextEditor
+ SOURCES
+ client.cpp client.h
+ documentsymbolcache.cpp documentsymbolcache.h
+ dynamiccapabilities.cpp dynamiccapabilities.h
+ languageclient.qrc
+ languageclientcompletionassist.cpp languageclientcompletionassist.h
+ languageclientfunctionhint.cpp languageclientfunctionhint.h
+ languageclienthoverhandler.cpp languageclienthoverhandler.h
+ languageclientinterface.cpp languageclientinterface.h
+ languageclientmanager.cpp languageclientmanager.h
+ languageclientoutline.cpp languageclientoutline.h
+ languageclientplugin.cpp languageclientplugin.h
+ languageclientquickfix.cpp languageclientquickfix.h
+ languageclientsettings.cpp languageclientsettings.h
+ languageclientutils.cpp languageclientutils.h
+ languageclient_global.h
+ locatorfilter.cpp locatorfilter.h
+)
diff --git a/src/plugins/languageclient/LanguageClient.json.in b/src/plugins/languageclient/LanguageClient.json.in
index c48a74c937c..8eea5c88cc6 100644
--- a/src/plugins/languageclient/LanguageClient.json.in
+++ b/src/plugins/languageclient/LanguageClient.json.in
@@ -2,7 +2,6 @@
\"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\",
diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp
index fc9df47ff76..3acea150800 100644
--- a/src/plugins/languageclient/client.cpp
+++ b/src/plugins/languageclient/client.cpp
@@ -36,12 +36,13 @@
#include <languageserverprotocol/languagefeatures.h>
#include <languageserverprotocol/messages.h>
#include <languageserverprotocol/workspace.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/session.h>
+#include <texteditor/codeassist/documentcontentcompletion.h>
#include <texteditor/semantichighlighter.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <texteditor/textmark.h>
-#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
#include <utils/mimetypes/mimedatabase.h>
#include <utils/qtcprocess.h>
#include <utils/synchronousprocess.h>
@@ -67,7 +68,7 @@ static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client", QtWarningMs
class TextMark : public TextEditor::TextMark
{
public:
- TextMark(const Utils::FileName &fileName, const Diagnostic &diag)
+ TextMark(const Utils::FilePath &fileName, const Diagnostic &diag)
: TextEditor::TextMark(fileName, diag.range().start().line() + 1, "lspmark")
, m_diagnostic(diag)
{
@@ -92,8 +93,11 @@ private:
Client::Client(BaseClientInterface *clientInterface)
: m_id(Core::Id::fromString(QUuid::createUuid().toString()))
, m_completionProvider(this)
+ , m_functionHintProvider(this)
, m_quickFixProvider(this)
, m_clientInterface(clientInterface)
+ , m_documentSymbolCache(this)
+ , m_hoverHandler(this)
{
m_contentHandler.insert(JsonRpcMessageHandler::jsonRpcMimeType(),
&JsonRpcMessageHandler::parseContent);
@@ -103,23 +107,121 @@ Client::Client(BaseClientInterface *clientInterface)
connect(clientInterface, &BaseClientInterface::finished, this, &Client::finished);
}
+static void updateEditorToolBar(QList<Utils::FilePath> files)
+{
+ QList<Core::IEditor *> editors = Core::DocumentModel::editorsForDocuments(
+ Utils::transform(files, [](Utils::FilePath &file) {
+ return Core::DocumentModel::documentForFilePath(file.toString());
+ }));
+ for (auto editor : editors)
+ updateEditorToolBar(editor);
+}
+
Client::~Client()
{
using namespace TextEditor;
// FIXME: instead of replacing the completion provider in the text document store the
// completion provider as a prioritised list in the text document
- for (TextDocument *document : m_resetAssistProvider) {
- document->setCompletionAssistProvider(nullptr);
+ for (TextDocument *document : m_resetAssistProvider.keys()) {
+ if (document->completionAssistProvider() == &m_completionProvider)
+ document->setCompletionAssistProvider(m_resetAssistProvider[document]);
+ document->setFunctionHintAssistProvider(nullptr);
document->setQuickFixAssistProvider(nullptr);
}
for (Core::IEditor * editor : Core::DocumentModel::editorsForOpenedDocuments()) {
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor)) {
TextEditorWidget *widget = textEditor->editorWidget();
widget->setRefactorMarkers(RefactorMarker::filterOutType(widget->refactorMarkers(), id()));
+ widget->removeHoverHandler(&m_hoverHandler);
}
}
for (const DocumentUri &uri : m_diagnostics.keys())
removeDiagnostics(uri);
+ updateEditorToolBar(m_openedDocument.keys());
+}
+
+static ClientCapabilities generateClientCapabilities()
+{
+ ClientCapabilities capabilities;
+ WorkspaceClientCapabilities workspaceCapabilities;
+ workspaceCapabilities.setWorkspaceFolders(true);
+ workspaceCapabilities.setApplyEdit(true);
+ DynamicRegistrationCapabilities allowDynamicRegistration;
+ allowDynamicRegistration.setDynamicRegistration(true);
+ workspaceCapabilities.setDidChangeConfiguration(allowDynamicRegistration);
+ workspaceCapabilities.setExecuteCommand(allowDynamicRegistration);
+ capabilities.setWorkspace(workspaceCapabilities);
+
+ TextDocumentClientCapabilities documentCapabilities;
+ TextDocumentClientCapabilities::SynchronizationCapabilities syncCapabilities;
+ syncCapabilities.setDynamicRegistration(true);
+ syncCapabilities.setWillSave(true);
+ syncCapabilities.setWillSaveWaitUntil(false);
+ syncCapabilities.setDidSave(true);
+ documentCapabilities.setSynchronization(syncCapabilities);
+
+ SymbolCapabilities symbolCapabilities;
+ SymbolCapabilities::SymbolKindCapabilities symbolKindCapabilities;
+ symbolKindCapabilities.setValueSet(
+ {SymbolKind::File, SymbolKind::Module, SymbolKind::Namespace,
+ SymbolKind::Package, SymbolKind::Class, SymbolKind::Method,
+ SymbolKind::Property, SymbolKind::Field, SymbolKind::Constructor,
+ SymbolKind::Enum, SymbolKind::Interface, SymbolKind::Function,
+ SymbolKind::Variable, SymbolKind::Constant, SymbolKind::String,
+ SymbolKind::Number, SymbolKind::Boolean, SymbolKind::Array,
+ SymbolKind::Object, SymbolKind::Key, SymbolKind::Null,
+ SymbolKind::EnumMember, SymbolKind::Struct, SymbolKind::Event,
+ SymbolKind::Operator, SymbolKind::TypeParameter});
+ symbolCapabilities.setSymbolKind(symbolKindCapabilities);
+ documentCapabilities.setDocumentSymbol(symbolCapabilities);
+
+ TextDocumentClientCapabilities::CompletionCapabilities completionCapabilities;
+ completionCapabilities.setDynamicRegistration(true);
+ TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities
+ completionItemKindCapabilities;
+ completionItemKindCapabilities.setValueSet(
+ {CompletionItemKind::Text, CompletionItemKind::Method,
+ CompletionItemKind::Function, CompletionItemKind::Constructor,
+ CompletionItemKind::Field, CompletionItemKind::Variable,
+ CompletionItemKind::Class, CompletionItemKind::Interface,
+ CompletionItemKind::Module, CompletionItemKind::Property,
+ CompletionItemKind::Unit, CompletionItemKind::Value,
+ CompletionItemKind::Enum, CompletionItemKind::Keyword,
+ CompletionItemKind::Snippet, CompletionItemKind::Color,
+ CompletionItemKind::File, CompletionItemKind::Reference,
+ CompletionItemKind::Folder, CompletionItemKind::EnumMember,
+ CompletionItemKind::Constant, CompletionItemKind::Struct,
+ CompletionItemKind::Event, CompletionItemKind::Operator,
+ CompletionItemKind::TypeParameter});
+ completionCapabilities.setCompletionItemKind(completionItemKindCapabilities);
+ TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities
+ completionItemCapbilities;
+ completionItemCapbilities.setSnippetSupport(false);
+ completionItemCapbilities.setCommitCharacterSupport(true);
+ completionCapabilities.setCompletionItem(completionItemCapbilities);
+ documentCapabilities.setCompletion(completionCapabilities);
+
+ TextDocumentClientCapabilities::CodeActionCapabilities codeActionCapabilities;
+ TextDocumentClientCapabilities::CodeActionCapabilities::CodeActionLiteralSupport literalSupport;
+ literalSupport.setCodeActionKind(
+ TextDocumentClientCapabilities::CodeActionCapabilities::CodeActionLiteralSupport::
+ CodeActionKind(QList<QString>{"*"}));
+ codeActionCapabilities.setCodeActionLiteralSupport(literalSupport);
+ documentCapabilities.setCodeAction(codeActionCapabilities);
+
+ TextDocumentClientCapabilities::HoverCapabilities hover;
+ hover.setContentFormat({MarkupKind::plaintext});
+ hover.setDynamicRegistration(true);
+ documentCapabilities.setHover(hover);
+
+ documentCapabilities.setReferences(allowDynamicRegistration);
+ documentCapabilities.setDocumentHighlight(allowDynamicRegistration);
+ documentCapabilities.setDefinition(allowDynamicRegistration);
+ documentCapabilities.setTypeDefinition(allowDynamicRegistration);
+ documentCapabilities.setImplementation(allowDynamicRegistration);
+ capabilities.setTextDocument(documentCapabilities);
+
+ return capabilities;
}
void Client::initialize()
@@ -129,15 +231,15 @@ void Client::initialize()
QTC_ASSERT(m_state == Uninitialized, return);
qCDebug(LOGLSPCLIENT) << "initializing language server " << m_displayName;
auto initRequest = new InitializeRequest();
- if (auto startupProject = SessionManager::startupProject()) {
- auto params = initRequest->params().value_or(InitializeParams());
- params.setRootUri(DocumentUri::fromFileName(startupProject->projectDirectory()));
- initRequest->setParams(params);
+ auto params = initRequest->params().value_or(InitializeParams());
+ params.setCapabilities(generateClientCapabilities());
+ if (m_project) {
+ params.setRootUri(DocumentUri::fromFileName(m_project->projectDirectory()));
params.setWorkSpaceFolders(Utils::transform(SessionManager::projects(), [](Project *pro){
return WorkSpaceFolder(pro->projectDirectory().toString(), pro->displayName());
}));
- initRequest->setParams(params);
}
+ initRequest->setParams(params);
initRequest->setResponseCallback([this](const InitializeRequest::Response &initResponse){
intializeCallback(initResponse);
});
@@ -164,27 +266,27 @@ Client::State Client::state() const
return m_state;
}
-void Client::openDocument(Core::IDocument *document)
+bool Client::openDocument(Core::IDocument *document)
{
using namespace TextEditor;
if (!isSupportedDocument(document))
- return;
- const FileName &filePath = document->filePath();
+ return false;
+ const FilePath &filePath = document->filePath();
const QString method(DidOpenTextDocumentNotification::methodName);
if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
if (!registered.value())
- return;
+ return false;
const TextDocumentRegistrationOptions option(
m_dynamicCapabilities.option(method).toObject());
if (option.isValid(nullptr)
&& !option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType()))) {
- return;
+ return false;
}
} 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;
+ return false;
}
}
auto uri = DocumentUri::fromFileName(filePath);
@@ -196,24 +298,46 @@ void Client::openDocument(Core::IDocument *document)
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->completionAssistProvider();
- m_resetAssistProvider << textDocument;
+ connect(textDocument,
+ &TextEditor::TextDocument::contentsChangedWithPosition,
+ this,
+ [this, textDocument](int position, int charsRemoved, int charsAdded) {
+ documentContentsChanged(textDocument, position, charsRemoved, charsAdded);
+ });
+ auto *oldCompletionProvider = qobject_cast<TextEditor::DocumentContentCompletionProvider *>(
+ textDocument->completionAssistProvider());
+ if (oldCompletionProvider || !textDocument->completionAssistProvider()) {
+ // only replace the completion assist provider if it is the default one or null
+ m_completionProvider.setTriggerCharacters(
+ m_serverCapabilities.completionProvider()
+ .value_or(ServerCapabilities::CompletionOptions())
+ .triggerCharacters()
+ .value_or(QList<QString>()));
+ textDocument->setCompletionAssistProvider(&m_completionProvider);
+ }
+ m_resetAssistProvider[textDocument] = oldCompletionProvider;
+ m_functionHintProvider.setTriggerCharacters(
+ m_serverCapabilities.signatureHelpProvider()
+ .value_or(ServerCapabilities::SignatureHelpOptions())
+ .triggerCharacters()
+ .value_or(QList<QString>()));
textDocument->setCompletionAssistProvider(&m_completionProvider);
+ textDocument->setFunctionHintAssistProvider(&m_functionHintProvider);
textDocument->setQuickFixAssistProvider(&m_quickFixProvider);
connect(textDocument, &QObject::destroyed, this, [this, textDocument]{
m_resetAssistProvider.remove(textDocument);
});
+ m_openedDocument.insert(document->filePath(), textDocument->plainText());
+ } else {
+ m_openedDocument.insert(document->filePath(), QString());
}
- m_openedDocument.append(document->filePath());
sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
if (textDocument)
requestDocumentSymbols(textDocument);
+
+ return true;
}
void Client::sendContent(const IContent &content)
@@ -245,6 +369,11 @@ void Client::closeDocument(const DidCloseTextDocumentParams &params)
sendContent(params.textDocument().uri(), DidCloseTextDocumentNotification(params));
}
+bool Client::documentOpen(const Core::IDocument *document) const
+{
+ return m_openedDocument.contains(document->filePath());
+}
+
void Client::documentContentsSaved(Core::IDocument *document)
{
if (!m_openedDocument.contains(document->filePath()))
@@ -281,7 +410,7 @@ void Client::documentContentsSaved(Core::IDocument *document)
void Client::documentWillSave(Core::IDocument *document)
{
- const FileName &filePath = document->filePath();
+ const FilePath &filePath = document->filePath();
if (!m_openedDocument.contains(filePath))
return;
bool sendMessage = true;
@@ -307,7 +436,10 @@ void Client::documentWillSave(Core::IDocument *document)
sendContent(WillSaveTextDocumentNotification(params));
}
-void Client::documentContentsChanged(Core::IDocument *document)
+void Client::documentContentsChanged(TextEditor::TextDocument *document,
+ int position,
+ int charsRemoved,
+ int charsAdded)
{
if (!m_openedDocument.contains(document->filePath()))
return;
@@ -327,7 +459,21 @@ void Client::documentContentsChanged(Core::IDocument *document)
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()));
+ DidChangeTextDocumentParams params;
+ params.setTextDocument(docId);
+ if (syncKind == TextDocumentSyncKind::Incremental) {
+ DidChangeTextDocumentParams::TextDocumentContentChangeEvent change;
+ QTextDocument oldDoc(m_openedDocument[document->filePath()]);
+ QTextCursor cursor(&oldDoc);
+ cursor.setPosition(position + charsRemoved);
+ cursor.setPosition(position, QTextCursor::KeepAnchor);
+ change.setRange(Range(cursor));
+ change.setText(document->textAt(position, charsAdded));
+ params.setContentChanges({change});
+ } else {
+ params.setContentChanges({document->plainText()});
+ }
+ m_openedDocument[document->filePath()] = document->plainText();
sendContent(DidChangeTextDocumentNotification(params));
}
@@ -364,7 +510,7 @@ static bool sendTextDocumentPositionParamsRequest(Client *client,
if (sendMessage) {
const TextDocumentRegistrationOptions option(dynamicCapabilities.option(Request::methodName));
if (option.isValid(nullptr))
- sendMessage = option.filterApplies(FileName::fromString(QUrl(uri).adjusted(QUrl::PreferLocalFile).toString()));
+ sendMessage = option.filterApplies(FilePath::fromString(QUrl(uri).adjusted(QUrl::PreferLocalFile).toString()));
else
sendMessage = supportedFile;
} else {
@@ -400,7 +546,7 @@ void Client::requestDocumentSymbols(TextEditor::TextDocument *document)
{
// TODO: Do not use this information for highlighting but the overview model
return;
- const FileName &filePath = document->filePath();
+ const FilePath &filePath = document->filePath();
bool sendMessage = m_dynamicCapabilities.isRegistered(DocumentSymbolsRequest::methodName).value_or(false);
if (sendMessage) {
const TextDocumentRegistrationOptions option(m_dynamicCapabilities.option(DocumentSymbolsRequest::methodName));
@@ -542,7 +688,7 @@ void Client::cursorPositionChanged(TextEditor::TextEditorWidget *widget)
void Client::requestCodeActions(const DocumentUri &uri, const QList<Diagnostic> &diagnostics)
{
- const Utils::FileName fileName = uri.toFileName();
+ const Utils::FilePath fileName = uri.toFileName();
TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFileName(fileName);
if (!doc)
return;
@@ -570,7 +716,7 @@ void Client::requestCodeActions(const CodeActionRequest &request)
if (!request.isValid(nullptr))
return;
- const Utils::FileName fileName
+ const Utils::FilePath fileName
= request.params().value_or(CodeActionParams()).textDocument().uri().toFileName();
const QString method(CodeActionRequest::methodName);
@@ -631,6 +777,16 @@ void Client::executeCommand(const Command &command)
sendContent(request);
}
+const ProjectExplorer::Project *Client::project() const
+{
+ return m_project;
+}
+
+void Client::setCurrentProject(ProjectExplorer::Project *project)
+{
+ m_project = project;
+}
+
void Client::projectOpened(ProjectExplorer::Project *project)
{
if (!sendWorkspceFolderChanges())
@@ -645,10 +801,19 @@ void Client::projectOpened(ProjectExplorer::Project *project)
void Client::projectClosed(ProjectExplorer::Project *project)
{
+ if (project == m_project) {
+ if (m_state == Initialized) {
+ shutdown();
+ } else {
+ m_state = Shutdown; // otherwise the manager would try to restart this server
+ emit finished();
+ }
+ }
if (!sendWorkspceFolderChanges())
return;
WorkspaceFoldersChangeEvent event;
- event.setRemoved({WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())});
+ event.setRemoved(
+ {WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())});
DidChangeWorkspaceFoldersParams params;
params.setEvent(event);
DidChangeWorkspaceFoldersNotification change(params);
@@ -663,27 +828,18 @@ void Client::setSupportedLanguage(const LanguageFilter &filter)
bool Client::isSupportedDocument(const Core::IDocument *document) const
{
QTC_ASSERT(document, return false);
- return isSupportedFile(document->filePath(), document->mimeType());
+ return m_languagFilter.isSupported(document);
}
-bool Client::isSupportedFile(const Utils::FileName &filePath, const QString &mimeType) const
+bool Client::isSupportedFile(const Utils::FilePath &filePath, const QString &mimeType) const
{
- if (m_languagFilter.mimeTypes.isEmpty() && m_languagFilter.filePattern.isEmpty())
- return true;
- if (m_languagFilter.mimeTypes.contains(mimeType))
- return true;
- auto regexps = Utils::transform(m_languagFilter.filePattern, [](const QString &pattern){
- return QRegExp(pattern, Utils::HostOsInfo::fileNameCaseSensitivity(), QRegExp::Wildcard);
- });
- return Utils::anyOf(regexps, [filePath](const QRegExp &reg){
- return reg.exactMatch(filePath.toString()) || reg.exactMatch(filePath.fileName());
- });
+ return m_languagFilter.isSupported(filePath, mimeType);
}
bool Client::isSupportedUri(const DocumentUri &uri) const
{
- return isSupportedFile(uri.toFileName(),
- Utils::mimeTypeForFile(uri.toFileName().fileName()).name());
+ return m_languagFilter.isSupported(uri.toFileName(),
+ Utils::mimeTypeForFile(uri.toFileName().fileName()).name());
}
bool Client::needsRestart(const BaseSettings *settings) const
@@ -717,9 +873,11 @@ bool Client::reset()
m_state = Uninitialized;
m_responseHandlers.clear();
m_clientInterface->resetBuffer();
+ updateEditorToolBar(m_openedDocument.keys());
m_openedDocument.clear();
m_serverCapabilities = ServerCapabilities();
m_dynamicCapabilities.reset();
+ m_project = nullptr;
for (const DocumentUri &uri : m_diagnostics.keys())
removeDiagnostics(uri);
return true;
@@ -769,6 +927,16 @@ const BaseClientInterface *Client::clientInterface() const
return m_clientInterface.data();
}
+DocumentSymbolCache *Client::documentSymbolCache()
+{
+ return &m_documentSymbolCache;
+}
+
+HoverHandler *Client::hoverHandler()
+{
+ return &m_hoverHandler;
+}
+
void Client::log(const ShowMessageParams &message,
Core::MessageManager::PrintToOutputPaneFlag flag)
{
@@ -793,8 +961,7 @@ void Client::showMessageBox(const ShowMessageRequestParams &message, const Messa
}
box->setModal(true);
connect(box, &QMessageBox::finished, this, [=]{
- ShowMessageRequest::Response response;
- response.setId(id);
+ ShowMessageRequest::Response response(id);
const MessageActionItem &item = itemForButton.value(box->clickedButton());
response.setResult(item.isValid(nullptr) ? LanguageClientValue<MessageActionItem>(item)
: LanguageClientValue<MessageActionItem>());
@@ -856,8 +1023,7 @@ void Client::handleMethod(const QString &method, MessageId id, const IContent *c
if (paramsValid) {
showMessageBox(params, request->id());
} else {
- ShowMessageRequest::Response response;
- response.setId(request->id());
+ ShowMessageRequest::Response response(request->id());
ResponseError<std::nullptr_t> error;
const QString errorMessage =
QString("Could not parse ShowMessageRequest parameter of '%1': \"%2\"")
@@ -882,9 +1048,23 @@ void Client::handleMethod(const QString &method, MessageId id, const IContent *c
paramsValid = params.isValid(&error);
if (paramsValid)
applyWorkspaceEdit(params.edit());
+ } else if (method == WorkSpaceFolderRequest::methodName) {
+ WorkSpaceFolderRequest::Response response(dynamic_cast<const WorkSpaceFolderRequest *>(content)->id());
+ const QList<ProjectExplorer::Project *> projects
+ = ProjectExplorer::SessionManager::projects();
+ WorkSpaceFolderResult result;
+ if (projects.isEmpty()) {
+ result = nullptr;
+ } else {
+ result = Utils::transform(projects, [](ProjectExplorer::Project *project) {
+ return WorkSpaceFolder(project->projectDirectory().toString(),
+ project->displayName());
+ });
+ }
+ response.setResult(result);
+ sendContent(response);
} else if (id.isValid(&error)) {
- Response<JsonObject, JsonObject> response;
- response.setId(id);
+ Response<JsonObject, JsonObject> response(id);
ResponseError<JsonObject> error;
error.setCode(ResponseError<JsonObject>::MethodNotFound);
response.setError(error);
@@ -949,9 +1129,17 @@ void Client::intializeCallback(const InitializeRequest::Response &initResponse)
qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " initialized";
m_state = Initialized;
sendContent(InitializeNotification());
+ for (auto openedDocument : Core::DocumentModel::openedDocuments()) {
+ if (openDocument(openedDocument)) {
+ for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(openedDocument))
+ updateEditorToolBar(editor);
+ }
+ }
+ for (Core::IEditor *editor : Core::DocumentModel::editorsForOpenedDocuments()) {
+ if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor))
+ textEditor->editorWidget()->addHoverHandler(&m_hoverHandler);
+ }
emit initialized(m_serverCapabilities);
- for (auto openedDocument : Core::DocumentModel::openedDocuments())
- openDocument(openedDocument);
}
void Client::shutDownCallback(const ShutdownRequest::Response &shutdownResponse)
diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h
index 635512fa287..9746607e627 100644
--- a/src/plugins/languageclient/client.h
+++ b/src/plugins/languageclient/client.h
@@ -25,10 +25,13 @@
#pragma once
+#include "documentsymbolcache.h"
#include "dynamiccapabilities.h"
#include "languageclientcompletionassist.h"
+#include "languageclientfunctionhint.h"
#include "languageclientquickfix.h"
#include "languageclientsettings.h"
+#include "languageclienthoverhandler.h"
#include <coreplugin/id.h>
#include <coreplugin/messagemanager.h>
@@ -90,12 +93,15 @@ public:
bool reachable() const { return m_state == Initialized; }
// document synchronization
- void openDocument(Core::IDocument *document);
+ bool openDocument(Core::IDocument *document);
void closeDocument(const LanguageServerProtocol::DidCloseTextDocumentParams &params);
- bool documentOpen(const LanguageServerProtocol::DocumentUri &uri) const;
+ bool documentOpen(const Core::IDocument *document) const;
void documentContentsSaved(Core::IDocument *document);
void documentWillSave(Core::IDocument *document);
- void documentContentsChanged(Core::IDocument *document);
+ void documentContentsChanged(TextEditor::TextDocument *document,
+ int position,
+ int charsRemoved,
+ int charsAdded);
void registerCapabilities(const QList<LanguageServerProtocol::Registration> &registrations);
void unregisterCapabilities(const QList<LanguageServerProtocol::Unregistration> &unregistrations);
bool findLinkAt(LanguageServerProtocol::GotoDefinitionRequest &request);
@@ -111,6 +117,8 @@ public:
void executeCommand(const LanguageServerProtocol::Command &command);
// workspace control
+ void setCurrentProject(ProjectExplorer::Project *project);
+ const ProjectExplorer::Project *project() const;
void projectOpened(ProjectExplorer::Project *project);
void projectClosed(ProjectExplorer::Project *project);
@@ -121,7 +129,7 @@ public:
void setSupportedLanguage(const LanguageFilter &filter);
bool isSupportedDocument(const Core::IDocument *document) const;
- bool isSupportedFile(const Utils::FileName &filePath, const QString &mimeType) const;
+ bool isSupportedFile(const Utils::FilePath &filePath, const QString &mimeType) const;
bool isSupportedUri(const LanguageServerProtocol::DocumentUri &uri) const;
void setName(const QString &name) { m_displayName = name; }
@@ -148,6 +156,8 @@ public:
const LanguageServerProtocol::ServerCapabilities &capabilities() const;
const DynamicCapabilities &dynamicCapabilities() const;
const BaseClientInterface *clientInterface() const;
+ DocumentSymbolCache *documentSymbolCache();
+ HoverHandler *hoverHandler();
signals:
void initialized(LanguageServerProtocol::ServerCapabilities capabilities);
@@ -186,17 +196,21 @@ private:
QHash<QByteArray, ContentHandler> m_contentHandler;
QString m_displayName;
LanguageFilter m_languagFilter;
- QList<Utils::FileName> m_openedDocument;
+ QMap<Utils::FilePath, QString> m_openedDocument;
Core::Id m_id;
LanguageServerProtocol::ServerCapabilities m_serverCapabilities;
DynamicCapabilities m_dynamicCapabilities;
LanguageClientCompletionAssistProvider m_completionProvider;
+ FunctionHintAssistProvider m_functionHintProvider;
LanguageClientQuickFixProvider m_quickFixProvider;
- QSet<TextEditor::TextDocument *> m_resetAssistProvider;
+ QMap<TextEditor::TextDocument *, QPointer<TextEditor::CompletionAssistProvider>> m_resetAssistProvider;
QHash<LanguageServerProtocol::DocumentUri, LanguageServerProtocol::MessageId> m_highlightRequests;
int m_restartsLeft = 5;
QScopedPointer<BaseClientInterface> m_clientInterface;
QMap<LanguageServerProtocol::DocumentUri, QList<TextMark *>> m_diagnostics;
+ DocumentSymbolCache m_documentSymbolCache;
+ HoverHandler m_hoverHandler;
+ const ProjectExplorer::Project *m_project = nullptr;
};
} // namespace LanguageClient
diff --git a/src/plugins/languageclient/documentsymbolcache.cpp b/src/plugins/languageclient/documentsymbolcache.cpp
new file mode 100644
index 00000000000..b922486d56e
--- /dev/null
+++ b/src/plugins/languageclient/documentsymbolcache.cpp
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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 "documentsymbolcache.h"
+
+#include "client.h"
+
+#include <coreplugin/editormanager/editormanager.h>
+#include <texteditor/textdocument.h>
+
+using namespace LanguageServerProtocol;
+
+namespace LanguageClient {
+
+DocumentSymbolCache::DocumentSymbolCache(Client *client)
+ : QObject(client)
+ , m_client(client)
+{
+ connect(Core::EditorManager::instance(),
+ &Core::EditorManager::documentOpened,
+ this,
+ [this](Core::IDocument *document) {
+ connect(document, &Core::IDocument::contentsChanged, this, [this, document]() {
+ m_cache.remove(DocumentUri::fromFileName(document->filePath()));
+ });
+ });
+}
+
+void DocumentSymbolCache::requestSymbols(const DocumentUri &uri)
+{
+ auto entry = m_cache.find(uri);
+ if (entry != m_cache.end()) {
+ emit gotSymbols(uri, entry.value());
+ return;
+ }
+
+ const DocumentSymbolParams params((TextDocumentIdentifier(uri)));
+ DocumentSymbolsRequest request(params);
+ request.setResponseCallback([uri, self = QPointer<DocumentSymbolCache>(this)](
+ const DocumentSymbolsRequest::Response &response) {
+ if (self)
+ self->handleResponse(uri, response);
+ });
+ m_client->sendContent(request);
+}
+
+void DocumentSymbolCache::handleResponse(const DocumentUri &uri,
+ const DocumentSymbolsRequest::Response &response)
+{
+ if (Utils::optional<DocumentSymbolsRequest::Response::Error> error = response.error()) {
+ if (m_client)
+ m_client->log(error.value());
+ }
+ const DocumentSymbolsResult &symbols = response.result().value_or(DocumentSymbolsResult());
+ m_cache[uri] = symbols;
+ emit gotSymbols(uri, symbols);
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/documentsymbolcache.h b/src/plugins/languageclient/documentsymbolcache.h
new file mode 100644
index 00000000000..2f0b2376bc3
--- /dev/null
+++ b/src/plugins/languageclient/documentsymbolcache.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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 "utils/optional.h"
+
+#include <languageserverprotocol/languagefeatures.h>
+
+#include <QObject>
+
+namespace LanguageClient {
+
+class Client;
+
+class DocumentSymbolCache : public QObject
+{
+ Q_OBJECT
+public:
+ DocumentSymbolCache(Client *client);
+
+ void requestSymbols(const LanguageServerProtocol::DocumentUri &uri);
+
+signals:
+ void gotSymbols(const LanguageServerProtocol::DocumentUri &uri,
+ const LanguageServerProtocol::DocumentSymbolsResult &symbols);
+
+private:
+ void handleResponse(const LanguageServerProtocol::DocumentUri &uri,
+ const LanguageServerProtocol::DocumentSymbolsRequest::Response &response);
+
+ QMap<LanguageServerProtocol::DocumentUri, LanguageServerProtocol::DocumentSymbolsResult> m_cache;
+ Client *m_client = nullptr;
+};
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/images/languageclient.png b/src/plugins/languageclient/images/languageclient.png
new file mode 100644
index 00000000000..38e5f284981
--- /dev/null
+++ b/src/plugins/languageclient/images/languageclient.png
Binary files differ
diff --git a/src/plugins/languageclient/images/languageclient@2x.png b/src/plugins/languageclient/images/languageclient@2x.png
new file mode 100644
index 00000000000..bb01ed83bba
--- /dev/null
+++ b/src/plugins/languageclient/images/languageclient@2x.png
Binary files differ
diff --git a/src/plugins/languageclient/languageclient.pro b/src/plugins/languageclient/languageclient.pro
index dbae20e1098..b2d39acf16c 100644
--- a/src/plugins/languageclient/languageclient.pro
+++ b/src/plugins/languageclient/languageclient.pro
@@ -4,29 +4,37 @@ DEFINES += LANGUAGECLIENT_LIBRARY
HEADERS += \
client.h \
+ documentsymbolcache.h \
dynamiccapabilities.h \
languageclient_global.h \
languageclientcompletionassist.h \
+ languageclientfunctionhint.h \
+ languageclienthoverhandler.h \
languageclientinterface.h \
languageclientmanager.h \
languageclientoutline.h \
languageclientplugin.h \
languageclientquickfix.h \
languageclientsettings.h \
- languageclientutils.h
+ languageclientutils.h \
+ locatorfilter.h
SOURCES += \
client.cpp \
+ documentsymbolcache.cpp \
dynamiccapabilities.cpp \
languageclientcompletionassist.cpp \
+ languageclientfunctionhint.cpp \
+ languageclienthoverhandler.cpp \
languageclientinterface.cpp \
languageclientmanager.cpp \
languageclientoutline.cpp \
languageclientplugin.cpp \
languageclientquickfix.cpp \
languageclientsettings.cpp \
- languageclientutils.cpp
+ languageclientutils.cpp \
+ locatorfilter.cpp
RESOURCES += \
languageclient.qrc
diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs
index 0c2752ef5eb..13ca2105703 100644
--- a/src/plugins/languageclient/languageclient.qbs
+++ b/src/plugins/languageclient/languageclient.qbs
@@ -16,10 +16,16 @@ QtcPlugin {
files: [
"client.cpp",
"client.h",
+ "documentsymbolcache.cpp",
+ "documentsymbolcache.h",
"dynamiccapabilities.cpp",
"dynamiccapabilities.h",
"languageclient.qrc",
"languageclient_global.h",
+ "languageclienthoverhandler.cpp",
+ "languageclienthoverhandler.h",
+ "languageclientfunctionhint.cpp",
+ "languageclientfunctionhint.h",
"languageclientinterface.cpp",
"languageclientinterface.h",
"languageclientcompletionassist.cpp",
@@ -36,5 +42,7 @@ QtcPlugin {
"languageclientsettings.h",
"languageclientutils.cpp",
"languageclientutils.h",
+ "locatorfilter.cpp",
+ "locatorfilter.h",
]
}
diff --git a/src/plugins/languageclient/languageclient.qrc b/src/plugins/languageclient/languageclient.qrc
index eb73a75300a..f5bbf281e01 100644
--- a/src/plugins/languageclient/languageclient.qrc
+++ b/src/plugins/languageclient/languageclient.qrc
@@ -1,5 +1,7 @@
<RCC>
<qresource prefix="/languageclient">
+ <file>images/languageclient.png</file>
+ <file>images/languageclient@2x.png</file>
<file>images/settingscategory_languageclient.png</file>
<file>images/settingscategory_languageclient@2x.png</file>
</qresource>
diff --git a/src/plugins/languageclient/languageclient_global.h b/src/plugins/languageclient/languageclient_global.h
index a4cd255d90b..685379804df 100644
--- a/src/plugins/languageclient/languageclient_global.h
+++ b/src/plugins/languageclient/languageclient_global.h
@@ -31,7 +31,16 @@ namespace LanguageClient {
namespace Constants {
const char LANGUAGECLIENT_SETTINGS_CATEGORY[] = "ZY.LanguageClient";
+const char LANGUAGECLIENT_SETTINGS_PAGE[] = "LanguageClient.General";
const char LANGUAGECLIENT_SETTINGS_TR[] = QT_TRANSLATE_NOOP("LanguageClient", "Language Client");
+const char LANGUAGECLIENT_DOCUMENT_FILTER_ID[] = "Current Document Symbols";
+const char LANGUAGECLIENT_DOCUMENT_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("LanguageClient", "Symbols in Current Document");
+const char LANGUAGECLIENT_WORKSPACE_FILTER_ID[] = "Workspace Symbols";
+const char LANGUAGECLIENT_WORKSPACE_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("LanguageClient", "Symbols in Workspace");
+const char LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_ID[] = "Workspace Classes and Structs";
+const char LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("LanguageClient", "Classes and Structs in Workspace");
+const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_ID[] = "Workspace Functions and Methods";
+const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("LanguageClient", "Functions and Methods in Workspace");
} // namespace Constants
} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientcompletionassist.cpp b/src/plugins/languageclient/languageclientcompletionassist.cpp
index 1d9a10f6e4f..a036f7beb34 100644
--- a/src/plugins/languageclient/languageclientcompletionassist.cpp
+++ b/src/plugins/languageclient/languageclientcompletionassist.cpp
@@ -41,6 +41,7 @@
#include <QDebug>
#include <QLoggingCategory>
#include <QRegExp>
+#include <QRegularExpression>
#include <QTextBlock>
#include <QTextDocument>
#include <QTime>
@@ -76,6 +77,7 @@ public:
private:
CompletionItem m_item;
+ mutable QChar m_triggeredCommitCharacter;
mutable QString m_sortText;
};
@@ -89,8 +91,14 @@ QString LanguageClientCompletionItem::text() const
bool LanguageClientCompletionItem::implicitlyApplies() const
{ return false; }
-bool LanguageClientCompletionItem::prematurelyApplies(const QChar &/*typedCharacter*/) const
-{ return false; }
+bool LanguageClientCompletionItem::prematurelyApplies(const QChar &typedCharacter) const
+{
+ if (m_item.commitCharacters().has_value() && m_item.commitCharacters().value().contains(typedCharacter)) {
+ m_triggeredCommitCharacter = typedCharacter;
+ return true;
+ }
+ return false;
+}
void LanguageClientCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
int /*basePosition*/) const
@@ -101,13 +109,20 @@ void LanguageClientCompletionItem::apply(TextDocumentManipulatorInterface &manip
} 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)
+ for (auto it = textToInsert.crbegin(), end = textToInsert.crend(); it != end; ++it) {
+ if (it->toLower() != manipulator.characterAt(pos - length - 1).toLower()) {
length = 0;
+ break;
+ }
+ ++length;
}
+ QTextCursor cursor = manipulator.textCursorAt(pos);
+ cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
+ const QString blockTextUntilPosition = cursor.selectedText();
+ static QRegularExpression identifier("[a-zA-Z_][a-zA-Z0-9_]*$");
+ QRegularExpressionMatch match = identifier.match(blockTextUntilPosition);
+ int matchLength = match.hasMatch() ? match.capturedLength(0) : 0;
+ length = qMax(length, matchLength);
manipulator.replace(pos - length, length, textToInsert);
}
@@ -115,6 +130,8 @@ void LanguageClientCompletionItem::apply(TextDocumentManipulatorInterface &manip
for (const auto &edit : *additionalEdits)
applyTextEdit(manipulator, edit);
}
+ if (!m_triggeredCommitCharacter.isNull())
+ manipulator.insertCodeSnippet(manipulator.currentPosition(), m_triggeredCommitCharacter);
}
QIcon LanguageClientCompletionItem::icon() const
@@ -136,8 +153,7 @@ QIcon LanguageClientCompletionItem::icon() const
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;
+ default: icon = iconForType(Unknown); break;
}
return icon;
}
@@ -312,8 +328,9 @@ IAssistProposal *LanguageClientCompletionAssistProcessor::perform(const AssistIn
--line; // line is 0 based in the protocol
--column; // column is 0 based in the protocol
params.setPosition({line, column});
+ params.setContext(context);
params.setTextDocument(
- DocumentUri::fromFileName(Utils::FileName::fromString(interface->fileName())));
+ DocumentUri::fromFileName(Utils::FilePath::fromString(interface->fileName())));
completionRequest.setResponseCallback([this](auto response) {
this->handleCompletionResponse(response);
});
diff --git a/src/plugins/languageclient/languageclientfunctionhint.cpp b/src/plugins/languageclient/languageclientfunctionhint.cpp
new file mode 100644
index 00000000000..9a1fe6fc8e9
--- /dev/null
+++ b/src/plugins/languageclient/languageclientfunctionhint.cpp
@@ -0,0 +1,144 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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 "languageclientfunctionhint.h"
+#include "client.h"
+
+#include <languageserverprotocol/languagefeatures.h>
+#include <texteditor/codeassist/assistinterface.h>
+#include <texteditor/codeassist/functionhintproposal.h>
+#include <texteditor/codeassist/iassistprocessor.h>
+#include <texteditor/codeassist/ifunctionhintproposalmodel.h>
+
+using namespace TextEditor;
+using namespace LanguageServerProtocol;
+
+namespace LanguageClient {
+
+class FunctionHintProposalModel : public IFunctionHintProposalModel
+{
+public:
+ explicit FunctionHintProposalModel(SignatureHelp signature)
+ : m_sigis(signature)
+ {}
+ void reset() override {}
+ int size() const override
+ { return m_sigis.signatures().size(); }
+ QString text(int index) const override;
+
+ int activeArgument(const QString &/*prefix*/) const override
+ { return m_sigis.activeParameter().value_or(0); }
+
+private:
+ LanguageServerProtocol::SignatureHelp m_sigis;
+};
+
+QString FunctionHintProposalModel::text(int index) const
+{
+ return m_sigis.signatures().size() > index ? m_sigis.signatures().at(index).label() : QString();
+}
+
+class FunctionHintProcessor : public IAssistProcessor
+{
+public:
+ explicit FunctionHintProcessor(Client *client) : m_client(client) {}
+ IAssistProposal *perform(const AssistInterface *interface) override;
+ bool running() override { return m_running; }
+ bool needsRestart() const override { return true; }
+
+private:
+ void handleSignatureResponse(const SignatureHelpRequest::Response &response);
+
+ QPointer<Client> m_client;
+ bool m_running = false;
+ int m_pos = -1;
+};
+
+IAssistProposal *FunctionHintProcessor::perform(const AssistInterface *interface)
+{
+ QTC_ASSERT(m_client, return nullptr);
+ m_pos = interface->position();
+ QTextCursor cursor(interface->textDocument());
+ cursor.setPosition(m_pos);
+ auto uri = DocumentUri::fromFileName(Utils::FilePath::fromString(interface->fileName()));
+ SignatureHelpRequest request;
+ request.setParams(TextDocumentPositionParams(TextDocumentIdentifier(uri), Position(cursor)));
+ request.setResponseCallback([this](auto response) { this->handleSignatureResponse(response); });
+ m_client->sendContent(request);
+ m_running = true;
+ return nullptr;
+}
+
+void FunctionHintProcessor::handleSignatureResponse(const SignatureHelpRequest::Response &response)
+{
+ m_running = false;
+ if (auto error = response.error())
+ m_client->log(error.value());
+ FunctionHintProposalModelPtr model(
+ new FunctionHintProposalModel(response.result().value().value()));
+ setAsyncProposalAvailable(new FunctionHintProposal(m_pos, model));
+}
+
+FunctionHintAssistProvider::FunctionHintAssistProvider(Client *client)
+ : m_client(client)
+{}
+
+TextEditor::IAssistProcessor *FunctionHintAssistProvider::createProcessor() const
+{
+ return new FunctionHintProcessor(m_client);
+}
+
+IAssistProvider::RunType FunctionHintAssistProvider::runType() const
+{
+ return Asynchronous;
+}
+
+int FunctionHintAssistProvider::activationCharSequenceLength() const
+{
+ return m_activationCharSequenceLength;
+}
+
+bool FunctionHintAssistProvider::isActivationCharSequence(
+ const QString &sequence) const
+{
+ return Utils::anyOf(m_triggerChars,
+ [sequence](const QString &trigger) { return trigger.endsWith(sequence); });
+}
+
+bool FunctionHintAssistProvider::isContinuationChar(const QChar &/*c*/) const
+{
+ return true;
+}
+
+void FunctionHintAssistProvider::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/languageclientfunctionhint.h b/src/plugins/languageclient/languageclientfunctionhint.h
new file mode 100644
index 00000000000..cf5f96d812a
--- /dev/null
+++ b/src/plugins/languageclient/languageclientfunctionhint.h
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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 Client;
+
+class FunctionHintAssistProvider : public TextEditor::CompletionAssistProvider
+{
+public:
+ explicit FunctionHintAssistProvider(Client *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 &c) const override;
+
+ void setTriggerCharacters(QList<QString> triggerChars);
+private:
+ QList<QString> m_triggerChars;
+ int m_activationCharSequenceLength = 0;
+ Client *m_client = nullptr; // not owned
+};
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclienthoverhandler.cpp b/src/plugins/languageclient/languageclienthoverhandler.cpp
new file mode 100644
index 00000000000..166fdc30493
--- /dev/null
+++ b/src/plugins/languageclient/languageclienthoverhandler.cpp
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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 "languageclienthoverhandler.h"
+
+#include "client.h"
+
+#include <texteditor/textdocument.h>
+#include <texteditor/texteditor.h>
+#include <utils/mimetypes/mimedatabase.h>
+#include <utils/qtcassert.h>
+#include <utils/tooltip/tooltip.h>
+
+using namespace LanguageServerProtocol;
+
+namespace LanguageClient {
+
+HoverHandler::HoverHandler(Client *client)
+ : m_client(client)
+{}
+
+HoverHandler::~HoverHandler()
+{
+ abort();
+}
+
+void HoverHandler::abort()
+{
+ if (m_client && m_client->reachable() && m_currentRequest.has_value())
+ m_client->cancelRequest(*m_currentRequest);
+ m_currentRequest.reset();
+}
+
+void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget,
+ int pos,
+ TextEditor::BaseHoverHandler::ReportPriority report)
+{
+ if (m_currentRequest.has_value())
+ abort();
+ if (m_client.isNull()
+ || !m_client->documentOpen(editorWidget->textDocument())
+ || !m_client->capabilities().hoverProvider().value_or(false)) {
+ report(Priority_None);
+ return;
+ }
+
+ bool sendMessage = m_client->capabilities().hoverProvider().value_or(false);
+ if (Utils::optional<bool> registered = m_client->dynamicCapabilities().isRegistered(
+ HoverRequest::methodName)) {
+ sendMessage = registered.value();
+ if (sendMessage) {
+ const TextDocumentRegistrationOptions option(
+ m_client->dynamicCapabilities().option(HoverRequest::methodName).toObject());
+ if (option.isValid(nullptr)) {
+ sendMessage = option.filterApplies(editorWidget->textDocument()->filePath(),
+ Utils::mimeTypeForName(
+ editorWidget->textDocument()->mimeType()));
+ }
+ }
+ }
+ if (!sendMessage) {
+ report(Priority_None);
+ return;
+ }
+
+ m_report = report;
+ auto uri = DocumentUri::fromFileName(editorWidget->textDocument()->filePath());
+ QTextCursor cursor = editorWidget->textCursor();
+ cursor.setPosition(pos);
+ TextDocumentPositionParams params(uri, Position(cursor));
+ HoverRequest request(params);
+ request.setResponseCallback(
+ [this](const HoverRequest::Response &response) { handleResponse(response); });
+ m_client->sendContent(request);
+}
+
+void HoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point)
+{
+ if (toolTip().isEmpty())
+ Utils::ToolTip::hide();
+ else
+ Utils::ToolTip::show(point, toolTip(), editorWidget);
+}
+
+void HoverHandler::handleResponse(const HoverRequest::Response &response)
+{
+ m_currentRequest.reset();
+ if (Utils::optional<HoverRequest::Response::Error> error = response.error()) {
+ if (m_client)
+ m_client->log(error.value());
+ }
+ if (Utils::optional<Hover> result = response.result())
+ setContent(result.value().content());
+ m_report(priority());
+}
+
+static QString toolTipForMarkedStrings(const QList<MarkedString> &markedStrings)
+{
+ QString tooltip;
+ for (const MarkedString &markedString : markedStrings) {
+ if (!tooltip.isEmpty())
+ tooltip += '\n';
+ if (auto string = Utils::get_if<QString>(&markedString))
+ tooltip += *string;
+ else if (auto string = Utils::get_if<MarkedLanguageString>(&markedString))
+ tooltip += string->value() + " [" + string->language() + ']';
+ }
+ return tooltip;
+}
+
+void HoverHandler::setContent(const HoverContent &hoverContent)
+{
+ if (auto markupContent = Utils::get_if<MarkupContent>(&hoverContent)) {
+ const QString &content = markupContent->content();
+ if (markupContent->kind() == MarkupKind::plaintext)
+ setToolTip(content);
+ else if (m_client)
+ m_client->log(tr("Got unsupported markup hover content: ") + content);
+ } else if (auto markedString = Utils::get_if<MarkedString>(&hoverContent)) {
+ setToolTip(toolTipForMarkedStrings({*markedString}));
+ } else if (auto markedStrings = Utils::get_if<QList<MarkedString>>(&hoverContent)) {
+ setToolTip(toolTipForMarkedStrings(*markedStrings));
+ }
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclienthoverhandler.h b/src/plugins/languageclient/languageclienthoverhandler.h
new file mode 100644
index 00000000000..88a439e2117
--- /dev/null
+++ b/src/plugins/languageclient/languageclienthoverhandler.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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/languagefeatures.h>
+#include <texteditor/basehoverhandler.h>
+
+namespace LanguageClient {
+
+class Client;
+
+class HoverHandler : public TextEditor::BaseHoverHandler
+{
+ Q_DECLARE_TR_FUNCTIONS(HoverHandler)
+public:
+ explicit HoverHandler(Client *client);
+ ~HoverHandler() override;
+
+ void abort() override;
+
+protected:
+ void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
+ int pos,
+ ReportPriority report) override;
+ void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) override;
+
+private:
+ void handleResponse(const LanguageServerProtocol::HoverRequest::Response &response);
+ void setContent(const LanguageServerProtocol::HoverContent &content);
+
+ QPointer<Client> m_client;
+ Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest;
+ TextEditor::BaseHoverHandler::ReportPriority m_report;
+};
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientinterface.cpp b/src/plugins/languageclient/languageclientinterface.cpp
index 6e138e02c04..91d2562e672 100644
--- a/src/plugins/languageclient/languageclientinterface.cpp
+++ b/src/plugins/languageclient/languageclientinterface.cpp
@@ -35,7 +35,6 @@
using namespace LanguageServerProtocol;
static Q_LOGGING_CATEGORY(LOGLSPCLIENTV, "qtc.languageclient.messages", QtWarningMsg);
-static Q_LOGGING_CATEGORY(LOGLSPCLIENTPARSE, "qtc.languageclient.parse", QtWarningMsg);
namespace LanguageClient {
@@ -64,8 +63,8 @@ void BaseClientInterface::resetBuffer()
void BaseClientInterface::parseData(const QByteArray &data)
{
const qint64 preWritePosition = m_buffer.pos();
- qCDebug(LOGLSPCLIENTPARSE) << "parse buffer pos: " << preWritePosition;
- qCDebug(LOGLSPCLIENTPARSE) << " data: " << data;
+ qCDebug(parseLog) << "parse buffer pos: " << preWritePosition;
+ qCDebug(parseLog) << " data: " << data;
if (!m_buffer.atEnd())
m_buffer.seek(preWritePosition + m_buffer.bytesAvailable());
m_buffer.write(data);
@@ -73,9 +72,9 @@ void BaseClientInterface::parseData(const QByteArray &data)
while (!m_buffer.atEnd()) {
QString parseError;
BaseMessage::parse(&m_buffer, parseError, m_currentMessage);
- qCDebug(LOGLSPCLIENTPARSE) << " complete: " << m_currentMessage.isComplete();
- qCDebug(LOGLSPCLIENTPARSE) << " length: " << m_currentMessage.contentLength;
- qCDebug(LOGLSPCLIENTPARSE) << " content: " << m_currentMessage.content;
+ qCDebug(parseLog) << " complete: " << m_currentMessage.isComplete();
+ qCDebug(parseLog) << " length: " << m_currentMessage.contentLength;
+ qCDebug(parseLog) << " content: " << m_currentMessage.content;
if (!parseError.isEmpty())
emit error(parseError);
if (!m_currentMessage.isComplete())
@@ -112,7 +111,7 @@ StdIOClientInterface::~StdIOClientInterface()
bool StdIOClientInterface::needsRestart(const StdIOSettings *settings) const
{
- return m_executable != settings->m_executable || m_arguments != settings->m_arguments;
+ return m_executable != settings->m_executable || m_arguments != settings->arguments();
}
bool StdIOClientInterface::start()
diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp
index 637a0efa28e..4c5ec02a594 100644
--- a/src/plugins/languageclient/languageclientmanager.cpp
+++ b/src/plugins/languageclient/languageclientmanager.cpp
@@ -26,10 +26,12 @@
#include "languageclientmanager.h"
#include "languageclientutils.h"
+#include "languageclientplugin.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/find/searchresultwindow.h>
+#include <coreplugin/icore.h>
#include <languageserverprotocol/messages.h>
#include <projectexplorer/session.h>
#include <projectexplorer/project.h>
@@ -49,42 +51,51 @@ namespace LanguageClient {
static LanguageClientManager *managerInstance = nullptr;
-LanguageClientManager::LanguageClientManager()
+LanguageClientManager::LanguageClientManager(QObject *parent)
+ : QObject (parent)
{
+ using namespace Core;
+ using namespace ProjectExplorer;
JsonRpcMessageHandler::registerMessageProvider<PublishDiagnosticsNotification>();
JsonRpcMessageHandler::registerMessageProvider<ApplyWorkspaceEditRequest>();
JsonRpcMessageHandler::registerMessageProvider<LogMessageNotification>();
JsonRpcMessageHandler::registerMessageProvider<ShowMessageRequest>();
JsonRpcMessageHandler::registerMessageProvider<ShowMessageNotification>();
- managerInstance = this;
+ JsonRpcMessageHandler::registerMessageProvider<WorkSpaceFolderRequest>();
+ connect(EditorManager::instance(), &EditorManager::editorOpened,
+ this, &LanguageClientManager::editorOpened);
+ connect(EditorManager::instance(), &EditorManager::documentOpened,
+ this, &LanguageClientManager::documentOpened);
+ connect(EditorManager::instance(), &EditorManager::documentClosed,
+ this, &LanguageClientManager::documentClosed);
+ connect(EditorManager::instance(), &EditorManager::saved,
+ this, &LanguageClientManager::documentContentsSaved);
+ connect(EditorManager::instance(), &EditorManager::aboutToSave,
+ this, &LanguageClientManager::documentWillSave);
+ connect(SessionManager::instance(), &SessionManager::projectAdded,
+ this, &LanguageClientManager::projectAdded);
+ connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ this, &LanguageClientManager::projectRemoved);
}
LanguageClientManager::~LanguageClientManager()
{
QTC_ASSERT(m_clients.isEmpty(), qDeleteAll(m_clients));
+ qDeleteAll(m_currentSettings);
+ managerInstance = nullptr;
}
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);
+ if (managerInstance)
+ return;
+ QTC_ASSERT(LanguageClientPlugin::instance(), return);
+ managerInstance = new LanguageClientManager(LanguageClientPlugin::instance());
}
void LanguageClientManager::startClient(Client *client)
{
+ QTC_ASSERT(managerInstance, return);
QTC_ASSERT(client, return);
if (managerInstance->m_shuttingDown) {
managerInstance->clientFinished(client);
@@ -99,20 +110,40 @@ void LanguageClientManager::startClient(Client *client)
client->initialize();
else
managerInstance->clientFinished(client);
+
+ connect(client,
+ &Client::initialized,
+ &managerInstance->m_currentDocumentLocatorFilter,
+ &DocumentLocatorFilter::updateCurrentClient);
+}
+
+void LanguageClientManager::startClient(BaseSettings *setting, ProjectExplorer::Project *project)
+{
+ QTC_ASSERT(managerInstance, return);
+ QTC_ASSERT(setting, return);
+ QTC_ASSERT(setting->isValid(), return);
+ Client *client = setting->createClient();
+ QTC_ASSERT(client, return);
+ client->setCurrentProject(project);
+ startClient(client);
+ managerInstance->m_clientsForSetting[setting->m_id].append(client);
}
QVector<Client *> LanguageClientManager::clients()
{
+ QTC_ASSERT(managerInstance, return {});
return managerInstance->m_clients;
}
void LanguageClientManager::addExclusiveRequest(const MessageId &id, Client *client)
{
+ QTC_ASSERT(managerInstance, return);
managerInstance->m_exclusiveRequests[id] << client;
}
void LanguageClientManager::reportFinished(const MessageId &id, Client *byClient)
{
+ QTC_ASSERT(managerInstance, return);
for (Client *client : managerInstance->m_exclusiveRequests[id]) {
if (client != byClient)
client->cancelRequest(id);
@@ -120,11 +151,24 @@ void LanguageClientManager::reportFinished(const MessageId &id, Client *byClient
managerInstance->m_exclusiveRequests.remove(id);
}
+void LanguageClientManager::shutdownClient(Client *client)
+{
+ if (!client)
+ return;
+ if (client->reachable())
+ client->shutdown();
+ else if (client->state() != Client::Shutdown && client->state() != Client::ShutdownRequested)
+ deleteClient(client);
+}
+
void LanguageClientManager::deleteClient(Client *client)
{
+ QTC_ASSERT(managerInstance, return);
QTC_ASSERT(client, return);
client->disconnect();
managerInstance->m_clients.removeAll(client);
+ for (QVector<Client *> &clients : managerInstance->m_clientsForSetting)
+ clients.removeAll(client);
if (managerInstance->m_shuttingDown)
delete client;
else
@@ -133,15 +177,12 @@ void LanguageClientManager::deleteClient(Client *client)
void LanguageClientManager::shutdown()
{
+ QTC_ASSERT(managerInstance, return);
if (managerInstance->m_shuttingDown)
return;
managerInstance->m_shuttingDown = true;
- for (auto interface : managerInstance->m_clients) {
- if (interface->reachable())
- interface->shutdown();
- else
- deleteClient(interface);
- }
+ for (Client *client : managerInstance->m_clients)
+ shutdownClient(client);
QTimer::singleShot(3000, managerInstance, [](){
for (auto interface : managerInstance->m_clients)
deleteClient(interface);
@@ -157,12 +198,107 @@ LanguageClientManager *LanguageClientManager::instance()
QList<Client *> LanguageClientManager::clientsSupportingDocument(
const TextEditor::TextDocument *doc)
{
+ QTC_ASSERT(managerInstance, return {});
QTC_ASSERT(doc, return {};);
return Utils::filtered(managerInstance->reachableClients(), [doc](Client *client) {
return client->isSupportedDocument(doc);
}).toList();
}
+void LanguageClientManager::applySettings()
+{
+ QTC_ASSERT(managerInstance, return);
+ qDeleteAll(managerInstance->m_currentSettings);
+ managerInstance->m_currentSettings = Utils::transform(LanguageClientSettings::currentPageSettings(),
+ [](BaseSettings *settings) {
+ return settings->copy();
+ });
+ LanguageClientSettings::toSettings(Core::ICore::settings(), managerInstance->m_currentSettings);
+
+ const QList<BaseSettings *> restarts = Utils::filtered(managerInstance->m_currentSettings,
+ [](BaseSettings *settings) {
+ return settings->needsRestart();
+ });
+
+ for (BaseSettings *setting : restarts) {
+ for (Client *client : clientForSetting(setting))
+ shutdownClient(client);
+ if (!setting->isValid() || !setting->m_enabled)
+ continue;
+ switch (setting->m_startBehavior) {
+ case BaseSettings::AlwaysOn:
+ startClient(setting);
+ break;
+ case BaseSettings::RequiresFile:
+ if (Utils::anyOf(Core::DocumentModel::openedDocuments(),
+ [filter = setting->m_languageFilter](Core::IDocument *doc) {
+ return filter.isSupported(doc);
+ })) {
+ startClient(setting);
+ }
+ break;
+ case BaseSettings::RequiresProject: {
+ for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) {
+ if (setting->m_languageFilter.isSupported(doc)) {
+ const Utils::FilePath filePath = doc->filePath();
+ for (ProjectExplorer::Project *project :
+ ProjectExplorer::SessionManager::projects()) {
+ if (project->isKnownFile(filePath))
+ startClient(setting, project);
+ }
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+QList<BaseSettings *> LanguageClientManager::currentSettings()
+{
+ QTC_ASSERT(managerInstance, return {});
+ return managerInstance->m_currentSettings;
+}
+
+QVector<Client *> LanguageClientManager::clientForSetting(const BaseSettings *setting)
+{
+ QTC_ASSERT(managerInstance, return {});
+ auto instance = managerInstance;
+ return instance->m_clientsForSetting.value(setting->m_id);
+}
+
+const BaseSettings *LanguageClientManager::settingForClient(Client *client)
+{
+ QTC_ASSERT(managerInstance, return nullptr);
+ for (const QString &id : managerInstance->m_clientsForSetting.keys()) {
+ for (const Client *settingClient : managerInstance->m_clientsForSetting[id]) {
+ if (settingClient == client) {
+ return Utils::findOrDefault(managerInstance->m_currentSettings,
+ [id](BaseSettings *setting) {
+ return setting->m_id == id;
+ });
+ }
+ }
+ }
+ return nullptr;
+}
+
+Client *LanguageClientManager::clientForEditor(Core::IEditor *iEditor)
+{
+ QTC_ASSERT(managerInstance, return nullptr);
+
+ auto editor = qobject_cast<TextEditor::BaseTextEditor *>(iEditor);
+ if (!editor)
+ return nullptr;
+
+ return Utils::findOrDefault(managerInstance->reachableClients(),
+ [doc = editor->textDocument()](Client *client) {
+ return client->documentOpen(doc);
+ });
+}
+
QVector<Client *> LanguageClientManager::reachableClients()
{
return Utils::filtered(m_clients, &Client::reachable);
@@ -188,7 +324,7 @@ void LanguageClientManager::clientFinished(Client *client)
client->disconnect(this);
client->log(tr("Unexpectedly finished. Restarting in %1 seconds.").arg(restartTimeoutS),
Core::MessageManager::Flash);
- QTimer::singleShot(restartTimeoutS * 1000, client, [client](){ startClient(client); });
+ QTimer::singleShot(restartTimeoutS * 1000, client, [client]() { startClient(client); });
} else {
if (unexpectedFinish && !m_shuttingDown)
client->log(tr("Unexpectedly finished."), Core::MessageManager::Flash);
@@ -198,23 +334,19 @@ void LanguageClientManager::clientFinished(Client *client)
}
}
-void LanguageClientManager::editorOpened(Core::IEditor *iEditor)
+void LanguageClientManager::editorOpened(Core::IEditor *editor)
{
using namespace TextEditor;
- Core::IDocument *document = iEditor->document();
- for (Client *interface : reachableClients())
- interface->openDocument(document);
-
- if (BaseTextEditor *editor = qobject_cast<BaseTextEditor *>(iEditor)) {
- if (TextEditorWidget *widget = editor->editorWidget()) {
+ if (auto *textEditor = qobject_cast<BaseTextEditor *>(editor)) {
+ if (TextEditorWidget *widget = textEditor->editorWidget()) {
connect(widget, &TextEditorWidget::requestLinkAt, this,
- [this, filePath = document->filePath()]
+ [this, filePath = editor->document()->filePath()]
(const QTextCursor &cursor, Utils::ProcessLinkCallback &callback) {
findLinkAt(filePath, cursor, callback);
});
connect(widget, &TextEditorWidget::requestUsages, this,
- [this, filePath = document->filePath()]
- (const QTextCursor &cursor) {
+ [this, filePath = editor->document()->filePath()]
+ (const QTextCursor &cursor){
findUsages(filePath, cursor);
});
connect(widget, &TextEditorWidget::cursorPositionChanged, this, [this, widget](){
@@ -229,20 +361,52 @@ void LanguageClientManager::editorOpened(Core::IEditor *iEditor)
}
});
});
+ updateEditorToolBar(editor);
+ for (auto client : reachableClients())
+ widget->addHoverHandler(client->hoverHandler());
}
}
}
-void LanguageClientManager::editorsClosed(const QList<Core::IEditor *> &editors)
+void LanguageClientManager::documentOpened(Core::IDocument *document)
{
- for (auto iEditor : editors) {
- if (auto editor = qobject_cast<TextEditor::BaseTextEditor *>(iEditor)) {
- const DidCloseTextDocumentParams params(TextDocumentIdentifier(
- DocumentUri::fromFileName(editor->document()->filePath())));
- for (Client *interface : reachableClients())
- interface->closeDocument(params);
+ // check whether we have to start servers for this document
+ for (BaseSettings *setting : LanguageClientSettings::currentPageSettings()) {
+ const QVector<Client *> clients = clientForSetting(setting);
+ if (setting->isValid() && setting->m_enabled
+ && setting->m_languageFilter.isSupported(document)) {
+ if (setting->m_startBehavior == BaseSettings::RequiresProject) {
+ const Utils::FilePath filePath = document->filePath();
+ for (ProjectExplorer::Project *project :
+ ProjectExplorer::SessionManager::projects()) {
+ // check whether file is part of this project
+ if (!project->isKnownFile(filePath))
+ continue;
+
+ // check whether we already have a client running for this project
+ if (Utils::findOrDefault(clients,
+ [project](QPointer<Client> client) {
+ return client->project() == project;
+ })) {
+ continue;
+ }
+ startClient(setting, project);
+ }
+ } else if (setting->m_startBehavior == BaseSettings::RequiresFile && clients.isEmpty()) {
+ startClient(setting);
+ }
}
}
+ for (Client *interface : reachableClients())
+ interface->openDocument(document);
+}
+
+void LanguageClientManager::documentClosed(Core::IDocument *document)
+{
+ const DidCloseTextDocumentParams params(
+ TextDocumentIdentifier(DocumentUri::fromFileName(document->filePath())));
+ for (Client *interface : reachableClients())
+ interface->closeDocument(params);
}
void LanguageClientManager::documentContentsSaved(Core::IDocument *document)
@@ -257,7 +421,7 @@ void LanguageClientManager::documentWillSave(Core::IDocument *document)
interface->documentContentsSaved(document);
}
-void LanguageClientManager::findLinkAt(const Utils::FileName &filePath,
+void LanguageClientManager::findLinkAt(const Utils::FilePath &filePath,
const QTextCursor &cursor,
Utils::ProcessLinkCallback callback)
{
@@ -321,7 +485,7 @@ QList<Core::SearchResultItem> generateSearchResultItems(const LanguageClientArra
return result;
}
-void LanguageClientManager::findUsages(const Utils::FileName &filePath, const QTextCursor &cursor)
+void LanguageClientManager::findUsages(const Utils::FilePath &filePath, const QTextCursor &cursor)
{
const DocumentUri uri = DocumentUri::fromFileName(filePath);
const TextDocumentIdentifier document(uri);
@@ -357,13 +521,31 @@ void LanguageClientManager::findUsages(const Utils::FileName &filePath, const QT
void LanguageClientManager::projectAdded(ProjectExplorer::Project *project)
{
+ for (BaseSettings *setting : m_currentSettings) {
+ if (setting->isValid()
+ && setting->m_enabled
+ && setting->m_startBehavior == BaseSettings::RequiresProject) {
+ if (Utils::findOrDefault(clientForSetting(setting),
+ [project](QPointer<Client> client) {
+ return client->project() == project;
+ })
+ == nullptr) {
+ for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) {
+ if (setting->m_languageFilter.isSupported(doc)) {
+ if (project->isKnownFile(doc->filePath()))
+ startClient(setting, project);
+ }
+ }
+ }
+ }
+ }
for (Client *interface : reachableClients())
interface->projectOpened(project);
}
void LanguageClientManager::projectRemoved(ProjectExplorer::Project *project)
{
- for (Client *interface : reachableClients())
+ for (Client *interface : m_clients)
interface->projectClosed(project);
}
diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h
index 6adf1bd7732..6be720697f0 100644
--- a/src/plugins/languageclient/languageclientmanager.h
+++ b/src/plugins/languageclient/languageclientmanager.h
@@ -27,6 +27,7 @@
#include "client.h"
#include "languageclientsettings.h"
+#include "locatorfilter.h"
#include <coreplugin/id.h>
@@ -56,11 +57,13 @@ public:
static void init();
static void startClient(Client *client);
+ static void startClient(BaseSettings *setting, ProjectExplorer::Project *project = nullptr);
static QVector<Client *> clients();
static void addExclusiveRequest(const LanguageServerProtocol::MessageId &id, Client *client);
static void reportFinished(const LanguageServerProtocol::MessageId &id, Client *byClient);
+ static void shutdownClient(Client *client);
static void deleteClient(Client *client);
static void shutdown();
@@ -69,19 +72,26 @@ public:
static QList<Client *> clientsSupportingDocument(const TextEditor::TextDocument *doc);
+ static void applySettings();
+ static QList<BaseSettings *> currentSettings();
+ static QVector<Client *> clientForSetting(const BaseSettings *setting);
+ static const BaseSettings *settingForClient(Client *setting);
+ static Client *clientForEditor(Core::IEditor *editor);
+
signals:
void shutdownFinished();
private:
- LanguageClientManager();
+ LanguageClientManager(QObject *parent);
void editorOpened(Core::IEditor *editor);
- void editorsClosed(const QList<Core::IEditor *> &editors);
+ void documentOpened(Core::IDocument *document);
+ void documentClosed(Core::IDocument *document);
void documentContentsSaved(Core::IDocument *document);
void documentWillSave(Core::IDocument *document);
- void findLinkAt(const Utils::FileName &filePath, const QTextCursor &cursor,
+ void findLinkAt(const Utils::FilePath &filePath, const QTextCursor &cursor,
Utils::ProcessLinkCallback callback);
- void findUsages(const Utils::FileName &filePath, const QTextCursor &cursor);
+ void findUsages(const Utils::FilePath &filePath, const QTextCursor &cursor);
void projectAdded(ProjectExplorer::Project *project);
void projectRemoved(ProjectExplorer::Project *project);
@@ -93,8 +103,12 @@ private:
bool m_shuttingDown = false;
QVector<Client *> m_clients;
+ QList<BaseSettings *> m_currentSettings; // owned
+ QMap<QString, QVector<Client *>> m_clientsForSetting;
QHash<LanguageServerProtocol::MessageId, QList<Client *>> m_exclusiveRequests;
-
- friend class LanguageClientPlugin;
+ DocumentLocatorFilter m_currentDocumentLocatorFilter;
+ WorkspaceLocatorFilter m_workspaceLocatorFilter;
+ WorkspaceClassLocatorFilter m_workspaceClassLocatorFilter;
+ WorkspaceMethodLocatorFilter m_workspaceMethodLocatorFilter;
};
} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientoutline.cpp b/src/plugins/languageclient/languageclientoutline.cpp
index 3c3a1aa1b16..8a15a9e0b80 100644
--- a/src/plugins/languageclient/languageclientoutline.cpp
+++ b/src/plugins/languageclient/languageclientoutline.cpp
@@ -26,6 +26,7 @@
#include "languageclientoutline.h"
#include "languageclientmanager.h"
+#include "languageclientutils.h"
#include <coreplugin/find/itemviewfind.h>
#include <coreplugin/editormanager/ieditor.h>
@@ -43,46 +44,6 @@ using namespace LanguageServerProtocol;
namespace LanguageClient {
-static const QIcon symbolIcon(int type)
-{
- using namespace Utils::CodeModelIcon;
- static QMap<SymbolKind, QIcon> icons;
- if (type < int(SymbolKind::FirstSymbolKind) || type > int(SymbolKind::LastSymbolKind))
- return {};
- auto kind = static_cast<SymbolKind>(type);
- if (icons.contains(kind)) {
- switch (kind) {
- case SymbolKind::File: icons[kind] = Utils::Icons::NEWFILE.icon(); break;
- case SymbolKind::Module: icons[kind] = iconForType(Namespace); break;
- case SymbolKind::Namespace: icons[kind] = iconForType(Namespace); break;
- case SymbolKind::Package: icons[kind] = iconForType(Namespace); break;
- case SymbolKind::Class: icons[kind] = iconForType(Class); break;
- case SymbolKind::Method: icons[kind] = iconForType(FuncPublic); break;
- case SymbolKind::Property: icons[kind] = iconForType(Property); break;
- case SymbolKind::Field: icons[kind] = iconForType(VarPublic); break;
- case SymbolKind::Constructor: icons[kind] = iconForType(Class); break;
- case SymbolKind::Enum: icons[kind] = iconForType(Enum); break;
- case SymbolKind::Interface: icons[kind] = iconForType(Class); break;
- case SymbolKind::Function: icons[kind] = iconForType(FuncPublic); break;
- case SymbolKind::Variable: icons[kind] = iconForType(VarPublic); break;
- case SymbolKind::Constant: icons[kind] = iconForType(VarPublic); break;
- case SymbolKind::String: icons[kind] = iconForType(VarPublic); break;
- case SymbolKind::Number: icons[kind] = iconForType(VarPublic); break;
- case SymbolKind::Boolean: icons[kind] = iconForType(VarPublic); break;
- case SymbolKind::Array: icons[kind] = iconForType(VarPublic); break;
- case SymbolKind::Object: icons[kind] = iconForType(Class); break;
- case SymbolKind::Key: icons[kind] = iconForType(Keyword); break;
- case SymbolKind::Null: icons[kind] = iconForType(Keyword); break;
- case SymbolKind::EnumMember: icons[kind] = iconForType(Enumerator); break;
- case SymbolKind::Struct: icons[kind] = iconForType(Struct); break;
- case SymbolKind::Event: icons[kind] = iconForType(FuncPublic); break;
- case SymbolKind::Operator: icons[kind] = iconForType(FuncPublic); break;
- case SymbolKind::TypeParameter: icons[kind] = iconForType(VarPublic); break;
- }
- }
- return icons[kind];
-}
-
class LanguageClientOutlineItem : public Utils::TypedTreeItem<LanguageClientOutlineItem>
{
public:
@@ -155,7 +116,7 @@ public:
void setCursorSynchronization(bool syncWithCursor) override;
private:
- void handleResponse(const LanguageServerProtocol::DocumentSymbolsRequest::Response &response);
+ void handleResponse(const DocumentUri &uri, const DocumentSymbolsResult &response);
void updateTextCursor(const QModelIndex &proxyIndex);
void updateSelectionInTree(const QTextCursor &currentCursor);
void onItemActivated(const QModelIndex &index);
@@ -164,6 +125,7 @@ private:
QPointer<TextEditor::BaseTextEditor> m_editor;
LanguageClientOutlineModel m_model;
Utils::TreeView m_view;
+ DocumentUri m_uri;
bool m_sync = false;
};
@@ -172,23 +134,24 @@ LanguageClientOutlineWidget::LanguageClientOutlineWidget(Client *client,
: m_client(client)
, m_editor(editor)
, m_view(this)
+ , m_uri(DocumentUri::fromFileName(editor->textDocument()->filePath()))
{
- const DocumentSymbolParams params(
- TextDocumentIdentifier(
- DocumentUri::fromFileName(editor->textDocument()->filePath())));
- DocumentSymbolsRequest request(params);
- request.setResponseCallback([self = QPointer<LanguageClientOutlineWidget>(this)]
- (const DocumentSymbolsRequest::Response &response){
- if (self)
- self->handleResponse(response);
+ connect(client->documentSymbolCache(),
+ &DocumentSymbolCache::gotSymbols,
+ this,
+ &LanguageClientOutlineWidget::handleResponse);
+ connect(editor->textDocument(), &TextEditor::TextDocument::contentsChanged, this, [this]() {
+ if (m_client)
+ m_client->documentSymbolCache()->requestSymbols(m_uri);
});
+ client->documentSymbolCache()->requestSymbols(m_uri);
+
auto *layout = new QVBoxLayout;
layout->setMargin(0);
layout->setSpacing(0);
layout->addWidget(Core::ItemViewFind::createSearchableWrapper(&m_view));
setLayout(layout);
- client->sendContent(request);
m_view.setModel(&m_model);
m_view.setHeaderHidden(true);
connect(&m_view, &QAbstractItemView::activated,
@@ -212,20 +175,17 @@ void LanguageClientOutlineWidget::setCursorSynchronization(bool syncWithCursor)
updateSelectionInTree(m_editor->textCursor());
}
-void LanguageClientOutlineWidget::handleResponse(const DocumentSymbolsRequest::Response &response)
+void LanguageClientOutlineWidget::handleResponse(const DocumentUri &uri,
+ const DocumentSymbolsResult &result)
{
- if (Utils::optional<DocumentSymbolsRequest::Response::Error> error = response.error()) {
- if (m_client)
- m_client->log(error.value());
- }
- if (Utils::optional<DocumentSymbolsResult> result = response.result()) {
- if (Utils::holds_alternative<QList<SymbolInformation>>(result.value()))
- m_model.setInfo(Utils::get<QList<SymbolInformation>>(result.value()));
- else if (Utils::holds_alternative<QList<DocumentSymbol>>(result.value()))
- m_model.setInfo(Utils::get<QList<DocumentSymbol>>(result.value()));
- else
- m_model.clear();
- }
+ if (uri != m_uri)
+ return;
+ if (Utils::holds_alternative<QList<SymbolInformation>>(result))
+ m_model.setInfo(Utils::get<QList<SymbolInformation>>(result));
+ else if (Utils::holds_alternative<QList<DocumentSymbol>>(result))
+ m_model.setInfo(Utils::get<QList<DocumentSymbol>>(result));
+ else
+ m_model.clear();
}
void LanguageClientOutlineWidget::updateTextCursor(const QModelIndex &proxyIndex)
@@ -245,6 +205,8 @@ void LanguageClientOutlineWidget::updateSelectionInTree(const QTextCursor &curre
selection.select(m_model.indexForItem(item), m_model.indexForItem(item));
});
m_view.selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
+ if (!selection.isEmpty())
+ m_view.scrollTo(selection.indexes().first());
}
void LanguageClientOutlineWidget::onItemActivated(const QModelIndex &index)
diff --git a/src/plugins/languageclient/languageclientplugin.cpp b/src/plugins/languageclient/languageclientplugin.cpp
index 1d64097e62b..31643474f60 100644
--- a/src/plugins/languageclient/languageclientplugin.cpp
+++ b/src/plugins/languageclient/languageclientplugin.cpp
@@ -25,18 +25,37 @@
#include "languageclientplugin.h"
+#include "languageclientmanager.h"
+
#include "client.h"
namespace LanguageClient {
+static LanguageClientPlugin *m_instance = nullptr;
+
+LanguageClientPlugin::LanguageClientPlugin()
+{
+ m_instance = this;
+}
+
+LanguageClientPlugin::~LanguageClientPlugin()
+{
+ m_instance = nullptr;
+}
+
+LanguageClientPlugin *LanguageClientPlugin::instance()
+{
+ return m_instance;
+}
+
bool LanguageClientPlugin::initialize(const QStringList & /*arguments*/, QString * /*errorString*/)
{
+ LanguageClientManager::init();
return true;
}
void LanguageClientPlugin::extensionsInitialized()
{
- LanguageClientManager::init();
LanguageClientSettings::init();
}
@@ -45,6 +64,8 @@ ExtensionSystem::IPlugin::ShutdownFlag LanguageClientPlugin::aboutToShutdown()
LanguageClientManager::shutdown();
if (LanguageClientManager::clients().isEmpty())
return ExtensionSystem::IPlugin::SynchronousShutdown;
+ QTC_ASSERT(LanguageClientManager::instance(),
+ return ExtensionSystem::IPlugin::SynchronousShutdown);
connect(LanguageClientManager::instance(), &LanguageClientManager::shutdownFinished,
this, &ExtensionSystem::IPlugin::asynchronousShutdownFinished);
return ExtensionSystem::IPlugin::AsynchronousShutdown;
diff --git a/src/plugins/languageclient/languageclientplugin.h b/src/plugins/languageclient/languageclientplugin.h
index 2cff52cd7ba..8fbf66de034 100644
--- a/src/plugins/languageclient/languageclientplugin.h
+++ b/src/plugins/languageclient/languageclientplugin.h
@@ -38,7 +38,10 @@ class LanguageClientPlugin : public ExtensionSystem::IPlugin
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "LanguageClient.json")
public:
- LanguageClientPlugin() = default;
+ LanguageClientPlugin();
+ ~LanguageClientPlugin() override;
+
+ static LanguageClientPlugin *instance();
// IPlugin interface
private:
@@ -47,7 +50,6 @@ private:
ShutdownFlag aboutToShutdown() override;
private:
- LanguageClientManager m_clientManager;
LanguageClientOutlineWidgetFactory m_outlineFactory;
};
diff --git a/src/plugins/languageclient/languageclientquickfix.cpp b/src/plugins/languageclient/languageclientquickfix.cpp
index def01b17d81..70db31b8712 100644
--- a/src/plugins/languageclient/languageclientquickfix.cpp
+++ b/src/plugins/languageclient/languageclientquickfix.cpp
@@ -111,7 +111,7 @@ IAssistProposal *LanguageClientQuickFixAssistProcessor::perform(const AssistInte
cursor.select(QTextCursor::LineUnderCursor);
Range range(cursor);
params.setRange(range);
- auto uri = DocumentUri::fromFileName(Utils::FileName::fromString(interface->fileName()));
+ auto uri = DocumentUri::fromFileName(Utils::FilePath::fromString(interface->fileName()));
params.setTextDocument(uri);
CodeActionParams::CodeActionContext context;
context.setDiagnostics(m_client->diagnosticsAt(uri, range));
diff --git a/src/plugins/languageclient/languageclientsettings.cpp b/src/plugins/languageclient/languageclientsettings.cpp
index 74ca22d5f2e..a1b8db21691 100644
--- a/src/plugins/languageclient/languageclientsettings.cpp
+++ b/src/plugins/languageclient/languageclientsettings.cpp
@@ -30,7 +30,12 @@
#include "languageclient_global.h"
#include "languageclientinterface.h"
+#include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/icore.h>
+#include <coreplugin/idocument.h>
+#include <coreplugin/variablechooser.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/session.h>
#include <utils/algorithm.h>
#include <utils/delegates.h>
#include <utils/fancylineedit.h>
@@ -38,7 +43,6 @@
#include <utils/jsontreeitem.h>
#include <QBoxLayout>
-#include <QCheckBox>
#include <QComboBox>
#include <QCompleter>
#include <QCoreApplication>
@@ -56,7 +60,9 @@
#include <QTreeView>
constexpr char nameKey[] = "name";
+constexpr char idKey[] = "id";
constexpr char enabledKey[] = "enabled";
+constexpr char startupBehaviorKey[] = "startupBehavior";
constexpr char mimeTypeKey[] = "mimeType";
constexpr char filePatternKey[] = "filePattern";
constexpr char executableKey[] = "executable";
@@ -80,15 +86,15 @@ public:
bool setData(const QModelIndex &index, const QVariant &value, int role) final;
Qt::ItemFlags flags(const QModelIndex &index) const final;
- void reset(const QList<StdIOSettings *> &settings);
- QList<StdIOSettings *> settings() const { return m_settings; }
- QList<StdIOSettings *> removed() const { return m_removed; }
- StdIOSettings *settingForIndex(const QModelIndex &index) const;
- QModelIndex indexForSetting(StdIOSettings *setting) const;
+ void reset(const QList<BaseSettings *> &settings);
+ QList<BaseSettings *> settings() const { return m_settings; }
+ QList<BaseSettings *> removed() const { return m_removed; }
+ BaseSettings *settingForIndex(const QModelIndex &index) const;
+ QModelIndex indexForSetting(BaseSettings *setting) const;
private:
- QList<StdIOSettings *> m_settings; // owned
- QList<StdIOSettings *> m_removed;
+ QList<BaseSettings *> m_settings; // owned
+ QList<BaseSettings *> m_removed;
};
class LanguageClientSettingsPageWidget : public QWidget
@@ -104,7 +110,7 @@ private:
LanguageClientSettingsModel &m_settings;
QTreeView *m_view = nullptr;
struct CurrentSettings {
- StdIOSettings *setting = nullptr;
+ BaseSettings *setting = nullptr;
QWidget *widget = nullptr;
} m_currentSettings;
@@ -126,9 +132,10 @@ public:
void apply() override;
void finish() override;
+ QList<BaseSettings *> settings() const;
+
private:
LanguageClientSettingsModel m_model;
- QList<StdIOSettings *> m_settings; // owned
QPointer<LanguageClientSettingsPageWidget> m_widget;
};
@@ -207,6 +214,7 @@ void LanguageClientSettingsPageWidget::addItem()
{
const int row = m_settings.rowCount();
m_settings.insertRows(row);
+ m_view->setCurrentIndex(m_settings.index(row));
}
void LanguageClientSettingsPageWidget::deleteItem()
@@ -218,7 +226,7 @@ void LanguageClientSettingsPageWidget::deleteItem()
LanguageClientSettingsPage::LanguageClientSettingsPage()
{
- setId("LanguageClient.General");
+ setId(Constants::LANGUAGECLIENT_SETTINGS_PAGE);
setDisplayName(tr("General"));
setCategory(Constants::LANGUAGECLIENT_SETTINGS_CATEGORY);
setDisplayCategory(QCoreApplication::translate("LanguageClient",
@@ -231,7 +239,6 @@ LanguageClientSettingsPage::~LanguageClientSettingsPage()
{
if (m_widget)
delete m_widget;
- qDeleteAll(m_settings);
}
void LanguageClientSettingsPage::init()
@@ -250,44 +257,32 @@ QWidget *LanguageClientSettingsPage::widget()
void LanguageClientSettingsPage::apply()
{
- qDeleteAll(m_settings);
if (m_widget)
m_widget->applyCurrentSettings();
- m_settings = Utils::transform(m_model.settings(), [](const StdIOSettings *other){
- return dynamic_cast<StdIOSettings *>(other->copy());
- });
- LanguageClientSettings::toSettings(Core::ICore::settings(), m_settings);
-
- QList<StdIOSettings *> restarts = Utils::filtered(m_settings, &StdIOSettings::needsRestart);
- for (auto setting : restarts + m_model.removed()) {
- if (auto client = setting->m_client) {
- if (client->reachable())
- client->shutdown();
- else
- LanguageClientManager::deleteClient(client);
- }
- }
- for (StdIOSettings *setting : restarts) {
- if (setting && setting->isValid() && setting->m_enabled) {
- if (auto client = setting->createClient()) {
- setting->m_client = client;
- LanguageClientManager::startClient(client);
- }
- }
+ LanguageClientManager::applySettings();
+
+ for (BaseSettings *setting : m_model.removed()) {
+ for (Client *client : LanguageClientManager::clientForSetting(setting))
+ LanguageClientManager::shutdownClient(client);
}
if (m_widget) {
int row = m_widget->currentRow();
- m_model.reset(m_settings);
+ m_model.reset(LanguageClientManager::currentSettings());
m_widget->resetCurrentSettings(row);
} else {
- m_model.reset(m_settings);
+ m_model.reset(LanguageClientManager::currentSettings());
}
}
void LanguageClientSettingsPage::finish()
{
- m_model.reset(m_settings);
+ m_model.reset(LanguageClientManager::currentSettings());
+}
+
+QList<BaseSettings *> LanguageClientSettingsPage::settings() const
+{
+ return m_model.settings();
}
LanguageClientSettingsModel::~LanguageClientSettingsModel()
@@ -297,11 +292,11 @@ LanguageClientSettingsModel::~LanguageClientSettingsModel()
QVariant LanguageClientSettingsModel::data(const QModelIndex &index, int role) const
{
- StdIOSettings *setting = settingForIndex(index);
+ BaseSettings *setting = settingForIndex(index);
if (!setting)
return QVariant();
if (role == Qt::DisplayRole)
- return setting->m_name;
+ return Utils::globalMacroExpander()->expand(setting->m_name);
else if (role == Qt::CheckStateRole)
return setting->m_enabled ? Qt::Checked : Qt::Unchecked;
return QVariant();
@@ -332,7 +327,7 @@ bool LanguageClientSettingsModel::insertRows(int row, int count, const QModelInd
bool LanguageClientSettingsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
- StdIOSettings *setting = settingForIndex(index);
+ BaseSettings *setting = settingForIndex(index);
if (!setting || role != Qt::CheckStateRole)
return false;
@@ -348,26 +343,24 @@ Qt::ItemFlags LanguageClientSettingsModel::flags(const QModelIndex &/*index*/) c
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
}
-void LanguageClientSettingsModel::reset(const QList<StdIOSettings *> &settings)
+void LanguageClientSettingsModel::reset(const QList<BaseSettings *> &settings)
{
beginResetModel();
qDeleteAll(m_settings);
qDeleteAll(m_removed);
m_removed.clear();
- m_settings = Utils::transform(settings, [](const StdIOSettings *other){
- return dynamic_cast<StdIOSettings *>(other->copy());
- });
+ m_settings = Utils::transform(settings, [](const BaseSettings *other) { return other->copy(); });
endResetModel();
}
-StdIOSettings *LanguageClientSettingsModel::settingForIndex(const QModelIndex &index) const
+BaseSettings *LanguageClientSettingsModel::settingForIndex(const QModelIndex &index) const
{
if (!index.isValid() || index.row() >= m_settings.size())
return nullptr;
return m_settings[index.row()];
}
-QModelIndex LanguageClientSettingsModel::indexForSetting(StdIOSettings *setting) const
+QModelIndex LanguageClientSettingsModel::indexForSetting(BaseSettings *setting) const
{
const int index = m_settings.indexOf(setting);
return index < 0 ? QModelIndex() : createIndex(index, 0, setting);
@@ -378,6 +371,7 @@ void BaseSettings::applyFromSettingsWidget(QWidget *widget)
if (auto settingsWidget = qobject_cast<BaseSettingsWidget *>(widget)) {
m_name = settingsWidget->name();
m_languageFilter = settingsWidget->filter();
+ m_startBehavior = settingsWidget->startupBehavior();
}
}
@@ -388,7 +382,14 @@ QWidget *BaseSettings::createSettingsWidget(QWidget *parent) const
bool BaseSettings::needsRestart() const
{
- return m_client ? !m_enabled || m_client->needsRestart(this) : m_enabled;
+ const QVector<Client *> clients = LanguageClientManager::clientForSetting(this);
+ if (clients.isEmpty())
+ return m_enabled;
+ if (!m_enabled)
+ return true;
+ return Utils::anyOf(clients, [this](const Client *client) {
+ return client->needsRestart(this);
+ });
}
bool BaseSettings::isValid() const
@@ -396,23 +397,25 @@ bool BaseSettings::isValid() const
return !m_name.isEmpty();
}
-Client *BaseSettings::createClient() const
+Client *BaseSettings::createClient()
{
+ if (!isValid() || !m_enabled)
+ return nullptr;
BaseClientInterface *interface = createInterface();
- if (QTC_GUARD(interface)) {
- auto *client = new Client(interface);
- client->setName(m_name);
- client->setSupportedLanguage(m_languageFilter);
- return client;
- }
- return nullptr;
+ QTC_ASSERT(interface, return nullptr);
+ auto *client = new Client(interface);
+ client->setName(Utils::globalMacroExpander()->expand(m_name));
+ client->setSupportedLanguage(m_languageFilter);
+ return client;
}
QVariantMap BaseSettings::toMap() const
{
QVariantMap map;
map.insert(nameKey, m_name);
+ map.insert(idKey, m_id);
map.insert(enabledKey, m_enabled);
+ map.insert(startupBehaviorKey, m_startBehavior);
map.insert(mimeTypeKey, m_languageFilter.mimeTypes);
map.insert(filePatternKey, m_languageFilter.filePattern);
return map;
@@ -421,23 +424,31 @@ QVariantMap BaseSettings::toMap() const
void BaseSettings::fromMap(const QVariantMap &map)
{
m_name = map[nameKey].toString();
+ m_id = map.value(idKey, QUuid::createUuid().toString()).toString();
m_enabled = map[enabledKey].toBool();
+ m_startBehavior = BaseSettings::StartBehavior(
+ map.value(startupBehaviorKey, BaseSettings::RequiresFile).toInt());
m_languageFilter.mimeTypes = map[mimeTypeKey].toStringList();
m_languageFilter.filePattern = map[filePatternKey].toStringList();
}
-void LanguageClientSettings::init()
+static LanguageClientSettingsPage &settingsPage()
{
static LanguageClientSettingsPage settingsPage;
- settingsPage.init();
+ return settingsPage;
+}
+
+void LanguageClientSettings::init()
+{
+ settingsPage().init();
}
-QList<StdIOSettings *> LanguageClientSettings::fromSettings(QSettings *settingsIn)
+QList<BaseSettings *> LanguageClientSettings::fromSettings(QSettings *settingsIn)
{
settingsIn->beginGroup(settingsGroupKey);
auto variants = settingsIn->value(clientsKey).toList();
auto settings = Utils::transform(variants, [](const QVariant& var){
- auto settings = new StdIOSettings();
+ BaseSettings *settings = new StdIOSettings();
settings->fromMap(var.toMap());
return settings;
});
@@ -445,11 +456,17 @@ QList<StdIOSettings *> LanguageClientSettings::fromSettings(QSettings *settingsI
return settings;
}
-void LanguageClientSettings::toSettings(QSettings *settings, const QList<StdIOSettings *> &languageClientSettings)
+QList<BaseSettings *> LanguageClientSettings::currentPageSettings()
+{
+ return settingsPage().settings();
+}
+
+void LanguageClientSettings::toSettings(QSettings *settings,
+ const QList<BaseSettings *> &languageClientSettings)
{
settings->beginGroup(settingsGroupKey);
settings->setValue(clientsKey, Utils::transform(languageClientSettings,
- [](const StdIOSettings *setting){
+ [](const BaseSettings *setting){
return QVariant(setting->toMap());
}));
settings->endGroup();
@@ -473,11 +490,13 @@ bool StdIOSettings::needsRestart() const
{
if (BaseSettings::needsRestart())
return true;
- if (m_client.isNull())
- return false;
- if (auto stdIOInterface = qobject_cast<const StdIOClientInterface *>(m_client->clientInterface()))
- return stdIOInterface->needsRestart(this);
- return false;
+ return Utils::anyOf(LanguageClientManager::clientForSetting(this),
+ [this](QPointer<Client> client) {
+ if (auto stdIOInterface = qobject_cast<const StdIOClientInterface *>(
+ client->clientInterface()))
+ return stdIOInterface->needsRestart(this);
+ return false;
+ });
}
bool StdIOSettings::isValid() const
@@ -500,15 +519,39 @@ void StdIOSettings::fromMap(const QVariantMap &map)
m_arguments = map[argumentsKey].toString();
}
+QString StdIOSettings::arguments() const
+{
+ return Utils::globalMacroExpander()->expand(m_arguments);
+}
+
BaseClientInterface *StdIOSettings::createInterface() const
{
- return new StdIOClientInterface(m_executable, m_arguments);
+ return new StdIOClientInterface(m_executable, arguments());
}
-static QWidget *createCapabilitiesView(
- const LanguageServerProtocol::ServerCapabilities &capabilities)
+class JsonTreeItemDelegate : public QStyledItemDelegate
{
- auto root = new Utils::JsonTreeItem("Capabilities", QJsonValue(capabilities));
+public:
+ QString displayText(const QVariant &value, const QLocale &) const override
+ {
+ QString result = value.toString();
+ if (result.size() == 1) {
+ switch (result.at(0).toLatin1()) {
+ case '\n':
+ return QString("\\n");
+ case '\t':
+ return QString("\\t");
+ case '\r':
+ return QString("\\r");
+ }
+ }
+ return result;
+ }
+};
+
+static QWidget *createCapabilitiesView(const QJsonValue &capabilities)
+{
+ auto root = new Utils::JsonTreeItem("Capabilities", capabilities);
if (root->canFetchMore())
root->fetchMore();
@@ -520,19 +563,39 @@ static QWidget *createCapabilitiesView(
capabilitiesView->setModel(capabilitiesModel);
capabilitiesView->setAlternatingRowColors(true);
capabilitiesView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ capabilitiesView->setItemDelegate(new JsonTreeItemDelegate);
return capabilitiesView;
}
+static QString startupBehaviorString(BaseSettings::StartBehavior behavior)
+{
+ switch (behavior) {
+ case BaseSettings::AlwaysOn:
+ return QCoreApplication::translate("LanguageClient::BaseSettings", "Always On");
+ case BaseSettings::RequiresFile:
+ return QCoreApplication::translate("LanguageClient::BaseSettings", "Requires an Open File");
+ case BaseSettings::RequiresProject:
+ return QCoreApplication::translate("LanguageClient::BaseSettings",
+ "Start Server per Project");
+ default:
+ break;
+ }
+ return {};
+}
+
BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *parent)
: QWidget(parent)
, m_name(new QLineEdit(settings->m_name, this))
, m_mimeTypes(new QLabel(settings->m_languageFilter.mimeTypes.join(filterSeparator), this))
, m_filePattern(new QLineEdit(settings->m_languageFilter.filePattern.join(filterSeparator), this))
+ , m_startupBehavior(new QComboBox)
{
int row = 0;
auto *mainLayout = new QGridLayout;
mainLayout->addWidget(new QLabel(tr("Name:")), row, 0);
mainLayout->addWidget(m_name, row, 1);
+ auto chooser = new Core::VariableChooser(this);
+ chooser->addSupportedWidget(m_name);
mainLayout->addWidget(new QLabel(tr("Language:")), ++row, 0);
auto mimeLayout = new QHBoxLayout;
mimeLayout->addWidget(m_mimeTypes);
@@ -542,6 +605,12 @@ BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *pa
mainLayout->addLayout(mimeLayout, row, 1);
m_filePattern->setPlaceholderText(tr("File pattern"));
mainLayout->addWidget(m_filePattern, ++row, 1);
+ mainLayout->addWidget(new QLabel(tr("Startup behavior:")), ++row, 0);
+ for (int behavior = 0; behavior < BaseSettings::LastSentinel ; ++behavior)
+ m_startupBehavior->addItem(startupBehaviorString(BaseSettings::StartBehavior(behavior)));
+ m_startupBehavior->setCurrentIndex(settings->m_startBehavior);
+ mainLayout->addWidget(m_startupBehavior, row, 1);
+
connect(addMimeTypeButton, &QPushButton::pressed,
this, &BaseSettingsWidget::showAddMimeTypeDialog);
@@ -551,9 +620,13 @@ BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *pa
};
mainLayout->addWidget(new QLabel(tr("Capabilities:")), ++row, 0, Qt::AlignTop);
- if (Client *client = settings->m_client.data()) {
+ QVector<Client *> clients = LanguageClientManager::clientForSetting(settings);
+ if (clients.isEmpty()) {
+ mainLayout->addWidget(createInfoLabel());
+ } else { // TODO move the capabilities view into a new widget outside of the settings
+ Client *client = clients.first();
if (client->state() == Client::Initialized)
- mainLayout->addWidget(createCapabilitiesView(client->capabilities()));
+ mainLayout->addWidget(createCapabilitiesView(QJsonValue(client->capabilities())));
else
mainLayout->addWidget(createInfoLabel(), row, 1);
connect(client, &Client::finished, mainLayout, [mainLayout, row, createInfoLabel]() {
@@ -564,12 +637,9 @@ BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *pa
[mainLayout, row](
const LanguageServerProtocol::ServerCapabilities &capabilities) {
delete mainLayout->itemAtPosition(row, 1)->widget();
- mainLayout->addWidget(createCapabilitiesView(capabilities), row, 1);
+ mainLayout->addWidget(createCapabilitiesView(QJsonValue(capabilities)), row, 1);
});
- } else {
- mainLayout->addWidget(createInfoLabel());
}
-
setLayout(mainLayout);
}
@@ -580,8 +650,13 @@ QString BaseSettingsWidget::name() const
LanguageFilter BaseSettingsWidget::filter() const
{
- return {m_mimeTypes->text().split(filterSeparator),
- m_filePattern->text().split(filterSeparator)};
+ return {m_mimeTypes->text().split(filterSeparator, QString::SkipEmptyParts),
+ m_filePattern->text().split(filterSeparator, QString::SkipEmptyParts)};
+}
+
+BaseSettings::StartBehavior BaseSettingsWidget::startupBehavior() const
+{
+ return BaseSettings::StartBehavior(m_startupBehavior->currentIndex());
}
class MimeTypeModel : public QStringListModel
@@ -692,6 +767,9 @@ StdIOSettingsWidget::StdIOSettingsWidget(const StdIOSettings *settings, QWidget
m_executable->setExpectedKind(Utils::PathChooser::ExistingCommand);
m_executable->setPath(QDir::toNativeSeparators(settings->m_executable));
mainLayout->addWidget(m_arguments, baseRows + 1, 1);
+
+ auto chooser = new Core::VariableChooser(this);
+ chooser->addSupportedWidget(m_arguments);
}
QString StdIOSettingsWidget::executable() const
@@ -704,4 +782,23 @@ QString StdIOSettingsWidget::arguments() const
return m_arguments->text();
}
+bool LanguageFilter::isSupported(const Utils::FilePath &filePath, const QString &mimeType) const
+{
+ if (mimeTypes.contains(mimeType))
+ return true;
+ if (filePattern.isEmpty() && filePath.isEmpty())
+ return mimeTypes.isEmpty();
+ auto regexps = Utils::transform(filePattern, [](const QString &pattern){
+ return QRegExp(pattern, Utils::HostOsInfo::fileNameCaseSensitivity(), QRegExp::Wildcard);
+ });
+ return Utils::anyOf(regexps, [filePath](const QRegExp &reg){
+ return reg.exactMatch(filePath.toString()) || reg.exactMatch(filePath.fileName());
+ });
+}
+
+bool LanguageFilter::isSupported(const Core::IDocument *document) const
+{
+ return isSupported(document->filePath(), document->mimeType());
+}
+
} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientsettings.h b/src/plugins/languageclient/languageclientsettings.h
index bdf54e60525..1036348cb7b 100644
--- a/src/plugins/languageclient/languageclientsettings.h
+++ b/src/plugins/languageclient/languageclientsettings.h
@@ -30,14 +30,21 @@
#include <QAbstractItemModel>
#include <QLabel>
#include <QPointer>
+#include <QUuid>
#include <QWidget>
QT_BEGIN_NAMESPACE
-class QCheckBox;
+class QComboBox;
class QLineEdit;
QT_END_NAMESPACE
-namespace Utils { class PathChooser; }
+namespace Utils {
+class FilePath;
+class PathChooser;
+} // namespace Utils
+
+namespace Core { class IDocument; }
+namespace ProjectExplorer { class Project; }
namespace LanguageClient {
@@ -50,31 +57,36 @@ struct LanguageFilter
{
QStringList mimeTypes;
QStringList filePattern;
+ bool isSupported(const Utils::FilePath &filePath, const QString &mimeType) const;
+ bool isSupported(const Core::IDocument *document) const;
};
class BaseSettings
{
public:
BaseSettings() = default;
- BaseSettings(const QString &name, bool enabled, const LanguageFilter &filter)
- : m_name(name)
- , m_enabled(enabled)
- , m_languageFilter(filter)
- {}
virtual ~BaseSettings() = default;
+ enum StartBehavior {
+ AlwaysOn = 0,
+ RequiresFile,
+ RequiresProject,
+ LastSentinel
+ };
+
QString m_name = QString("New Language Server");
+ QString m_id = QUuid::createUuid().toString();
bool m_enabled = true;
+ StartBehavior m_startBehavior = RequiresFile;
LanguageFilter m_languageFilter;
- QPointer<Client> m_client; // not owned
virtual void applyFromSettingsWidget(QWidget *widget);
virtual QWidget *createSettingsWidget(QWidget *parent = nullptr) const;
virtual BaseSettings *copy() const { return new BaseSettings(*this); }
virtual bool needsRestart() const;
- virtual bool isValid() const ;
- Client *createClient() const;
+ virtual bool isValid() const;
+ Client *createClient();
virtual QVariantMap toMap() const;
virtual void fromMap(const QVariantMap &map);
@@ -85,19 +97,15 @@ protected:
BaseSettings(BaseSettings &&other) = default;
BaseSettings &operator=(const BaseSettings &other) = default;
BaseSettings &operator=(BaseSettings &&other) = default;
+
+private:
+ bool canStart(QList<const Core::IDocument *> documents) const;
};
class StdIOSettings : public BaseSettings
{
public:
StdIOSettings() = default;
- StdIOSettings(const QString &name, bool enabled, const LanguageFilter &filter,
- const QString &executable, const QString &arguments)
- : BaseSettings(name, enabled, filter)
- , m_executable(executable)
- , m_arguments(arguments)
- {}
-
~StdIOSettings() override = default;
QString m_executable;
@@ -110,6 +118,7 @@ public:
bool isValid() const override;
QVariantMap toMap() const override;
void fromMap(const QVariantMap &map) override;
+ QString arguments() const;
protected:
BaseClientInterface *createInterface() const override;
@@ -124,8 +133,9 @@ class LanguageClientSettings
{
public:
static void init();
- static QList<StdIOSettings *> fromSettings(QSettings *settings);
- static void toSettings(QSettings *settings, const QList<StdIOSettings *> &languageClientSettings);
+ static QList<BaseSettings *> fromSettings(QSettings *settings);
+ static QList<BaseSettings *> currentPageSettings();
+ static void toSettings(QSettings *settings, const QList<BaseSettings *> &languageClientSettings);
};
class BaseSettingsWidget : public QWidget
@@ -137,6 +147,9 @@ public:
QString name() const;
LanguageFilter filter() const;
+ BaseSettings::StartBehavior startupBehavior() const;
+ bool alwaysOn() const;
+ bool requiresProject() const;
private:
void showAddMimeTypeDialog();
@@ -144,6 +157,7 @@ private:
QLineEdit *m_name = nullptr;
QLabel *m_mimeTypes = nullptr;
QLineEdit *m_filePattern = nullptr;
+ QComboBox *m_startupBehavior = nullptr;
static constexpr char filterSeparator = ';';
};
diff --git a/src/plugins/languageclient/languageclientutils.cpp b/src/plugins/languageclient/languageclientutils.cpp
index 7a28c5e3a95..317af50f745 100644
--- a/src/plugins/languageclient/languageclientutils.cpp
+++ b/src/plugins/languageclient/languageclientutils.cpp
@@ -26,17 +26,23 @@
#include "languageclientutils.h"
#include "client.h"
+#include "languageclient_global.h"
+#include "languageclientmanager.h"
#include <coreplugin/editormanager/documentmodel.h>
+#include <coreplugin/icore.h>
#include <texteditor/codeassist/textdocumentmanipulatorinterface.h>
#include <texteditor/refactoringchanges.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <utils/textutils.h>
+#include <utils/utilsicons.h>
#include <QFile>
#include <QTextDocument>
+#include <QToolBar>
+#include <QToolButton>
using namespace LanguageServerProtocol;
using namespace Utils;
@@ -185,4 +191,84 @@ void updateCodeActionRefactoringMarker(Client *client,
}
}
+void updateEditorToolBar(Core::IEditor *editor)
+{
+ auto *textEditor = qobject_cast<BaseTextEditor *>(editor);
+ if (!textEditor)
+ return;
+ TextEditorWidget *widget = textEditor->editorWidget();
+ if (!widget)
+ return;
+
+ const Core::IDocument *document = editor->document();
+ QStringList clientsWithDoc;
+ for (auto client : LanguageClientManager::clients()) {
+ if (client->documentOpen(document))
+ clientsWithDoc << client->name();
+ }
+
+ static QMap<QWidget *, QAction *> actions;
+
+ if (actions.contains(widget)) {
+ auto action = actions[widget];
+ if (clientsWithDoc.isEmpty()) {
+ widget->toolBar()->removeAction(action);
+ actions.remove(widget);
+ } else {
+ action->setText(clientsWithDoc.join(';'));
+ }
+ } else if (!clientsWithDoc.isEmpty()) {
+ const QIcon icon
+ = Utils::Icon({{":/languageclient/images/languageclient.png",
+ Utils::Theme::IconsBaseColor}})
+ .icon();
+ actions[widget] = widget->toolBar()->addAction(icon, clientsWithDoc.join(';'), []() {
+ Core::ICore::showOptionsDialog(Constants::LANGUAGECLIENT_SETTINGS_PAGE);
+ });
+ QObject::connect(widget, &QWidget::destroyed, [widget]() {
+ actions.remove(widget);
+ });
+ }
+}
+
+const QIcon symbolIcon(int type)
+{
+ using namespace Utils::CodeModelIcon;
+ static QMap<SymbolKind, QIcon> icons;
+ if (type < int(SymbolKind::FirstSymbolKind) || type > int(SymbolKind::LastSymbolKind))
+ return {};
+ auto kind = static_cast<SymbolKind>(type);
+ if (!icons.contains(kind)) {
+ switch (kind) {
+ case SymbolKind::File: icons[kind] = Utils::Icons::NEWFILE.icon(); break;
+ case SymbolKind::Module: icons[kind] = iconForType(Namespace); break;
+ case SymbolKind::Namespace: icons[kind] = iconForType(Namespace); break;
+ case SymbolKind::Package: icons[kind] = iconForType(Namespace); break;
+ case SymbolKind::Class: icons[kind] = iconForType(Class); break;
+ case SymbolKind::Method: icons[kind] = iconForType(FuncPublic); break;
+ case SymbolKind::Property: icons[kind] = iconForType(Property); break;
+ case SymbolKind::Field: icons[kind] = iconForType(VarPublic); break;
+ case SymbolKind::Constructor: icons[kind] = iconForType(Class); break;
+ case SymbolKind::Enum: icons[kind] = iconForType(Enum); break;
+ case SymbolKind::Interface: icons[kind] = iconForType(Class); break;
+ case SymbolKind::Function: icons[kind] = iconForType(FuncPublic); break;
+ case SymbolKind::Variable: icons[kind] = iconForType(VarPublic); break;
+ case SymbolKind::Constant: icons[kind] = iconForType(VarPublic); break;
+ case SymbolKind::String: icons[kind] = iconForType(VarPublic); break;
+ case SymbolKind::Number: icons[kind] = iconForType(VarPublic); break;
+ case SymbolKind::Boolean: icons[kind] = iconForType(VarPublic); break;
+ case SymbolKind::Array: icons[kind] = iconForType(VarPublic); break;
+ case SymbolKind::Object: icons[kind] = iconForType(Class); break;
+ case SymbolKind::Key: icons[kind] = iconForType(Keyword); break;
+ case SymbolKind::Null: icons[kind] = iconForType(Keyword); break;
+ case SymbolKind::EnumMember: icons[kind] = iconForType(Enumerator); break;
+ case SymbolKind::Struct: icons[kind] = iconForType(Struct); break;
+ case SymbolKind::Event: icons[kind] = iconForType(FuncPublic); break;
+ case SymbolKind::Operator: icons[kind] = iconForType(FuncPublic); break;
+ case SymbolKind::TypeParameter: icons[kind] = iconForType(VarPublic); break;
+ }
+ }
+ return icons[kind];
+}
+
} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientutils.h b/src/plugins/languageclient/languageclientutils.h
index f07b6e18bfb..04925e6bf14 100644
--- a/src/plugins/languageclient/languageclientutils.h
+++ b/src/plugins/languageclient/languageclientutils.h
@@ -30,6 +30,8 @@
#include <texteditor/refactoroverlay.h>
+namespace Core { class IEditor; }
+
namespace TextEditor {
class TextDocument;
class TextDocumentManipulatorInterface;
@@ -48,5 +50,7 @@ void applyTextEdit(TextEditor::TextDocumentManipulatorInterface &manipulator,
void updateCodeActionRefactoringMarker(Client *client,
const LanguageServerProtocol::CodeAction &action,
const LanguageServerProtocol::DocumentUri &uri);
+void updateEditorToolBar(Core::IEditor *editor);
+const QIcon symbolIcon(int type);
} // namespace LanguageClient
diff --git a/src/plugins/languageclient/locatorfilter.cpp b/src/plugins/languageclient/locatorfilter.cpp
new file mode 100644
index 00000000000..973e0982743
--- /dev/null
+++ b/src/plugins/languageclient/locatorfilter.cpp
@@ -0,0 +1,316 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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 "locatorfilter.h"
+
+#include "languageclient_global.h"
+#include "languageclientmanager.h"
+#include "languageclientutils.h"
+
+#include <coreplugin/editormanager/editormanager.h>
+#include <languageserverprotocol/servercapabilities.h>
+#include <texteditor/textdocument.h>
+#include <texteditor/texteditor.h>
+#include <utils/fuzzymatcher.h>
+#include <utils/linecolumn.h>
+
+#include <QFutureWatcher>
+#include <QRegularExpression>
+
+using namespace LanguageServerProtocol;
+
+namespace LanguageClient {
+
+DocumentLocatorFilter::DocumentLocatorFilter()
+{
+ setId(Constants::LANGUAGECLIENT_DOCUMENT_FILTER_ID);
+ setDisplayName(Constants::LANGUAGECLIENT_DOCUMENT_FILTER_DISPLAY_NAME);
+ setShortcutString(".");
+ setIncludedByDefault(false);
+ setPriority(ILocatorFilter::Low);
+ connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
+ this, &DocumentLocatorFilter::updateCurrentClient);
+}
+
+void DocumentLocatorFilter::updateCurrentClient()
+{
+ Core::IEditor *editor = Core::EditorManager::currentEditor();
+ resetSymbols();
+ disconnect(m_resetSymbolsConnection);
+
+ if (Client *client = LanguageClientManager::clientForEditor(editor)) {
+ if (m_symbolCache != client->documentSymbolCache()) {
+ disconnect(m_updateSymbolsConnection);
+ m_symbolCache = client->documentSymbolCache();
+ m_updateSymbolsConnection = connect(m_symbolCache,
+ &DocumentSymbolCache::gotSymbols,
+ this,
+ &DocumentLocatorFilter::updateSymbols);
+ }
+ m_resetSymbolsConnection = connect(editor->document(),
+ &Core::IDocument::contentsChanged,
+ this,
+ &DocumentLocatorFilter::resetSymbols);
+ m_currentUri = DocumentUri::fromFileName(editor->document()->filePath());
+ } else {
+ disconnect(m_updateSymbolsConnection);
+ m_symbolCache.clear();
+ m_currentUri.clear();
+ }
+}
+
+void DocumentLocatorFilter::updateSymbols(const DocumentUri &uri,
+ const DocumentSymbolsResult &symbols)
+{
+ if (uri != m_currentUri)
+ return;
+ QMutexLocker locker(&m_mutex);
+ m_currentSymbols = symbols;
+ emit symbolsUpToDate({});
+}
+
+void DocumentLocatorFilter::resetSymbols()
+{
+ QMutexLocker locker(&m_mutex);
+ m_currentSymbols.reset();
+}
+
+Core::LocatorFilterEntry generateLocatorEntry(const SymbolInformation &info,
+ Core::ILocatorFilter *filter)
+{
+ Core::LocatorFilterEntry entry;
+ entry.filter = filter;
+ entry.displayName = info.name();
+ if (Utils::optional<QString> container = info.containerName())
+ entry.extraInfo = container.value_or(QString());
+ entry.displayIcon = symbolIcon(info.kind());
+ entry.internalData = QVariant::fromValue(info.location().toLink());
+ return entry;
+}
+
+Core::LocatorFilterEntry generateLocatorEntry(const DocumentSymbol &info,
+ Core::ILocatorFilter *filter)
+{
+ Core::LocatorFilterEntry entry;
+ entry.filter = filter;
+ entry.displayName = info.name();
+ if (Utils::optional<QString> detail = info.detail())
+ entry.extraInfo = detail.value_or(QString());
+ entry.displayIcon = symbolIcon(info.kind());
+ const Position &pos = info.range().start();
+ entry.internalData = QVariant::fromValue(Utils::LineColumn(pos.line(), pos.character()));
+ return entry;
+}
+
+template<class T>
+QList<Core::LocatorFilterEntry> DocumentLocatorFilter::generateEntries(const QList<T> &list,
+ const QString &filter)
+{
+ QList<Core::LocatorFilterEntry> entries;
+ FuzzyMatcher::CaseSensitivity caseSensitivity
+ = ILocatorFilter::caseSensitivity(filter) == Qt::CaseSensitive
+ ? FuzzyMatcher::CaseSensitivity::CaseSensitive
+ : FuzzyMatcher::CaseSensitivity::CaseInsensitive;
+ const QRegularExpression regexp = FuzzyMatcher::createRegExp(filter, caseSensitivity);
+ if (!regexp.isValid())
+ return entries;
+
+ for (const T &item : list) {
+ QRegularExpressionMatch match = regexp.match(item.name());
+ if (match.hasMatch())
+ entries << generateLocatorEntry(item, this);
+ }
+ return entries;
+}
+
+void DocumentLocatorFilter::prepareSearch(const QString &/*entry*/)
+{
+ QMutexLocker locker(&m_mutex);
+ if (m_symbolCache && !m_currentSymbols.has_value()) {
+ locker.unlock();
+ m_symbolCache->requestSymbols(m_currentUri);
+ }
+}
+
+QList<Core::LocatorFilterEntry> DocumentLocatorFilter::matchesFor(
+ QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry)
+{
+ if (!m_symbolCache)
+ return {};
+ QMutexLocker locker(&m_mutex);
+ if (!m_currentSymbols.has_value()) {
+ QEventLoop loop;
+ connect(this, &DocumentLocatorFilter::symbolsUpToDate, &loop, [&]() { loop.exit(1); });
+ QFutureWatcher<Core::LocatorFilterEntry> watcher;
+ watcher.setFuture(future.future());
+ connect(&watcher,
+ &QFutureWatcher<Core::LocatorFilterEntry>::canceled,
+ &loop,
+ &QEventLoop::quit);
+ locker.unlock();
+ if (!loop.exec())
+ return {};
+ locker.relock();
+ }
+
+ QTC_ASSERT(m_currentSymbols.has_value(), return {});
+
+ if (auto list = Utils::get_if<QList<DocumentSymbol>>(&m_currentSymbols.value()))
+ return generateEntries(*list, entry);
+ else if (auto list = Utils::get_if<QList<SymbolInformation>>(&m_currentSymbols.value()))
+ return generateEntries(*list, entry);
+
+ return {};
+}
+
+void DocumentLocatorFilter::accept(Core::LocatorFilterEntry selection,
+ QString * /*newText*/,
+ int * /*selectionStart*/,
+ int * /*selectionLength*/) const
+{
+ if (selection.internalData.canConvert<Utils::LineColumn>()) {
+ auto lineColumn = qvariant_cast<Utils::LineColumn>(selection.internalData);
+ Core::EditorManager::openEditorAt(m_currentUri.toFileName().toString(),
+ lineColumn.line + 1,
+ lineColumn.column);
+ } else if (selection.internalData.canConvert<Utils::Link>()) {
+ auto link = qvariant_cast<Utils::Link>(selection.internalData);
+ Core::EditorManager::openEditorAt(link.targetFileName, link.targetLine, link.targetColumn);
+ }
+}
+
+void DocumentLocatorFilter::refresh(QFutureInterface<void> & /*future*/) {}
+
+WorkspaceLocatorFilter::WorkspaceLocatorFilter()
+ : WorkspaceLocatorFilter(QVector<SymbolKind>())
+{}
+
+WorkspaceLocatorFilter::WorkspaceLocatorFilter(const QVector<SymbolKind> &filter)
+ : m_filterKinds(filter)
+{
+ setId(Constants::LANGUAGECLIENT_WORKSPACE_FILTER_ID);
+ setDisplayName(Constants::LANGUAGECLIENT_WORKSPACE_FILTER_DISPLAY_NAME);
+ setShortcutString(":");
+ setIncludedByDefault(false);
+ setPriority(ILocatorFilter::Low);
+}
+
+void WorkspaceLocatorFilter::prepareSearch(const QString &entry)
+{
+ m_pendingRequests.clear();
+ m_results.clear();
+
+ WorkspaceSymbolParams params;
+ params.setQuery(entry);
+
+ QMutexLocker locker(&m_mutex);
+ for (auto client : Utils::filtered(LanguageClientManager::clients(), &Client::reachable)) {
+ if (client->capabilities().workspaceSymbolProvider().value_or(false)) {
+ WorkspaceSymbolRequest request(params);
+ request.setResponseCallback(
+ [this, client](const WorkspaceSymbolRequest::Response &response) {
+ handleResponse(client, response);
+ });
+ m_pendingRequests[client] = request.id();
+ client->sendContent(request);
+ }
+ }
+}
+
+QList<Core::LocatorFilterEntry> WorkspaceLocatorFilter::matchesFor(
+ QFutureInterface<Core::LocatorFilterEntry> &future, const QString & /*entry*/)
+{
+ QMutexLocker locker(&m_mutex);
+ if (!m_pendingRequests.isEmpty()) {
+ QEventLoop loop;
+ connect(this, &WorkspaceLocatorFilter::allRequestsFinished, &loop, [&]() { loop.exit(1); });
+ QFutureWatcher<Core::LocatorFilterEntry> watcher;
+ watcher.setFuture(future.future());
+ connect(&watcher,
+ &QFutureWatcher<Core::LocatorFilterEntry>::canceled,
+ &loop,
+ &QEventLoop::quit);
+ locker.unlock();
+ if (!loop.exec())
+ return {};
+
+ locker.relock();
+ }
+
+
+ if (!m_filterKinds.isEmpty()) {
+ m_results = Utils::filtered(m_results, [&](const SymbolInformation &info) {
+ return m_filterKinds.contains(SymbolKind(info.kind()));
+ });
+ }
+ return Utils::transform(m_results,
+ [this](const SymbolInformation &info) {
+ return generateLocatorEntry(info, this);
+ })
+ .toList();
+}
+
+void WorkspaceLocatorFilter::accept(Core::LocatorFilterEntry selection,
+ QString * /*newText*/,
+ int * /*selectionStart*/,
+ int * /*selectionLength*/) const
+{
+ if (selection.internalData.canConvert<Utils::Link>()) {
+ auto link = qvariant_cast<Utils::Link>(selection.internalData);
+ Core::EditorManager::openEditorAt(link.targetFileName, link.targetLine, link.targetColumn);
+ }
+}
+
+void WorkspaceLocatorFilter::refresh(QFutureInterface<void> & /*future*/) {}
+
+void WorkspaceLocatorFilter::handleResponse(Client *client,
+ const WorkspaceSymbolRequest::Response &response)
+{
+ QMutexLocker locker(&m_mutex);
+ m_pendingRequests.remove(client);
+ auto result = response.result().value_or(LanguageClientArray<SymbolInformation>());
+ if (!result.isNull())
+ m_results.append(result.toList().toVector());
+ if (m_pendingRequests.isEmpty())
+ emit allRequestsFinished(QPrivateSignal());
+}
+
+WorkspaceClassLocatorFilter::WorkspaceClassLocatorFilter()
+ : WorkspaceLocatorFilter({SymbolKind::Class, SymbolKind::Struct})
+{
+ setId(Constants::LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_ID);
+ setDisplayName(Constants::LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DISPLAY_NAME);
+ setShortcutString("c");
+}
+
+WorkspaceMethodLocatorFilter::WorkspaceMethodLocatorFilter()
+ : WorkspaceLocatorFilter({SymbolKind::Method, SymbolKind::Function, SymbolKind::Constructor})
+{
+ setId(Constants::LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_ID);
+ setDisplayName(Constants::LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DISPLAY_NAME);
+ setShortcutString("m");
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/locatorfilter.h b/src/plugins/languageclient/locatorfilter.h
new file mode 100644
index 00000000000..a3aa9cd5975
--- /dev/null
+++ b/src/plugins/languageclient/locatorfilter.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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 "client.h"
+
+#include <coreplugin/locator/ilocatorfilter.h>
+#include <languageserverprotocol/lsptypes.h>
+#include <languageserverprotocol/languagefeatures.h>
+#include <languageserverprotocol/workspace.h>
+
+namespace Core { class IEditor; }
+
+namespace LanguageClient {
+
+class DocumentLocatorFilter : public Core::ILocatorFilter
+{
+ Q_OBJECT
+public:
+ DocumentLocatorFilter();
+
+ void updateCurrentClient();
+ void prepareSearch(const QString &entry) override;
+ QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
+ const QString &entry) override;
+ void accept(Core::LocatorFilterEntry selection,
+ QString *newText,
+ int *selectionStart,
+ int *selectionLength) const override;
+ void refresh(QFutureInterface<void> &future) override;
+
+signals:
+ void symbolsUpToDate(QPrivateSignal);
+
+protected:
+ QPointer<DocumentSymbolCache> m_symbolCache;
+ LanguageServerProtocol::DocumentUri m_currentUri;
+
+private:
+ void updateSymbols(const LanguageServerProtocol::DocumentUri &uri,
+ const LanguageServerProtocol::DocumentSymbolsResult &symbols);
+ void resetSymbols();
+
+ template<class T>
+ QList<Core::LocatorFilterEntry> generateEntries(const QList<T> &list, const QString &filter);
+
+ QMutex m_mutex;
+ QMetaObject::Connection m_updateSymbolsConnection;
+ QMetaObject::Connection m_resetSymbolsConnection;
+ Utils::optional<LanguageServerProtocol::DocumentSymbolsResult> m_currentSymbols;
+};
+
+class WorkspaceLocatorFilter : public Core::ILocatorFilter
+{
+ Q_OBJECT
+public:
+ WorkspaceLocatorFilter();
+
+ void prepareSearch(const QString &entry) override;
+ QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
+ const QString &entry) override;
+ void accept(Core::LocatorFilterEntry selection,
+ QString *newText,
+ int *selectionStart,
+ int *selectionLength) const override;
+ void refresh(QFutureInterface<void> &future) override;
+
+signals:
+ void allRequestsFinished(QPrivateSignal);
+
+protected:
+ explicit WorkspaceLocatorFilter(const QVector<LanguageServerProtocol::SymbolKind> &filter);
+
+private:
+ void handleResponse(Client *client,
+ const LanguageServerProtocol::WorkspaceSymbolRequest::Response &response);
+
+ QMutex m_mutex;
+ QMap<Client *, LanguageServerProtocol::MessageId> m_pendingRequests;
+ QVector<LanguageServerProtocol::SymbolInformation> m_results;
+ QVector<LanguageServerProtocol::SymbolKind> m_filterKinds;
+};
+
+class WorkspaceClassLocatorFilter : public WorkspaceLocatorFilter
+{
+public:
+ WorkspaceClassLocatorFilter();
+};
+
+class WorkspaceMethodLocatorFilter : public WorkspaceLocatorFilter
+{
+public:
+ WorkspaceMethodLocatorFilter();
+};
+
+} // namespace LanguageClient