| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | /****************************************************************************
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** Copyright (C) 2018 The Qt Company Ltd. | 
					
						
							|  |  |  | ** Contact: https://www.qt.io/licensing/
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** This file is part of Qt Creator. | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** Commercial License Usage | 
					
						
							|  |  |  | ** Licensees holding valid commercial Qt licenses may use this file in | 
					
						
							|  |  |  | ** accordance with the commercial license agreement provided with the | 
					
						
							|  |  |  | ** Software or, alternatively, in accordance with the terms contained in | 
					
						
							|  |  |  | ** a written agreement between you and The Qt Company. For licensing terms | 
					
						
							|  |  |  | ** and conditions see https://www.qt.io/terms-conditions. For further
 | 
					
						
							|  |  |  | ** information use the contact form at https://www.qt.io/contact-us.
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ** GNU General Public License Usage | 
					
						
							|  |  |  | ** Alternatively, this file may be used under the terms of the GNU | 
					
						
							|  |  |  | ** General Public License version 3 as published by the Free Software | 
					
						
							|  |  |  | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
					
						
							|  |  |  | ** included in the packaging of this file. Please review the following | 
					
						
							|  |  |  | ** information to ensure the GNU General Public License requirements will | 
					
						
							|  |  |  | ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
 | 
					
						
							|  |  |  | ** | 
					
						
							|  |  |  | ****************************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "baseclient.h"
 | 
					
						
							|  |  |  | #include "languageclientmanager.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <coreplugin/icore.h>
 | 
					
						
							|  |  |  | #include <coreplugin/idocument.h>
 | 
					
						
							|  |  |  | #include <coreplugin/messagemanager.h>
 | 
					
						
							|  |  |  | #include <languageserverprotocol/diagnostics.h>
 | 
					
						
							|  |  |  | #include <languageserverprotocol/languagefeatures.h>
 | 
					
						
							|  |  |  | #include <languageserverprotocol/messages.h>
 | 
					
						
							|  |  |  | #include <languageserverprotocol/workspace.h>
 | 
					
						
							|  |  |  | #include <texteditor/semantichighlighter.h>
 | 
					
						
							|  |  |  | #include <texteditor/textdocument.h>
 | 
					
						
							|  |  |  | #include <texteditor/texteditor.h>
 | 
					
						
							|  |  |  | #include <projectexplorer/project.h>
 | 
					
						
							|  |  |  | #include <projectexplorer/session.h>
 | 
					
						
							|  |  |  | #include <utils/mimetypes/mimedatabase.h>
 | 
					
						
							| 
									
										
										
										
											2018-09-10 15:15:37 +02:00
										 |  |  | #include <utils/qtcprocess.h>
 | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | #include <utils/synchronousprocess.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QDebug>
 | 
					
						
							|  |  |  | #include <QLoggingCategory>
 | 
					
						
							|  |  |  | #include <QMessageBox>
 | 
					
						
							|  |  |  | #include <QPointer>
 | 
					
						
							| 
									
										
										
										
											2018-09-12 10:24:02 +02:00
										 |  |  | #include <QPushButton>
 | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | #include <QTextBlock>
 | 
					
						
							|  |  |  | #include <QTextCursor>
 | 
					
						
							|  |  |  | #include <QTextDocument>
 | 
					
						
							| 
									
										
										
										
											2018-09-19 09:36:32 +02:00
										 |  |  | #include <QTimer>
 | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | using namespace LanguageServerProtocol; | 
					
						
							|  |  |  | using namespace Utils; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace LanguageClient { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-12 09:33:30 +03:00
										 |  |  | static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client", QtWarningMsg); | 
					
						
							|  |  |  | static Q_LOGGING_CATEGORY(LOGLSPCLIENTV, "qtc.languageclient.messages", QtWarningMsg); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | BaseClient::BaseClient() | 
					
						
							|  |  |  |     : m_id(Core::Id::fromString(QUuid::createUuid().toString())) | 
					
						
							| 
									
										
										
										
											2018-10-16 07:00:48 +02:00
										 |  |  |     , m_completionProvider(this) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); | 
					
						
							|  |  |  |     m_contentHandler.insert(JsonRpcMessageHandler::jsonRpcMimeType(), | 
					
						
							|  |  |  |                             &JsonRpcMessageHandler::parseContent); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | BaseClient::~BaseClient() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_buffer.close(); | 
					
						
							| 
									
										
										
										
											2018-10-16 07:00:48 +02:00
										 |  |  |     // FIXME: instead of replacing the completion provider in the text document store the
 | 
					
						
							|  |  |  |     // completion provider as a prioritised list in the text document
 | 
					
						
							|  |  |  |     for (TextEditor::TextDocument *document : m_resetCompletionProvider) | 
					
						
							|  |  |  |         document->setCompletionAssistProvider(nullptr); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::initialize() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     using namespace ProjectExplorer; | 
					
						
							|  |  |  |     QTC_ASSERT(m_state == Uninitialized, return); | 
					
						
							|  |  |  |     qCDebug(LOGLSPCLIENT) << "initializing language server " << m_displayName; | 
					
						
							|  |  |  |     auto initRequest = new InitializeRequest(); | 
					
						
							|  |  |  |     if (auto startupProject = SessionManager::startupProject()) { | 
					
						
							|  |  |  |         auto params = initRequest->params().value_or(InitializeParams()); | 
					
						
							|  |  |  |         params.setRootUri(DocumentUri::fromFileName(startupProject->projectDirectory())); | 
					
						
							|  |  |  |         initRequest->setParams(params); | 
					
						
							|  |  |  |         params.setWorkSpaceFolders(Utils::transform(SessionManager::projects(), [](Project *pro){ | 
					
						
							|  |  |  |             return WorkSpaceFolder(pro->projectDirectory().toString(), pro->displayName()); | 
					
						
							|  |  |  |         })); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-20 07:45:22 +01:00
										 |  |  |     initRequest->setResponseCallback([this](const InitializeRequest::Response &initResponse){ | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |         intializeCallback(initResponse); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     // directly send data otherwise the state check would fail;
 | 
					
						
							|  |  |  |     initRequest->registerResponseHandler(&m_responseHandlers); | 
					
						
							|  |  |  |     sendData(initRequest->toBaseMessage().toData()); | 
					
						
							|  |  |  |     m_state = InitializeRequested; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::shutdown() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QTC_ASSERT(m_state == Initialized, emit finished(); return); | 
					
						
							|  |  |  |     qCDebug(LOGLSPCLIENT) << "shutdown language server " << m_displayName; | 
					
						
							|  |  |  |     ShutdownRequest shutdown; | 
					
						
							| 
									
										
										
										
											2018-11-20 07:45:22 +01:00
										 |  |  |     shutdown.setResponseCallback([this](const ShutdownRequest::Response &shutdownResponse){ | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |         shutDownCallback(shutdownResponse); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     sendContent(shutdown); | 
					
						
							|  |  |  |     m_state = ShutdownRequested; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | BaseClient::State BaseClient::state() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_state; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::openDocument(Core::IDocument *document) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     using namespace TextEditor; | 
					
						
							| 
									
										
										
										
											2018-10-10 14:26:57 +02:00
										 |  |  |     if (!isSupportedDocument(document)) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |         return; | 
					
						
							|  |  |  |     const FileName &filePath = document->filePath(); | 
					
						
							|  |  |  |     const QString method(DidOpenTextDocumentNotification::methodName); | 
					
						
							|  |  |  |     if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) { | 
					
						
							|  |  |  |         if (!registered.value()) | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         const TextDocumentRegistrationOptions option( | 
					
						
							|  |  |  |                     m_dynamicCapabilities.option(method).toObject()); | 
					
						
							|  |  |  |         if (option.isValid(nullptr) | 
					
						
							|  |  |  |                 && !option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType()))) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync | 
					
						
							|  |  |  |                = m_serverCapabilities.textDocumentSync()) { | 
					
						
							|  |  |  |         if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) { | 
					
						
							|  |  |  |             if (!options->openClose().value_or(true)) | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     auto textDocument = qobject_cast<TextDocument *>(document); | 
					
						
							|  |  |  |     TextDocumentItem item; | 
					
						
							| 
									
										
										
										
											2018-09-05 13:38:08 +02:00
										 |  |  |     item.setLanguageId(TextDocumentItem::mimeTypeToLanguageId(document->mimeType())); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     item.setUri(DocumentUri::fromFileName(filePath)); | 
					
						
							|  |  |  |     item.setText(QString::fromUtf8(document->contents())); | 
					
						
							|  |  |  |     item.setVersion(textDocument ? textDocument->document()->revision() : 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     connect(document, &Core::IDocument::contentsChanged, this, | 
					
						
							|  |  |  |             [this, document](){ | 
					
						
							|  |  |  |         documentContentsChanged(document); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     if (textDocument) { | 
					
						
							| 
									
										
										
										
											2018-10-16 07:00:48 +02:00
										 |  |  |         m_resetCompletionProvider << textDocument; | 
					
						
							|  |  |  |         textDocument->setCompletionAssistProvider(&m_completionProvider); | 
					
						
							|  |  |  |         connect(textDocument, &QObject::destroyed, this, [this, textDocument]{ | 
					
						
							|  |  |  |             m_resetCompletionProvider.remove(textDocument); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |         if (BaseTextEditor *editor = BaseTextEditor::textEditorForDocument(textDocument)) { | 
					
						
							| 
									
										
										
										
											2018-09-19 09:36:32 +02:00
										 |  |  |             if (QPointer<TextEditorWidget> widget = editor->editorWidget()) { | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |                 connect(widget, &TextEditorWidget::cursorPositionChanged, this, [this, widget](){ | 
					
						
							| 
									
										
										
										
											2018-09-19 09:36:32 +02:00
										 |  |  |                     // TODO This would better be a compressing timer
 | 
					
						
							|  |  |  |                     QTimer::singleShot(50, this, [this, widget]() { | 
					
						
							|  |  |  |                         if (widget) | 
					
						
							|  |  |  |                             cursorPositionChanged(widget); | 
					
						
							|  |  |  |                     }); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |                 }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_openedDocument.append(document->filePath()); | 
					
						
							|  |  |  |     sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item))); | 
					
						
							|  |  |  |     if (textDocument) | 
					
						
							|  |  |  |         requestDocumentSymbols(textDocument); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::sendContent(const IContent &content) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QTC_ASSERT(m_state == Initialized, return); | 
					
						
							|  |  |  |     content.registerResponseHandler(&m_responseHandlers); | 
					
						
							|  |  |  |     QString error; | 
					
						
							|  |  |  |     if (!QTC_GUARD(content.isValid(&error))) | 
					
						
							|  |  |  |         Core::MessageManager::write(error); | 
					
						
							|  |  |  |     sendData(content.toBaseMessage().toData()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::sendContent(const DocumentUri &uri, const IContent &content) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_openedDocument.contains(uri.toFileName())) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     sendContent(content); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::cancelRequest(const MessageId &id) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_responseHandlers.remove(id); | 
					
						
							|  |  |  |     sendContent(CancelRequest(CancelParameter(id))); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::closeDocument(const DidCloseTextDocumentParams ¶ms) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     sendContent(params.textDocument().uri(), DidCloseTextDocumentNotification(params)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::documentContentsSaved(Core::IDocument *document) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_openedDocument.contains(document->filePath())) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     bool sendMessage = true; | 
					
						
							|  |  |  |     bool includeText = false; | 
					
						
							|  |  |  |     const QString method(DidSaveTextDocumentNotification::methodName); | 
					
						
							|  |  |  |     if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) { | 
					
						
							|  |  |  |         sendMessage = registered.value(); | 
					
						
							|  |  |  |         if (sendMessage) { | 
					
						
							|  |  |  |             const TextDocumentSaveRegistrationOptions option( | 
					
						
							|  |  |  |                         m_dynamicCapabilities.option(method).toObject()); | 
					
						
							|  |  |  |             if (option.isValid(nullptr)) { | 
					
						
							|  |  |  |                 sendMessage = option.filterApplies(document->filePath(), | 
					
						
							|  |  |  |                                                    Utils::mimeTypeForName(document->mimeType())); | 
					
						
							|  |  |  |                 includeText = option.includeText().value_or(includeText); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync | 
					
						
							|  |  |  |                = m_serverCapabilities.textDocumentSync()) { | 
					
						
							|  |  |  |         if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) { | 
					
						
							|  |  |  |             if (Utils::optional<SaveOptions> saveOptions = options->save()) | 
					
						
							|  |  |  |                 includeText = saveOptions.value().includeText().value_or(includeText); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!sendMessage) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     DidSaveTextDocumentParams params( | 
					
						
							|  |  |  |                 TextDocumentIdentifier(DocumentUri::fromFileName(document->filePath()))); | 
					
						
							|  |  |  |     if (includeText) | 
					
						
							|  |  |  |         params.setText(QString::fromUtf8(document->contents())); | 
					
						
							|  |  |  |     sendContent(DidSaveTextDocumentNotification(params)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::documentWillSave(Core::IDocument *document) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     const FileName &filePath = document->filePath(); | 
					
						
							|  |  |  |     if (!m_openedDocument.contains(filePath)) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     bool sendMessage = true; | 
					
						
							|  |  |  |     const QString method(WillSaveTextDocumentNotification::methodName); | 
					
						
							|  |  |  |     if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) { | 
					
						
							|  |  |  |         sendMessage = registered.value(); | 
					
						
							|  |  |  |         if (sendMessage) { | 
					
						
							|  |  |  |             const TextDocumentRegistrationOptions option(m_dynamicCapabilities.option(method)); | 
					
						
							|  |  |  |             if (option.isValid(nullptr)) { | 
					
						
							|  |  |  |                 sendMessage = option.filterApplies(filePath, | 
					
						
							|  |  |  |                                                    Utils::mimeTypeForName(document->mimeType())); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync | 
					
						
							|  |  |  |                = m_serverCapabilities.textDocumentSync()) { | 
					
						
							|  |  |  |         if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) | 
					
						
							|  |  |  |             sendMessage = options->willSave().value_or(sendMessage); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!sendMessage) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     const WillSaveTextDocumentParams params( | 
					
						
							|  |  |  |                 TextDocumentIdentifier(DocumentUri::fromFileName(document->filePath()))); | 
					
						
							|  |  |  |     sendContent(WillSaveTextDocumentNotification(params)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::documentContentsChanged(Core::IDocument *document) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_openedDocument.contains(document->filePath())) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     const QString method(DidChangeTextDocumentNotification::methodName); | 
					
						
							|  |  |  |     TextDocumentSyncKind syncKind = m_serverCapabilities.textDocumentSyncKindHelper(); | 
					
						
							|  |  |  |     if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) { | 
					
						
							|  |  |  |         syncKind = registered.value() ? TextDocumentSyncKind::None : TextDocumentSyncKind::Full; | 
					
						
							|  |  |  |         if (syncKind != TextDocumentSyncKind::None) { | 
					
						
							|  |  |  |             const TextDocumentChangeRegistrationOptions option( | 
					
						
							|  |  |  |                                     m_dynamicCapabilities.option(method).toObject()); | 
					
						
							|  |  |  |             syncKind = option.isValid(nullptr) ? option.syncKind() : syncKind; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     auto textDocument = qobject_cast<TextEditor::TextDocument *>(document); | 
					
						
							|  |  |  |     if (syncKind != TextDocumentSyncKind::None) { | 
					
						
							|  |  |  |         const auto uri = DocumentUri::fromFileName(document->filePath()); | 
					
						
							|  |  |  |         VersionedTextDocumentIdentifier docId(uri); | 
					
						
							|  |  |  |         docId.setVersion(textDocument ? textDocument->document()->revision() : 0); | 
					
						
							|  |  |  |         const DidChangeTextDocumentParams params(docId, QString::fromUtf8(document->contents())); | 
					
						
							|  |  |  |         sendContent(DidChangeTextDocumentNotification(params)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (textDocument) | 
					
						
							|  |  |  |         requestDocumentSymbols(textDocument); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::registerCapabilities(const QList<Registration> ®istrations) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_dynamicCapabilities.registerCapability(registrations); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::unregisterCapabilities(const QList<Unregistration> &unregistrations) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_dynamicCapabilities.unregisterCapability(unregistrations); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  | template <typename Request> | 
					
						
							|  |  |  | static bool sendTextDocumentPositionParamsRequest(BaseClient *client, | 
					
						
							|  |  |  |                                                   const Request &request, | 
					
						
							|  |  |  |                                                   const DynamicCapabilities &dynamicCapabilities, | 
					
						
							|  |  |  |                                                   const optional<bool> &serverCapability) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  |     if (!request.isValid(nullptr)) | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     const DocumentUri uri = request.params().value().textDocument().uri(); | 
					
						
							|  |  |  |     const bool supportedFile = client->isSupportedUri(uri); | 
					
						
							|  |  |  |     bool sendMessage = dynamicCapabilities.isRegistered(Request::methodName).value_or(false); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     if (sendMessage) { | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  |         const TextDocumentRegistrationOptions option(dynamicCapabilities.option(Request::methodName)); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |         if (option.isValid(nullptr)) | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  |             sendMessage = option.filterApplies(FileName::fromString(QUrl(uri).adjusted(QUrl::PreferLocalFile).toString())); | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |             sendMessage = supportedFile; | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  |         sendMessage = serverCapability.value_or(sendMessage) && supportedFile; | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (sendMessage) | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  |         client->sendContent(request); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     return sendMessage; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  | bool BaseClient::findLinkAt(GotoDefinitionRequest &request) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return LanguageClient::sendTextDocumentPositionParamsRequest( | 
					
						
							|  |  |  |                 this, request, m_dynamicCapabilities, m_serverCapabilities.definitionProvider()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool BaseClient::findUsages(FindReferencesRequest &request) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return LanguageClient::sendTextDocumentPositionParamsRequest( | 
					
						
							|  |  |  |                 this, request, m_dynamicCapabilities, m_serverCapabilities.referencesProvider()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation &info) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!info.isValid(nullptr)) | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     const Position &start = info.location().range().start(); | 
					
						
							|  |  |  |     return TextEditor::HighlightingResult(start.line() + 1, start.character() + 1, | 
					
						
							|  |  |  |                                           info.name().length(), info.kind()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::requestDocumentSymbols(TextEditor::TextDocument *document) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // TODO: Do not use this information for highlighting but the overview model
 | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |     const FileName &filePath = document->filePath(); | 
					
						
							|  |  |  |     bool sendMessage = m_dynamicCapabilities.isRegistered(DocumentSymbolsRequest::methodName).value_or(false); | 
					
						
							|  |  |  |     if (sendMessage) { | 
					
						
							|  |  |  |         const TextDocumentRegistrationOptions option(m_dynamicCapabilities.option(DocumentSymbolsRequest::methodName)); | 
					
						
							|  |  |  |         if (option.isValid(nullptr)) | 
					
						
							|  |  |  |             sendMessage = option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType())); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         sendMessage = m_serverCapabilities.documentSymbolProvider().value_or(false); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!sendMessage) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     DocumentSymbolsRequest request( | 
					
						
							|  |  |  |                 DocumentSymbolParams(TextDocumentIdentifier(DocumentUri::fromFileName(filePath)))); | 
					
						
							|  |  |  |     request.setResponseCallback( | 
					
						
							|  |  |  |                 [doc = QPointer<TextEditor::TextDocument>(document)] | 
					
						
							| 
									
										
										
										
											2018-11-20 07:45:22 +01:00
										 |  |  |                 (DocumentSymbolsRequest::Response response){ | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |         if (!doc) | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         const DocumentSymbolsResult result = response.result().value_or(DocumentSymbolsResult()); | 
					
						
							|  |  |  |         if (!holds_alternative<QList<SymbolInformation>>(result)) | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         const auto &symbols = get<QList<SymbolInformation>>(result); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         QFutureInterface<TextEditor::HighlightingResult> future; | 
					
						
							|  |  |  |         for (const SymbolInformation &symbol : symbols) | 
					
						
							|  |  |  |             future.reportResult(createHighlightingResult(symbol)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const TextEditor::FontSettings &fs = doc->fontSettings(); | 
					
						
							|  |  |  |         QHash<int, QTextCharFormat> formatMap; | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::File         )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_STRING); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Module       )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_STRING); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Namespace    )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_STRING); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Package      )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_STRING); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Class        )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_TYPE); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Method       )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_FUNCTION); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Property     )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_FIELD); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Field        )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_FIELD); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Constructor  )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_FUNCTION); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Enum         )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_TYPE); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Interface    )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_TYPE); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Function     )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_FUNCTION); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Variable     )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_LOCAL); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Constant     )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_LOCAL); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::String       )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_STRING); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Number       )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_NUMBER); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Boolean      )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_KEYWORD); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Array        )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_STRING); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Object       )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_LOCAL); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Key          )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_LOCAL); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Null         )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_KEYWORD); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::EnumMember   )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_ENUMERATION); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Struct       )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_TYPE); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Event        )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_STRING); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Operator     )] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_OPERATOR); | 
					
						
							|  |  |  |         formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::TypeParameter)] | 
					
						
							|  |  |  |                 = fs.toTextCharFormat(TextEditor::C_LOCAL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         TextEditor::SemanticHighlighter::incrementalApplyExtraAdditionalFormats( | 
					
						
							|  |  |  |                     doc->syntaxHighlighter(), future.future(), 0, future.resultCount() - 1, | 
					
						
							|  |  |  |                     formatMap); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     sendContent(request); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::cursorPositionChanged(TextEditor::TextEditorWidget *widget) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     const auto uri = DocumentUri::fromFileName(widget->textDocument()->filePath()); | 
					
						
							|  |  |  |     if (m_dynamicCapabilities.isRegistered(DocumentHighlightsRequest::methodName).value_or(false)) { | 
					
						
							|  |  |  |         TextDocumentRegistrationOptions option( | 
					
						
							|  |  |  |                     m_dynamicCapabilities.option(DocumentHighlightsRequest::methodName)); | 
					
						
							|  |  |  |         if (!option.filterApplies(widget->textDocument()->filePath())) | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |     } else if (!m_serverCapabilities.documentHighlightProvider().value_or(false)) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto runningRequest = m_highlightRequests.find(uri); | 
					
						
							|  |  |  |     if (runningRequest != m_highlightRequests.end()) | 
					
						
							|  |  |  |         cancelRequest(runningRequest.value()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     DocumentHighlightsRequest request(TextDocumentPositionParams(uri, widget->textCursor())); | 
					
						
							|  |  |  |     request.setResponseCallback( | 
					
						
							|  |  |  |                 [widget = QPointer<TextEditor::TextEditorWidget>(widget), this, uri] | 
					
						
							| 
									
										
										
										
											2018-11-20 07:45:22 +01:00
										 |  |  |                 (DocumentHighlightsRequest::Response response) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     { | 
					
						
							|  |  |  |         m_highlightRequests.remove(uri); | 
					
						
							|  |  |  |         if (!widget) | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         QList<QTextEdit::ExtraSelection> selections; | 
					
						
							|  |  |  |         const DocumentHighlightsResult result = response.result().value_or(DocumentHighlightsResult()); | 
					
						
							|  |  |  |         if (!holds_alternative<QList<DocumentHighlight>>(result)) { | 
					
						
							|  |  |  |             widget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const QTextCharFormat &format = | 
					
						
							|  |  |  |                 widget->textDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES); | 
					
						
							|  |  |  |         QTextDocument *document = widget->document(); | 
					
						
							|  |  |  |         for (const auto &highlight : get<QList<DocumentHighlight>>(result)) { | 
					
						
							|  |  |  |             QTextEdit::ExtraSelection selection{widget->textCursor(), format}; | 
					
						
							|  |  |  |             const int &start = highlight.range().start().toPositionInDocument(document); | 
					
						
							|  |  |  |             const int &end = highlight.range().end().toPositionInDocument(document); | 
					
						
							|  |  |  |             if (start < 0 || end < 0) | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             selection.cursor.setPosition(start); | 
					
						
							|  |  |  |             selection.cursor.setPosition(end, QTextCursor::KeepAnchor); | 
					
						
							|  |  |  |             selections << selection; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         widget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     m_highlightRequests[uri] = request.id(); | 
					
						
							|  |  |  |     sendContent(request); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::projectOpened(ProjectExplorer::Project *project) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!sendWorkspceFolderChanges()) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     WorkspaceFoldersChangeEvent event; | 
					
						
							|  |  |  |     event.setAdded({WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())}); | 
					
						
							|  |  |  |     DidChangeWorkspaceFoldersParams params; | 
					
						
							|  |  |  |     params.setEvent(event); | 
					
						
							|  |  |  |     DidChangeWorkspaceFoldersNotification change(params); | 
					
						
							|  |  |  |     sendContent(change); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::projectClosed(ProjectExplorer::Project *project) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!sendWorkspceFolderChanges()) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     WorkspaceFoldersChangeEvent event; | 
					
						
							|  |  |  |     event.setRemoved({WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())}); | 
					
						
							|  |  |  |     DidChangeWorkspaceFoldersParams params; | 
					
						
							|  |  |  |     params.setEvent(event); | 
					
						
							|  |  |  |     DidChangeWorkspaceFoldersNotification change(params); | 
					
						
							|  |  |  |     sendContent(change); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-10 14:26:57 +02:00
										 |  |  | void BaseClient::setSupportedLanguage(const LanguageFilter &filter) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2018-10-10 14:26:57 +02:00
										 |  |  |     m_languagFilter = filter; | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-10 14:26:57 +02:00
										 |  |  | bool BaseClient::isSupportedDocument(const Core::IDocument *document) const | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2018-10-10 14:26:57 +02:00
										 |  |  |     QTC_ASSERT(document, return false); | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  |     return isSupportedFile(document->filePath(), document->mimeType()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool BaseClient::isSupportedFile(const Utils::FileName &filePath, const QString &mimeType) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_languagFilter.mimeTypes.isEmpty() && m_languagFilter.filePattern.isEmpty()) | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     if (m_languagFilter.mimeTypes.contains(mimeType)) | 
					
						
							| 
									
										
										
										
											2018-10-10 14:26:57 +02:00
										 |  |  |         return true; | 
					
						
							|  |  |  |     auto regexps = Utils::transform(m_languagFilter.filePattern, [](const QString &pattern){ | 
					
						
							|  |  |  |         return QRegExp(pattern, Utils::HostOsInfo::fileNameCaseSensitivity(), QRegExp::Wildcard); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  |     return Utils::anyOf(regexps, [filePath](const QRegExp ®){ | 
					
						
							| 
									
										
										
										
											2018-10-10 14:26:57 +02:00
										 |  |  |         return reg.exactMatch(filePath.toString()) || reg.exactMatch(filePath.fileName()); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-28 08:16:19 +01:00
										 |  |  | bool BaseClient::isSupportedUri(const DocumentUri &uri) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return isSupportedFile(uri.toFileName(), | 
					
						
							|  |  |  |                            Utils::mimeTypeForFile(uri.toFileName().fileName()).name()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-19 07:05:46 +02:00
										 |  |  | bool BaseClient::needsRestart(const BaseSettings *settings) const | 
					
						
							| 
									
										
										
										
											2018-09-18 10:43:17 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2018-10-19 07:05:46 +02:00
										 |  |  |     QTC_ASSERT(settings, return false); | 
					
						
							|  |  |  |     return m_languagFilter.mimeTypes != settings->m_languageFilter.mimeTypes | 
					
						
							|  |  |  |             || m_languagFilter.filePattern != settings->m_languageFilter.filePattern; | 
					
						
							| 
									
										
										
										
											2018-09-18 10:43:17 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 10:00:29 +02:00
										 |  |  | bool BaseClient::reset() | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2018-09-14 10:00:29 +02:00
										 |  |  |     if (!m_restartsLeft) | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     --m_restartsLeft; | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     m_state = Uninitialized; | 
					
						
							|  |  |  |     m_responseHandlers.clear(); | 
					
						
							|  |  |  |     m_buffer.close(); | 
					
						
							|  |  |  |     m_buffer.setData(nullptr); | 
					
						
							|  |  |  |     m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); | 
					
						
							|  |  |  |     m_openedDocument.clear(); | 
					
						
							|  |  |  |     m_serverCapabilities = ServerCapabilities(); | 
					
						
							|  |  |  |     m_dynamicCapabilities.reset(); | 
					
						
							| 
									
										
										
										
											2018-09-14 10:00:29 +02:00
										 |  |  |     return true; | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::setError(const QString &message) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     log(message); | 
					
						
							|  |  |  |     m_state = Error; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::log(const QString &message, Core::MessageManager::PrintToOutputPaneFlag flag) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Core::MessageManager::write(QString("LanguageClient %1: %2").arg(name(), message), flag); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-23 09:45:51 +01:00
										 |  |  | const ServerCapabilities &BaseClient::capabilities() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_serverCapabilities; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const DynamicCapabilities &BaseClient::dynamicCapabilities() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_dynamicCapabilities; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-12 10:24:02 +02:00
										 |  |  | void BaseClient::log(const ShowMessageParams &message, | 
					
						
							| 
									
										
										
										
											2018-09-12 08:14:25 +02:00
										 |  |  |                      Core::MessageManager::PrintToOutputPaneFlag flag) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     log(message.toString(), flag); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-12 10:24:02 +02:00
										 |  |  | void BaseClient::showMessageBox(const ShowMessageRequestParams &message, const MessageId &id) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     auto box = new QMessageBox(); | 
					
						
							|  |  |  |     box->setText(message.toString()); | 
					
						
							|  |  |  |     box->setAttribute(Qt::WA_DeleteOnClose); | 
					
						
							|  |  |  |     switch (message.type()) { | 
					
						
							|  |  |  |     case Error: box->setIcon(QMessageBox::Critical); break; | 
					
						
							|  |  |  |     case Warning: box->setIcon(QMessageBox::Warning); break; | 
					
						
							|  |  |  |     case Info: box->setIcon(QMessageBox::Information); break; | 
					
						
							|  |  |  |     case Log: box->setIcon(QMessageBox::NoIcon); break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     QHash<QAbstractButton *, MessageActionItem> itemForButton; | 
					
						
							|  |  |  |     if (const Utils::optional<QList<MessageActionItem>> actions = message.actions()) { | 
					
						
							|  |  |  |         for (const MessageActionItem &action : actions.value()) | 
					
						
							|  |  |  |             itemForButton.insert(box->addButton(action.title(), QMessageBox::InvalidRole), action); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     box->setModal(true); | 
					
						
							|  |  |  |     connect(box, &QMessageBox::finished, this, [=]{ | 
					
						
							| 
									
										
										
										
											2018-11-20 07:45:22 +01:00
										 |  |  |         ShowMessageRequest::Response response; | 
					
						
							| 
									
										
										
										
											2018-09-12 10:24:02 +02:00
										 |  |  |         response.setId(id); | 
					
						
							|  |  |  |         const MessageActionItem &item = itemForButton.value(box->clickedButton()); | 
					
						
							|  |  |  |         response.setResult(item.isValid(nullptr) ? LanguageClientValue<MessageActionItem>(item) | 
					
						
							|  |  |  |                                                  : LanguageClientValue<MessageActionItem>()); | 
					
						
							|  |  |  |         sendContent(response); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     box->show(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | void BaseClient::handleResponse(const MessageId &id, const QByteArray &content, QTextCodec *codec) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (auto handler = m_responseHandlers[id]) | 
					
						
							|  |  |  |         handler(content, codec); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::handleMethod(const QString &method, MessageId id, const IContent *content) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QStringList error; | 
					
						
							|  |  |  |     bool paramsValid = true; | 
					
						
							|  |  |  |     if (method == PublishDiagnosticsNotification::methodName) { | 
					
						
							|  |  |  |         auto params = dynamic_cast<const PublishDiagnosticsNotification *>(content)->params().value_or(PublishDiagnosticsParams()); | 
					
						
							|  |  |  |         paramsValid = params.isValid(&error); | 
					
						
							|  |  |  |         if (paramsValid) | 
					
						
							|  |  |  |             LanguageClientManager::publishDiagnostics(m_id, params); | 
					
						
							|  |  |  |     } else if (method == LogMessageNotification::methodName) { | 
					
						
							|  |  |  |         auto params = dynamic_cast<const LogMessageNotification *>(content)->params().value_or(LogMessageParams()); | 
					
						
							|  |  |  |         paramsValid = params.isValid(&error); | 
					
						
							| 
									
										
										
										
											2018-09-12 10:24:02 +02:00
										 |  |  |         if (paramsValid) | 
					
						
							|  |  |  |             log(params, Core::MessageManager::Flash); | 
					
						
							|  |  |  |     } else if (method == ShowMessageNotification::methodName) { | 
					
						
							|  |  |  |         auto params = dynamic_cast<const ShowMessageNotification *>(content)->params().value_or(ShowMessageParams()); | 
					
						
							|  |  |  |         paramsValid = params.isValid(&error); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |         if (paramsValid) | 
					
						
							|  |  |  |             log(params); | 
					
						
							| 
									
										
										
										
											2018-09-12 10:24:02 +02:00
										 |  |  |     } else if (method == ShowMessageRequest::methodName) { | 
					
						
							| 
									
										
										
										
											2018-11-14 01:21:20 +01:00
										 |  |  |         auto request = dynamic_cast<const ShowMessageRequest *>(content); | 
					
						
							| 
									
										
										
										
											2018-09-12 10:24:02 +02:00
										 |  |  |         auto params = request->params().value_or(ShowMessageRequestParams()); | 
					
						
							|  |  |  |         paramsValid = params.isValid(&error); | 
					
						
							|  |  |  |         if (paramsValid) { | 
					
						
							|  |  |  |             showMessageBox(params, request->id()); | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2018-11-20 07:45:22 +01:00
										 |  |  |             ShowMessageRequest::Response response; | 
					
						
							| 
									
										
										
										
											2018-09-12 10:24:02 +02:00
										 |  |  |             response.setId(request->id()); | 
					
						
							| 
									
										
										
										
											2019-01-07 13:24:01 +01:00
										 |  |  |             ResponseError<std::nullptr_t> error; | 
					
						
							| 
									
										
										
										
											2018-09-12 10:24:02 +02:00
										 |  |  |             const QString errorMessage = | 
					
						
							|  |  |  |                     QString("Could not parse ShowMessageRequest parameter of '%1': \"%2\"") | 
					
						
							|  |  |  |                     .arg(request->id().toString(), | 
					
						
							|  |  |  |                          QString::fromUtf8(QJsonDocument(params).toJson())); | 
					
						
							|  |  |  |             error.setMessage(errorMessage); | 
					
						
							|  |  |  |             response.setError(error); | 
					
						
							|  |  |  |             sendContent(response); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     } else if (method == RegisterCapabilityRequest::methodName) { | 
					
						
							|  |  |  |         auto params = dynamic_cast<const RegisterCapabilityRequest *>(content)->params().value_or(RegistrationParams()); | 
					
						
							|  |  |  |         paramsValid = params.isValid(&error); | 
					
						
							|  |  |  |         if (paramsValid) | 
					
						
							|  |  |  |             m_dynamicCapabilities.registerCapability(params.registrations()); | 
					
						
							|  |  |  |     } else if (method == UnregisterCapabilityRequest::methodName) { | 
					
						
							|  |  |  |         auto params = dynamic_cast<const UnregisterCapabilityRequest *>(content)->params().value_or(UnregistrationParams()); | 
					
						
							|  |  |  |         paramsValid = params.isValid(&error); | 
					
						
							|  |  |  |         if (paramsValid) | 
					
						
							|  |  |  |             m_dynamicCapabilities.unregisterCapability(params.unregistrations()); | 
					
						
							|  |  |  |     } else if (id.isValid(&error)) { | 
					
						
							|  |  |  |         Response<JsonObject, JsonObject> response; | 
					
						
							|  |  |  |         response.setId(id); | 
					
						
							|  |  |  |         ResponseError<JsonObject> error; | 
					
						
							|  |  |  |         error.setCode(ResponseError<JsonObject>::MethodNotFound); | 
					
						
							|  |  |  |         response.setError(error); | 
					
						
							|  |  |  |         sendContent(response); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     std::reverse(error.begin(), error.end()); | 
					
						
							|  |  |  |     if (!paramsValid) { | 
					
						
							|  |  |  |         log(tr("Invalid parameter in \"%1\": %2").arg(method, error.join("->")), | 
					
						
							|  |  |  |             Core::MessageManager::Flash); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     delete content; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-20 07:45:22 +01:00
										 |  |  | void BaseClient::intializeCallback(const InitializeRequest::Response &initResponse) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     QTC_ASSERT(m_state == InitializeRequested, return); | 
					
						
							|  |  |  |     if (optional<ResponseError<InitializeError>> error = initResponse.error()) { | 
					
						
							|  |  |  |         if (error.value().data().has_value() | 
					
						
							|  |  |  |                 && error.value().data().value().retry().value_or(false)) { | 
					
						
							| 
									
										
										
										
											2018-10-24 06:56:14 +02:00
										 |  |  |             const QString title(tr("Language Server \"%1\" Initialize Error").arg(m_displayName)); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |             auto result = QMessageBox::warning(Core::ICore::dialogParent(), | 
					
						
							|  |  |  |                                                title, | 
					
						
							|  |  |  |                                                error.value().message(), | 
					
						
							|  |  |  |                                                QMessageBox::Retry | QMessageBox::Cancel, | 
					
						
							|  |  |  |                                                QMessageBox::Retry); | 
					
						
							|  |  |  |             if (result == QMessageBox::Retry) { | 
					
						
							|  |  |  |                 m_state = Uninitialized; | 
					
						
							|  |  |  |                 initialize(); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         setError(tr("Initialize error: ") + error.value().message()); | 
					
						
							|  |  |  |         emit finished(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const optional<InitializeResult> &_result = initResponse.result(); | 
					
						
							|  |  |  |     if (!_result.has_value()) {// continue on ill formed result
 | 
					
						
							|  |  |  |         log(tr("No initialize result.")); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         const InitializeResult &result = _result.value(); | 
					
						
							|  |  |  |         QStringList error; | 
					
						
							|  |  |  |         if (!result.isValid(&error)) // continue on ill formed result
 | 
					
						
							|  |  |  |             log(tr("Initialize result is not valid: ") + error.join("->")); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         m_serverCapabilities = result.capabilities().value_or(ServerCapabilities()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " initialized"; | 
					
						
							|  |  |  |     m_state = Initialized; | 
					
						
							|  |  |  |     sendContent(InitializeNotification()); | 
					
						
							|  |  |  |     emit initialized(m_serverCapabilities); | 
					
						
							|  |  |  |     for (auto openedDocument : Core::DocumentModel::openedDocuments()) | 
					
						
							|  |  |  |         openDocument(openedDocument); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-20 07:45:22 +01:00
										 |  |  | void BaseClient::shutDownCallback(const ShutdownRequest::Response &shutdownResponse) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     QTC_ASSERT(m_state == ShutdownRequested, return); | 
					
						
							| 
									
										
										
										
											2019-01-07 13:24:01 +01:00
										 |  |  |     optional<ShutdownRequest::Response::Error> errorValue = shutdownResponse.error(); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     if (errorValue.has_value()) { | 
					
						
							| 
									
										
										
										
											2019-01-07 13:24:01 +01:00
										 |  |  |         ShutdownRequest::Response::Error error = errorValue.value(); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |         qDebug() << error; | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // directly send data otherwise the state check would fail;
 | 
					
						
							|  |  |  |     sendData(ExitNotification().toBaseMessage().toData()); | 
					
						
							|  |  |  |     qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " shutdown"; | 
					
						
							|  |  |  |     m_state = Shutdown; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool BaseClient::sendWorkspceFolderChanges() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_dynamicCapabilities.isRegistered( | 
					
						
							|  |  |  |                 DidChangeWorkspaceFoldersNotification::methodName).value_or(false)) { | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (auto workspace = m_serverCapabilities.workspace()) { | 
					
						
							|  |  |  |         if (auto folder = workspace.value().workspaceFolders()) { | 
					
						
							|  |  |  |             if (folder.value().supported().value_or(false)) { | 
					
						
							|  |  |  |                 // holds either the Id for deregistration or whether it is registered
 | 
					
						
							|  |  |  |                 auto notification = folder.value().changeNotifications().value_or(false); | 
					
						
							|  |  |  |                 return holds_alternative<QString>(notification) | 
					
						
							|  |  |  |                         || (holds_alternative<bool>(notification) && get<bool>(notification)); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void BaseClient::parseData(const QByteArray &data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     const qint64 preWritePosition = m_buffer.pos(); | 
					
						
							| 
									
										
										
										
											2018-09-13 09:47:13 +02:00
										 |  |  |     if (!m_buffer.atEnd()) | 
					
						
							|  |  |  |         m_buffer.seek(preWritePosition + m_buffer.bytesAvailable()); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     m_buffer.write(data); | 
					
						
							|  |  |  |     m_buffer.seek(preWritePosition); | 
					
						
							|  |  |  |     while (!m_buffer.atEnd()) { | 
					
						
							|  |  |  |         QString parseError; | 
					
						
							|  |  |  |         BaseMessage::parse(&m_buffer, parseError, m_currentMessage); | 
					
						
							|  |  |  |         if (!parseError.isEmpty()) | 
					
						
							|  |  |  |             log(parseError); | 
					
						
							|  |  |  |         if (!m_currentMessage.isComplete()) | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         if (auto handler = m_contentHandler[m_currentMessage.mimeType]){ | 
					
						
							|  |  |  |             QString parseError; | 
					
						
							|  |  |  |             handler(m_currentMessage.content, m_currentMessage.codec, parseError, | 
					
						
							|  |  |  |                     [this](MessageId id, const QByteArray &content, QTextCodec *codec){ | 
					
						
							|  |  |  |                 this->handleResponse(id, content, codec); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |                     [this](const QString &method, MessageId id, const IContent *content){ | 
					
						
							|  |  |  |                 this->handleMethod(method, id, content); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             if (!parseError.isEmpty()) | 
					
						
							|  |  |  |                 log(parseError); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             log(tr("Cannot handle content of type: %1").arg(QLatin1String(m_currentMessage.mimeType))); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         m_currentMessage = BaseMessage(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-01-23 13:00:49 +01:00
										 |  |  |     if (m_buffer.atEnd()) { | 
					
						
							|  |  |  |         m_buffer.close(); | 
					
						
							|  |  |  |         m_buffer.setData(nullptr); | 
					
						
							|  |  |  |         m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 15:15:37 +02:00
										 |  |  | StdIOClient::StdIOClient(const QString &executable, const QString &arguments) | 
					
						
							|  |  |  |     : m_executable(executable) | 
					
						
							|  |  |  |     , m_arguments(arguments) | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     connect(&m_process, &QProcess::readyReadStandardError, | 
					
						
							|  |  |  |             this, &StdIOClient::readError); | 
					
						
							|  |  |  |     connect(&m_process, &QProcess::readyReadStandardOutput, | 
					
						
							|  |  |  |             this, &StdIOClient::readOutput); | 
					
						
							|  |  |  |     connect(&m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), | 
					
						
							|  |  |  |             this, &StdIOClient::onProcessFinished); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 15:15:37 +02:00
										 |  |  |     m_process.setArguments(Utils::QtcProcess::splitArgs(m_arguments)); | 
					
						
							|  |  |  |     m_process.setProgram(m_executable); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | StdIOClient::~StdIOClient() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Utils::SynchronousProcess::stopProcess(m_process); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-18 10:43:17 +02:00
										 |  |  | bool StdIOClient::needsRestart(const StdIOSettings *settings) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_executable != settings->m_executable || m_arguments != settings->m_arguments; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  | bool StdIOClient::start() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_process.start(); | 
					
						
							| 
									
										
										
										
											2018-10-15 08:39:38 +02:00
										 |  |  |     if (!m_process.waitForStarted() || m_process.state() != QProcess::Running) { | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |         setError(m_process.errorString()); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void StdIOClient::setWorkingDirectory(const QString &workingDirectory) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_process.setWorkingDirectory(workingDirectory); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void StdIOClient::sendData(const QByteArray &data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_process.state() != QProcess::Running) { | 
					
						
							|  |  |  |         log(tr("Cannot send data to unstarted server %1").arg(m_process.program())); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     qCDebug(LOGLSPCLIENTV) << "StdIOClient send data:"; | 
					
						
							|  |  |  |     qCDebug(LOGLSPCLIENTV).noquote() << data; | 
					
						
							|  |  |  |     m_process.write(data); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void StdIOClient::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (exitStatus == QProcess::CrashExit) | 
					
						
							| 
									
										
										
										
											2018-10-23 12:56:09 +02:00
										 |  |  |         setError(tr("Crashed with exit code %1: %2").arg(exitCode, m_process.error())); | 
					
						
							| 
									
										
										
										
											2018-07-13 12:33:46 +02:00
										 |  |  |     emit finished(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void StdIOClient::readError() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     qCDebug(LOGLSPCLIENTV) << "StdIOClient std err:\n"; | 
					
						
							|  |  |  |     qCDebug(LOGLSPCLIENTV).noquote() << m_process.readAllStandardError(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void StdIOClient::readOutput() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     const QByteArray &out = m_process.readAllStandardOutput(); | 
					
						
							|  |  |  |     qDebug(LOGLSPCLIENTV) << "StdIOClient std out:\n"; | 
					
						
							|  |  |  |     qDebug(LOGLSPCLIENTV).noquote() << out; | 
					
						
							|  |  |  |     parseData(out); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } // namespace LanguageClient
 |