// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "languageclientformatter.h" #include "client.h" #include "dynamiccapabilities.h" #include "languageclientutils.h" #include #include #include #include using namespace LanguageServerProtocol; using namespace Utils; namespace LanguageClient { LanguageClientFormatter::LanguageClientFormatter(TextEditor::TextDocument *document, Client *client) : m_client(client) , m_document(document) { m_cancelConnection = QObject::connect(document->document(), &QTextDocument::contentsChanged, [this]() { if (m_ignoreCancel) m_ignoreCancel = false; else cancelCurrentRequest(); }); } LanguageClientFormatter::~LanguageClientFormatter() { QObject::disconnect(m_cancelConnection); cancelCurrentRequest(); } static const FormattingOptions formattingOptions(const TextEditor::TabSettings &settings) { FormattingOptions options; options.setTabSize(settings.m_tabSize); options.setInsertSpace(settings.m_tabPolicy == TextEditor::TabSettings::SpacesOnlyTabPolicy); return options; } QFutureWatcher *LanguageClientFormatter::format( const QTextCursor &cursor, const TextEditor::TabSettings &tabSettings) { QTC_ASSERT(m_client, return nullptr); cancelCurrentRequest(); m_progress = QFutureInterface(); const FilePath &filePath = m_document->filePath(); const DynamicCapabilities dynamicCapabilities = m_client->dynamicCapabilities(); const QString method(DocumentRangeFormattingRequest::methodName); if (std::optional registered = dynamicCapabilities.isRegistered(method)) { if (!*registered) return nullptr; const TextDocumentRegistrationOptions option(dynamicCapabilities.option(method).toObject()); if (option.isValid() && !option.filterApplies(filePath, Utils::mimeTypeForName(m_document->mimeType()))) { return nullptr; } } else { const std::optional> &provider = m_client->capabilities().documentRangeFormattingProvider(); if (!provider.has_value()) return nullptr; if (std::holds_alternative(*provider) && !std::get(*provider)) return nullptr; } DocumentRangeFormattingParams params; const DocumentUri uri = m_client->hostPathToServerUri(filePath); params.setTextDocument(TextDocumentIdentifier(uri)); params.setOptions(formattingOptions(tabSettings)); if (!cursor.hasSelection()) { QTextCursor c = cursor; c.select(QTextCursor::LineUnderCursor); params.setRange(Range(c)); } else { params.setRange(Range(cursor)); } DocumentRangeFormattingRequest request(params); request.setResponseCallback([this](const DocumentRangeFormattingRequest::Response &response) { handleResponse(response); }); m_currentRequest = request.id(); m_client->sendMessage(request); // ignore first contents changed, because this function is called inside a begin/endEdit block m_ignoreCancel = true; m_progress.reportStarted(); auto watcher = new QFutureWatcher(); QObject::connect(watcher, &QFutureWatcher::canceled, [this]() { cancelCurrentRequest(); }); watcher->setFuture(m_progress.future()); return watcher; } void LanguageClientFormatter::cancelCurrentRequest() { if (QTC_GUARD(m_client) && m_currentRequest.has_value()) { m_progress.reportCanceled(); m_progress.reportFinished(); m_client->cancelRequest(*m_currentRequest); m_ignoreCancel = false; m_currentRequest = std::nullopt; } } void LanguageClientFormatter::handleResponse(const DocumentRangeFormattingRequest::Response &response) { m_currentRequest = std::nullopt; const std::optional &error = response.error(); if (QTC_GUARD(m_client) && error) m_client->log(*error); ChangeSet changeSet; if (std::optional> result = response.result()) { if (!result->isNull()) changeSet = editsToChangeSet(result->toList(), m_document->document()); } m_progress.reportResult(changeSet); m_progress.reportFinished(); } } // namespace LanguageClient