// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cocolanguageclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace LanguageClient; using namespace LanguageServerProtocol; using namespace Utils; using namespace Core; namespace Coco { using Key = LanguageServerProtocol::Key; CocoLanguageClient::CocoLanguageClient(const FilePath &coco, const FilePath &csmes) : Client(clientInterface(coco, csmes)) { setName("Coco"); hoverHandler()->setPreferDiagnosticts(false); setActivatable(false); LanguageFilter allFiles; allFiles.filePattern = QStringList{"*"}; setSupportedLanguage(allFiles); connect(EditorManager::instance(), &EditorManager::documentOpened, this, &CocoLanguageClient::onDocumentOpened); connect(EditorManager::instance(), &EditorManager::documentClosed, this, &CocoLanguageClient::onDocumentClosed); connect(EditorManager::instance(), &EditorManager::editorOpened, this, &CocoLanguageClient::handleEditorOpened); for (IDocument *openDocument : DocumentModel::openedDocuments()) onDocumentOpened(openDocument); ClientInfo info; info.setName("CocoQtCreator"); info.setVersion(QGuiApplication::applicationDisplayName()); setClientInfo(info); initClientCapabilities(); } CocoLanguageClient::~CocoLanguageClient() { const QList &editors = Core::DocumentModel::editorsForOpenedDocuments(); for (Core::IEditor *editor : editors) { if (auto textEditor = qobject_cast(editor)) textEditor->editorWidget()->removeHoverHandler(hoverHandler()); } } BaseClientInterface *CocoLanguageClient::clientInterface(const FilePath &coco, const FilePath &csmes) { auto interface = new StdIOClientInterface(); interface->setCommandLine(CommandLine(coco, {"--lsp-stdio", csmes.toUserOutput()})); return interface; } enum class CocoDiagnosticSeverity { Error = 1, Warning = 2, Information = 3, Hint = 4, CodeAdded = 100, PartiallyCovered = 101, NotCovered = 102, FullyCovered = 103, ManuallyValidated = 104, DeadCode = 105, ExecutionCountTooLow = 106, NotCoveredInfo = 107, CoveredInfo = 108, ManuallyValidatedInfo = 109 }; static TextEditor::TextStyle styleForSeverity(const CocoDiagnosticSeverity &severity) { using namespace TextEditor; switch (severity) { case CocoDiagnosticSeverity::Error: return C_ERROR; case CocoDiagnosticSeverity::Warning: return C_WARNING; case CocoDiagnosticSeverity::Information: return C_WARNING; case CocoDiagnosticSeverity::Hint: return C_WARNING; case CocoDiagnosticSeverity::CodeAdded: return C_COCO_CODE_ADDED; case CocoDiagnosticSeverity::PartiallyCovered: return C_COCO_PARTIALLY_COVERED; case CocoDiagnosticSeverity::NotCovered: return C_COCO_NOT_COVERED; case CocoDiagnosticSeverity::FullyCovered: return C_COCO_FULLY_COVERED; case CocoDiagnosticSeverity::ManuallyValidated: return C_COCO_MANUALLY_VALIDATED; case CocoDiagnosticSeverity::DeadCode: return C_COCO_DEAD_CODE; case CocoDiagnosticSeverity::ExecutionCountTooLow: return C_COCO_EXECUTION_COUNT_TOO_LOW; case CocoDiagnosticSeverity::NotCoveredInfo: return C_COCO_NOT_COVERED_INFO; case CocoDiagnosticSeverity::CoveredInfo: return C_COCO_COVERED_INFO; case CocoDiagnosticSeverity::ManuallyValidatedInfo: return C_COCO_MANUALLY_VALIDATED_INFO; } return C_TEXT; } class CocoDiagnostic : public Diagnostic { public: using Diagnostic::Diagnostic; std::optional cocoSeverity() const { if (auto val = optionalValue(severityKey)) return std::make_optional(static_cast(*val)); return std::nullopt; } }; class CocoTextMark : public TextEditor::TextMark { public: CocoTextMark(TextEditor::TextDocument *doc, const CocoDiagnostic &diag, const Id &clientId) : TextEditor::TextMark(doc, diag.range().start().line() + 1, {"Coco", clientId}) , m_severity(diag.cocoSeverity()) { setLineAnnotation(diag.message()); setToolTip(diag.message()); updateAnnotationColor(); } QColor annotationColor() const override { return m_annotationColor.isValid() ? m_annotationColor : TextEditor::TextMark::annotationColor(); } void updateAnnotationColor() { if (m_severity) { const TextEditor::TextStyle style = styleForSeverity(*m_severity); m_annotationColor = TextEditor::TextEditorSettings::fontSettings().formatFor(style).foreground(); } } std::optional m_severity; QColor m_annotationColor; }; class CocoDiagnosticManager : public DiagnosticManager { public: CocoDiagnosticManager(Client *client) : DiagnosticManager(client) { connect(TextEditor::TextEditorSettings::instance(), &TextEditor::TextEditorSettings::fontSettingsChanged, this, &CocoDiagnosticManager::fontSettingsChanged); setExtraSelectionsId("CocoExtraSelections"); } private: void fontSettingsChanged() { forAllMarks([](TextEditor::TextMark *mark){ static_cast(mark)->updateAnnotationColor(); mark->updateMarker(); }); } TextEditor::TextMark *createTextMark(TextEditor::TextDocument *doc, const Diagnostic &diagnostic, bool /*isProjectFile*/) const override { const CocoDiagnostic cocoDiagnostic(diagnostic); if (std::optional severity = cocoDiagnostic.cocoSeverity()) return new CocoTextMark(doc, cocoDiagnostic, client()->id()); return nullptr; } QTextEdit::ExtraSelection createDiagnosticSelection(const Diagnostic &diagnostic, QTextDocument *textDocument) const override { if (std::optional severity = CocoDiagnostic(diagnostic) .cocoSeverity()) { QTextCursor cursor(textDocument); cursor.setPosition(diagnostic.range().start().toPositionInDocument(textDocument)); cursor.setPosition(diagnostic.range().end().toPositionInDocument(textDocument), QTextCursor::KeepAnchor); const TextEditor::TextStyle style = styleForSeverity(*severity); QTextCharFormat format = TextEditor::TextEditorSettings::fontSettings() .toTextCharFormat(style); format.clearForeground(); return QTextEdit::ExtraSelection{cursor, format}; } return {}; } void setDiagnostics(const FilePath &filePath, const QList &diagnostics, const std::optional &version) override { DiagnosticManager::setDiagnostics(filePath, diagnostics, version); showDiagnostics(filePath, client()->documentVersion(filePath)); } }; DiagnosticManager *CocoLanguageClient::createDiagnosticManager() { return new CocoDiagnosticManager(this); } void CocoLanguageClient::handleDiagnostics(const PublishDiagnosticsParams ¶ms) { using namespace TextEditor; Client::handleDiagnostics(params); TextDocument *document = documentForFilePath(serverUriToHostPath(params.uri())); for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(document)) editor->editorWidget()->addHoverHandler(hoverHandler()); } class CocoTextDocumentCapabilities : public TextDocumentClientCapabilities { public: using TextDocumentClientCapabilities::TextDocumentClientCapabilities; void enableCodecoverageSupport() { JsonObject coverageSupport(QJsonObject{{"codeCoverageSupport", true}}); insert(Key("publishDiagnostics"), coverageSupport); } }; void CocoLanguageClient::initClientCapabilities() { ClientCapabilities capabilities = defaultClientCapabilities(); CocoTextDocumentCapabilities textDocumentCapabilities( capabilities.textDocument().value_or(TextDocumentClientCapabilities())); textDocumentCapabilities.enableCodecoverageSupport(); capabilities.setTextDocument(textDocumentCapabilities); setClientCapabilities(capabilities); } void CocoLanguageClient::onDocumentOpened(IDocument *document) { if (auto textDocument = qobject_cast(document)) openDocument(textDocument); } void CocoLanguageClient::onDocumentClosed(IDocument *document) { if (auto textDocument = qobject_cast(document)) closeDocument(textDocument); } void CocoLanguageClient::handleEditorOpened(IEditor *editor) { if (auto textEditor = qobject_cast(editor); textEditor && hasDiagnostics(textEditor->textDocument())) { textEditor->editorWidget()->addHoverHandler(hoverHandler()); } } } // namespace Coco