From 3baf305f9ba0192d662ab0ed44eac2910c1e4524 Mon Sep 17 00:00:00 2001 From: Fawzi Mohamed Date: Wed, 28 Sep 2022 11:16:49 +0200 Subject: [PATCH] Automatic qmlls support (experimental) Looks for qmlls (the qml language server of Qt) and if available and set in the preferences uses it instead of the embedded code model for the supported features. Its usage is driven by two flags that can be set in the QtQuick > QML/JS Editing preferences: "use qmlls" activates the use of qmlls if available; "use latest qmlls" always uses the qmlls of the latest Qt, instead of the one of the target (with the one used to compile QtCreator as fallback). To support disabling/enabling of qmlls as soon as one changes the preferences the singleton QmllsSettingsManager can emit a signal on changes. It also keeps track of the latest qmlls binary known. QmlJS::ModelmanagerInterface::ProjectInfo is also extended to keep track of the qmlls binary. QmlJSEditorDocument uses the ProjectInfo and QmllsSettingsManager to decide if a LanguageClient::Client should be started for that document. The client uses the QmllsClient subclass to keep track of the path of the qmlls clients and use the same qmlls process or all files that use the same binary. Currently qmlls <6.4.0 are not considered because they might have too many issues. The enabling/disabling of warnings and highlight is a bit cumbersome because they are handled together in the semantic highlighter, but must be handled separately depending on the qmlls capabilities. The disabling is done at the latest moment stopping the visualization of the embedded model warnings/highlights/suggestions. The computation of the semantic info is not suppressed to support the other features (find usages, semantic highlighting if active,...). When qmlls supports more features a complete removal of the semantic info construction could be evaluated. Change-Id: I3487e1680841025cabba6b339fbfe820ef83f858 Reviewed-by: David Schulz --- src/libs/qmljs/qmljsmodelmanagerinterface.cpp | 19 ++ src/libs/qmljs/qmljsmodelmanagerinterface.h | 3 + src/plugins/qmljseditor/CMakeLists.txt | 4 +- .../qmljseditor/qmljseditingsettingspage.cpp | 47 +++-- .../qmljseditor/qmljseditingsettingspage.h | 19 +- src/plugins/qmljseditor/qmljseditor.qbs | 5 + .../qmljseditor/qmljseditordocument.cpp | 172 +++++++++++++++++- .../qmljseditor/qmljseditordocument_p.h | 24 +++ src/plugins/qmljseditor/qmljseditorplugin.cpp | 2 + .../qmljseditor/qmljssemantichighlighter.cpp | 44 ++++- .../qmljseditor/qmljssemantichighlighter.h | 4 + src/plugins/qmljseditor/qmllsclient.cpp | 73 ++++++++ src/plugins/qmljseditor/qmllsclient.h | 25 +++ src/plugins/qmljseditor/qmllssettings.cpp | 106 +++++++++++ src/plugins/qmljseditor/qmllssettings.h | 45 +++++ src/plugins/qmljstools/qmljsmodelmanager.cpp | 4 + src/plugins/qmlprojectmanager/qmlproject.cpp | 23 +-- 17 files changed, 575 insertions(+), 44 deletions(-) create mode 100644 src/plugins/qmljseditor/qmllsclient.cpp create mode 100644 src/plugins/qmljseditor/qmllsclient.h create mode 100644 src/plugins/qmljseditor/qmllssettings.cpp create mode 100644 src/plugins/qmljseditor/qmllssettings.h diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index fc31c30e1b7..8826da30c7d 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -113,6 +113,9 @@ ModelManagerInterface::ModelManagerInterface(QObject *parent) m_defaultProjectInfo.qtQmlPath = FilePath::fromUserInput(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)); + m_defaultProjectInfo.qmllsPath = ModelManagerInterface::qmllsForBinPath( + FilePath::fromUserInput(QLibraryInfo::location(QLibraryInfo::BinariesPath)), + QLibraryInfo::version()); m_defaultProjectInfo.qtVersionString = QLibraryInfo::version().toString(); updateImportPaths(); @@ -216,6 +219,16 @@ ModelManagerInterface::WorkingCopy ModelManagerInterface::workingCopy() return WorkingCopy(); } +FilePath ModelManagerInterface::qmllsForBinPath(const Utils::FilePath &binPath, const QVersionNumber &version) +{ + if (version < QVersionNumber(6,4,0)) + return {}; + QString qmllsExe = "qmlls"; + if (HostOsInfo::isWindowsHost()) + qmllsExe = "qmlls.exe"; + return binPath.resolvePath(qmllsExe); +} + void ModelManagerInterface::activateScan() { if (!m_shouldScanImports) { @@ -415,6 +428,10 @@ bool pInfoLessThanImports(const ModelManagerInterface::ProjectInfo &p1, return true; if (p1.qtQmlPath > p2.qtQmlPath) return false; + if (p1.qmllsPath < p2.qmllsPath) + return true; + if (p1.qmllsPath > p2.qmllsPath) + return false; const PathsAndLanguages &s1 = p1.importPaths; const PathsAndLanguages &s2 = p2.importPaths; if (s1.size() < s2.size()) @@ -628,6 +645,8 @@ ModelManagerInterface::ProjectInfo ModelManagerInterface::projectInfoForPath( res.qtQmlPath = pInfo.qtQmlPath; res.qtVersionString = pInfo.qtVersionString; } + if (res.qmllsPath.isEmpty()) + res.qmllsPath = pInfo.qmllsPath; res.applicationDirectories.append(pInfo.applicationDirectories); for (const auto &importPath : pInfo.importPaths) res.importPaths.maybeInsert(importPath); diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h index e5d29a90ff8..b8a733e6810 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.h +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h @@ -20,6 +20,7 @@ #include #include #include +#include QT_FORWARD_DECLARE_CLASS(QTimer) @@ -62,6 +63,7 @@ public: Utils::Environment qmlDumpEnvironment; Utils::FilePath qtQmlPath; + Utils::FilePath qmllsPath; QString qtVersionString; QmlJS::QmlLanguageBundles activeBundle; QmlJS::QmlLanguageBundles extendedBundle; @@ -109,6 +111,7 @@ public: static ModelManagerInterface *instanceForFuture(const QFuture &future); static void writeWarning(const QString &msg); static WorkingCopy workingCopy(); + static Utils::FilePath qmllsForBinPath(const Utils::FilePath &binPath, const QVersionNumber &v); QmlJS::Snapshot snapshot() const; QmlJS::Snapshot newestSnapshot() const; diff --git a/src/plugins/qmljseditor/CMakeLists.txt b/src/plugins/qmljseditor/CMakeLists.txt index ab061f059da..122bb39aed1 100644 --- a/src/plugins/qmljseditor/CMakeLists.txt +++ b/src/plugins/qmljseditor/CMakeLists.txt @@ -1,8 +1,10 @@ add_qtc_plugin(QmlJSEditor DEPENDS LanguageUtils QmlJS QmlEditorWidgets - PLUGIN_DEPENDS Core ProjectExplorer QmlJSTools TextEditor + PLUGIN_DEPENDS Core ProjectExplorer QmlJSTools TextEditor LanguageClient SOURCES qmlexpressionundercursor.cpp qmlexpressionundercursor.h + qmllsclient.h qmllsclient.cpp + qmllssettings.h qmllssettings.cpp qmljsautocompleter.cpp qmljsautocompleter.h qmljscompletionassist.cpp qmljscompletionassist.h qmljscomponentfromobjectdef.cpp qmljscomponentfromobjectdef.h diff --git a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp index 22031a5d360..58ddc91d5eb 100644 --- a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp +++ b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp @@ -20,20 +20,13 @@ const char AUTO_FORMAT_ONLY_CURRENT_PROJECT[] = "QmlJSEditor.AutoFormatOnlyCurre const char QML_CONTEXTPANE_KEY[] = "QmlJSEditor.ContextPaneEnabled"; const char QML_CONTEXTPANEPIN_KEY[] = "QmlJSEditor.ContextPanePinned"; const char FOLD_AUX_DATA[] = "QmlJSEditor.FoldAuxData"; +const char USE_QMLLS[] = "QmlJSEditor.UseQmlls"; +const char USE_LATEST_QMLLS[] = "QmlJSEditor.UseLatestQmlls"; const char UIQML_OPEN_MODE[] = "QmlJSEditor.openUiQmlMode"; using namespace QmlJSEditor; using namespace QmlJSEditor::Internal; -QmlJsEditingSettings::QmlJsEditingSettings() - : m_enableContextPane(false), - m_pinContextPane(false), - m_autoFormatOnSave(false), - m_autoFormatOnlyCurrentProject(false), - m_foldAuxData(false), - m_uiQmlOpenMode("") -{} - void QmlJsEditingSettings::set() { if (get() != *this) @@ -50,6 +43,8 @@ void QmlJsEditingSettings::fromSettings(QSettings *settings) = settings->value(AUTO_FORMAT_ONLY_CURRENT_PROJECT, QVariant(false)).toBool(); m_foldAuxData = settings->value(FOLD_AUX_DATA, QVariant(true)).toBool(); m_uiQmlOpenMode = settings->value(UIQML_OPEN_MODE, "").toString(); + m_qmllsSettings.useQmlls = settings->value(USE_QMLLS, QVariant(false)).toBool(); + m_qmllsSettings.useLatestQmlls = settings->value(USE_LATEST_QMLLS, QVariant(false)).toBool(); settings->endGroup(); } @@ -62,7 +57,10 @@ void QmlJsEditingSettings::toSettings(QSettings *settings) const settings->setValue(AUTO_FORMAT_ONLY_CURRENT_PROJECT, m_autoFormatOnlyCurrentProject); settings->setValue(FOLD_AUX_DATA, m_foldAuxData); settings->setValue(UIQML_OPEN_MODE, m_uiQmlOpenMode); + settings->setValue(USE_QMLLS, m_qmllsSettings.useQmlls); + settings->setValue(USE_LATEST_QMLLS, m_qmllsSettings.useLatestQmlls); settings->endGroup(); + QmllsSettingsManager::instance()->checkForChanges(); } bool QmlJsEditingSettings::equals(const QmlJsEditingSettings &other) const @@ -72,6 +70,7 @@ bool QmlJsEditingSettings::equals(const QmlJsEditingSettings &other) const && m_autoFormatOnSave == other.m_autoFormatOnSave && m_autoFormatOnlyCurrentProject == other.m_autoFormatOnlyCurrentProject && m_foldAuxData == other.m_foldAuxData + && m_qmllsSettings == other.m_qmllsSettings && m_uiQmlOpenMode == other.m_uiQmlOpenMode; } @@ -125,6 +124,16 @@ void QmlJsEditingSettings::setFoldAuxData(const bool foldAuxData) m_foldAuxData = foldAuxData; } +QmllsSettings &QmlJsEditingSettings::qmllsSettigs() +{ + return m_qmllsSettings; +} + +const QmllsSettings &QmlJsEditingSettings::qmllsSettigs() const +{ + return m_qmllsSettings; +} + const QString QmlJsEditingSettings::uiQmlOpenMode() const { return m_uiQmlOpenMode; @@ -162,6 +171,14 @@ public: uiQmlOpenComboBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); uiQmlOpenComboBox->setSizeAdjustPolicy(QComboBox::QComboBox::AdjustToContents); + useQmlls = new QCheckBox(tr("Use qmlls (EXPERIMENTAL!)")); + useQmlls->setChecked(s.qmllsSettigs().useQmlls); + useLatestQmlls = new QCheckBox(tr("Always use latest qmlls")); + useLatestQmlls->setChecked(s.qmllsSettigs().useLatestQmlls); + useLatestQmlls->setEnabled(s.qmllsSettigs().useQmlls); + QObject::connect(useQmlls, &QCheckBox::stateChanged, this, [this](int checked) { + useLatestQmlls->setEnabled(checked != Qt::Unchecked); + }); using namespace Utils::Layouting; Column { Group { @@ -179,8 +196,13 @@ public: Form { Tr::tr("Open .ui.qml files with:"), uiQmlOpenComboBox }, }, }, + Group{ + title(tr("Language Server")), + Column{useQmlls, useLatestQmlls}, + }, st, - }.attachTo(this); + } + .attachTo(this); connect(autoFormatOnSave, &QCheckBox::toggled, autoFormatOnlyCurrentProject, &QWidget::setEnabled); @@ -195,6 +217,8 @@ public: s.setAutoFormatOnlyCurrentProject(autoFormatOnlyCurrentProject->isChecked()); s.setFoldAuxData(foldAuxData->isChecked()); s.setUiQmlOpenMode(uiQmlOpenComboBox->currentData().toString()); + s.qmllsSettigs().useQmlls = useQmlls->isChecked(); + s.qmllsSettigs().useLatestQmlls = useLatestQmlls->isChecked(); s.set(); } @@ -204,6 +228,8 @@ private: QCheckBox *pinContextPane; QCheckBox *enableContextPane; QCheckBox *foldAuxData; + QCheckBox *useQmlls; + QCheckBox *useLatestQmlls; QComboBox *uiQmlOpenComboBox; }; @@ -222,4 +248,3 @@ QmlJsEditingSettingsPage::QmlJsEditingSettingsPage() setCategory(Constants::SETTINGS_CATEGORY_QML); setWidgetCreator([] { return new QmlJsEditingSettingsPageWidget; }); } - diff --git a/src/plugins/qmljseditor/qmljseditingsettingspage.h b/src/plugins/qmljseditor/qmljseditingsettingspage.h index 15e8246d974..bf2dc08d4a2 100644 --- a/src/plugins/qmljseditor/qmljseditingsettingspage.h +++ b/src/plugins/qmljseditor/qmljseditingsettingspage.h @@ -3,6 +3,7 @@ #pragma once +#include "qmllssettings.h" #include #include #include @@ -16,7 +17,7 @@ namespace QmlJSEditor { class QmlJsEditingSettings { public: - QmlJsEditingSettings(); + QmlJsEditingSettings() = default; static QmlJsEditingSettings get(); void set(); @@ -41,6 +42,9 @@ public: bool foldAuxData() const; void setFoldAuxData(const bool foldAuxData); + QmllsSettings &qmllsSettigs(); + const QmllsSettings &qmllsSettigs() const; + const QString uiQmlOpenMode() const; void setUiQmlOpenMode(const QString &mode); @@ -50,12 +54,13 @@ public: { return !s1.equals(s2); } private: - bool m_enableContextPane; - bool m_pinContextPane; - bool m_autoFormatOnSave; - bool m_autoFormatOnlyCurrentProject; - bool m_foldAuxData; - QString m_uiQmlOpenMode; + bool m_enableContextPane = false; + bool m_pinContextPane = false; + bool m_autoFormatOnSave = false; + bool m_autoFormatOnlyCurrentProject = false; + bool m_foldAuxData = true; + QmllsSettings m_qmllsSettings; + QString m_uiQmlOpenMode = {}; }; namespace Internal { diff --git a/src/plugins/qmljseditor/qmljseditor.qbs b/src/plugins/qmljseditor/qmljseditor.qbs index b5b8f3d9396..8ad5a343879 100644 --- a/src/plugins/qmljseditor/qmljseditor.qbs +++ b/src/plugins/qmljseditor/qmljseditor.qbs @@ -13,10 +13,15 @@ QtcPlugin { Depends { name: "TextEditor" } Depends { name: "ProjectExplorer" } Depends { name: "QmlJSTools" } + Depends { name: "LanguageClient" } files: [ "qmlexpressionundercursor.cpp", "qmlexpressionundercursor.h", + "qmllsclient.cpp", + "qmllsclient.h", + "qmllssettings.cpp", + "qmllssettings.h", "qmljsautocompleter.cpp", "qmljsautocompleter.h", "qmljscompletionassist.cpp", diff --git a/src/plugins/qmljseditor/qmljseditordocument.cpp b/src/plugins/qmljseditor/qmljseditordocument.cpp index 81f30c410a3..68b235d0c18 100644 --- a/src/plugins/qmljseditor/qmljseditordocument.cpp +++ b/src/plugins/qmljseditor/qmljseditordocument.cpp @@ -12,6 +12,8 @@ #include "qmljssemantichighlighter.h" #include "qmljssemanticinfoupdater.h" #include "qmljstextmark.h" +#include "qmllsclient.h" +#include "qmllssettings.h" #include "qmloutlinemodel.h" #include @@ -24,7 +26,10 @@ #include #include +#include + #include +#include #include const char QML_UI_FILE_WARNING[] = "QmlJSEditor.QmlUiFileWarning"; @@ -36,6 +41,8 @@ using namespace QmlJSTools; namespace { +Q_LOGGING_CATEGORY(qmllsLog, "qtc.qmlls.editor", QtWarningMsg); + enum { UPDATE_DOCUMENT_DEFAULT_INTERVAL = 100, UPDATE_OUTLINE_INTERVAL = 500 @@ -471,6 +478,10 @@ QmlJSEditorDocumentPrivate::QmlJSEditorDocumentPrivate(QmlJSEditorDocument *pare this, &QmlJSEditorDocumentPrivate::reparseDocument); connect(modelManager, &ModelManagerInterface::documentUpdated, this, &QmlJSEditorDocumentPrivate::onDocumentUpdated); + connect(QmllsSettingsManager::instance(), + &QmllsSettingsManager::settingsChanged, + this, + &Internal::QmlJSEditorDocumentPrivate::settingsChanged); // semantic info m_semanticInfoUpdater = new SemanticInfoUpdater(this); @@ -493,6 +504,7 @@ QmlJSEditorDocumentPrivate::QmlJSEditorDocumentPrivate(QmlJSEditorDocument *pare this, &QmlJSEditorDocumentPrivate::updateOutlineModel); modelManager->updateSourceFiles(Utils::FilePaths({parent->filePath()}), false); + settingsChanged(); } QmlJSEditorDocumentPrivate::~QmlJSEditorDocumentPrivate() @@ -530,7 +542,8 @@ void QmlJSEditorDocumentPrivate::onDocumentUpdated(Document::Ptr doc) // got a correctly parsed (or recovered) file. m_semanticInfoDocRevision = doc->editorRevision(); m_semanticInfoUpdater->update(doc, ModelManagerInterface::instance()->snapshot()); - } else if (doc->language().isFullySupportedLanguage()) { + } else if (doc->language().isFullySupportedLanguage() + && m_qmllsStatus.semanticWarningsSource == QmllsStatus::Source::EmbeddedCodeModel) { createTextMarks(doc->diagnosticMessages()); } emit q->updateCodeWarnings(doc); @@ -567,7 +580,8 @@ void QmlJSEditorDocumentPrivate::acceptNewSemanticInfo(const SemanticInfo &seman m_outlineModelNeedsUpdate = true; m_semanticHighlightingNecessary = true; - createTextMarks(m_semanticInfo); + if (m_qmllsStatus.semanticWarningsSource == QmllsStatus::Source::EmbeddedCodeModel) + createTextMarks(m_semanticInfo); emit q->semanticInfoUpdated(m_semanticInfo); // calls triggerPendingUpdates as necessary } @@ -594,6 +608,8 @@ static void cleanMarks(QVector *marks, TextEditor::TextD void QmlJSEditorDocumentPrivate::createTextMarks(const QList &diagnostics) { + if (m_qmllsStatus.semanticWarningsSource != QmllsStatus::Source::EmbeddedCodeModel) + return; for (const DiagnosticMessage &diagnostic : diagnostics) { const auto onMarkRemoved = [this](QmlJSTextMark *mark) { m_diagnosticMarks.removeAll(mark); @@ -637,6 +653,158 @@ void QmlJSEditorDocumentPrivate::cleanSemanticMarks() cleanMarks(&m_semanticMarks, q); } +void QmlJSEditorDocumentPrivate::setSemanticWarningSource(QmllsStatus::Source newSource) +{ + if (m_qmllsStatus.semanticWarningsSource == newSource) + return; + m_qmllsStatus.semanticWarningsSource = newSource; + QTC_ASSERT(q->thread() == QThread::currentThread(), return ); + switch (m_qmllsStatus.semanticWarningsSource) { + case QmllsStatus::Source::Qmlls: + m_semanticHighlighter->setEnableWarnings(false); + cleanDiagnosticMarks(); + cleanSemanticMarks(); + if (!q->isSemanticInfoOutdated()) { + // clean up underlines for warning messages + m_semanticHighlightingNecessary = false; + m_semanticHighlighter->rerun(m_semanticInfo); + } + break; + case QmllsStatus::Source::EmbeddedCodeModel: + m_semanticHighlighter->setEnableWarnings(true); + reparseDocument(); + break; + } +} + +void QmlJSEditorDocumentPrivate::setSemanticHighlightSource(QmllsStatus::Source newSource) +{ + if (m_qmllsStatus.semanticHighlightSource == newSource) + return; + m_qmllsStatus.semanticHighlightSource = newSource; + QTC_ASSERT(q->thread() == QThread::currentThread(), return ); + switch (m_qmllsStatus.semanticHighlightSource) { + case QmllsStatus::Source::Qmlls: + m_semanticHighlighter->setEnableHighlighting(false); + cleanSemanticMarks(); + break; + case QmllsStatus::Source::EmbeddedCodeModel: + m_semanticHighlighter->setEnableHighlighting(true); + if (!q->isSemanticInfoOutdated()) { + m_semanticHighlightingNecessary = false; + m_semanticHighlighter->rerun(m_semanticInfo); + } + break; + } +} + +void QmlJSEditorDocumentPrivate::setCompletionSource(QmllsStatus::Source newSource) +{ + if (m_qmllsStatus.completionSource == newSource) + return; + m_qmllsStatus.completionSource = newSource; + switch (m_qmllsStatus.completionSource) { + case QmllsStatus::Source::Qmlls: + // activation of the document already takes care of setting it + break; + case QmllsStatus::Source::EmbeddedCodeModel: + // deactivation of the document takes care of restoring it + break; + } +} + +void QmlJSEditorDocumentPrivate::setSourcesWithCapabilities( + const LanguageServerProtocol::ServerCapabilities &cap) +{ + if (cap.completionProvider()) + setCompletionSource(QmllsStatus::Source::Qmlls); + else + setCompletionSource(QmllsStatus::Source::EmbeddedCodeModel); + if (cap.codeActionProvider()) + setSemanticWarningSource(QmllsStatus::Source::Qmlls); + else + setSemanticWarningSource(QmllsStatus::Source::EmbeddedCodeModel); + if (cap.semanticTokensProvider()) + setSemanticHighlightSource(QmllsStatus::Source::Qmlls); + else + setSemanticHighlightSource(QmllsStatus::Source::EmbeddedCodeModel); +} + +static Utils::FilePath qmllsForFile(const Utils::FilePath &file, + QmlJS::ModelManagerInterface *modelManager) +{ + QmllsSettingsManager *settingsManager = QmllsSettingsManager::instance(); + QmllsSettings settings = settingsManager->lastSettings(); + bool enabled = settings.useQmlls; + if (!enabled) + return Utils::FilePath(); + if (settings.useLatestQmlls) + return settingsManager->latestQmlls(); + QmlJS::ModelManagerInterface::ProjectInfo pInfo = modelManager->projectInfoForPath(file); + return pInfo.qmllsPath; +} + +void QmlJSEditorDocumentPrivate::settingsChanged() +{ + Utils::FilePath newQmlls = qmllsForFile(q->filePath(), ModelManagerInterface::instance()); + if (m_qmllsStatus.qmllsPath == newQmlls) + return; + m_qmllsStatus.qmllsPath = newQmlls; + auto lspClientManager = LanguageClient::LanguageClientManager::instance(); + if (newQmlls.isEmpty()) { + qCDebug(qmllsLog) << "disabling qmlls for" << q->filePath(); + if (LanguageClient::Client *client = lspClientManager->clientForDocument(q)) { + qCDebug(qmllsLog) << "deactivating " << q->filePath() << "in qmlls" << newQmlls; + client->deactivateDocument(q); + } else + qCWarning(qmllsLog) << "Could not find client to disable for document " << q->filePath() + << " in LanguageClient::LanguageClientManager"; + setCompletionSource(QmllsStatus::Source::EmbeddedCodeModel); + setSemanticWarningSource(QmllsStatus::Source::EmbeddedCodeModel); + setSemanticHighlightSource(QmllsStatus::Source::EmbeddedCodeModel); + } else if (QmllsClient *client = QmllsClient::clientForQmlls(newQmlls)) { + bool shouldActivate = false; + if (auto oldClient = lspClientManager->clientForDocument(q)) { + // check if it was disabled + if (client == oldClient) + shouldActivate = true; + } + switch (client->state()) { + case LanguageClient::Client::State::Uninitialized: + case LanguageClient::Client::State::InitializeRequested: + connect(client, + &LanguageClient::Client::initialized, + this, + &QmlJSEditorDocumentPrivate::setSourcesWithCapabilities); + break; + case LanguageClient::Client::State::Initialized: + setSourcesWithCapabilities(client->capabilities()); + break; + case LanguageClient::Client::State::Error: + qCWarning(qmllsLog) << "qmlls" << newQmlls << "requested for document" << q->filePath() + << "had errors, skipping setSourcesWithCababilities"; + break; + case LanguageClient::Client::State::Shutdown: + qCWarning(qmllsLog) << "qmlls" << newQmlls << "requested for document" << q->filePath() + << "did stop, skipping setSourcesWithCababilities"; + break; + case LanguageClient::Client::State::ShutdownRequested: + qCWarning(qmllsLog) << "qmlls" << newQmlls << "requested for document" << q->filePath() + << "is stopping, skipping setSourcesWithCababilities"; + break; + } + if (shouldActivate) { + qCDebug(qmllsLog) << "reactivating " << q->filePath() << "in qmlls" << newQmlls; + client->activateDocument(q); + } else { + qCDebug(qmllsLog) << "opening " << q->filePath() << "in qmlls" << newQmlls; + lspClientManager->openDocumentWithClient(q, client); + } + } else { + qCWarning(qmllsLog) << "could not start qmlls " << newQmlls << "for" << q->filePath(); + } +} + } // Internal QmlJSEditorDocument::QmlJSEditorDocument(Utils::Id id) diff --git a/src/plugins/qmljseditor/qmljseditordocument_p.h b/src/plugins/qmljseditor/qmljseditordocument_p.h index 021a1bbf5cb..7be1ec6d345 100644 --- a/src/plugins/qmljseditor/qmljseditordocument_p.h +++ b/src/plugins/qmljseditor/qmljseditordocument_p.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -23,6 +24,17 @@ class QmlOutlineModel; class SemanticInfoUpdater; +class QmllsStatus +{ +public: + enum class Source { Qmlls, EmbeddedCodeModel }; + + Source semanticWarningsSource; + Source semanticHighlightSource; + Source completionSource; + Utils::FilePath qmllsPath; +}; + class QmlJSEditorDocumentPrivate : public QObject { Q_OBJECT @@ -43,6 +55,14 @@ public: void createTextMarks(const QmlJSTools::SemanticInfo &info); void cleanSemanticMarks(); + // qmlls change source + void setSemanticWarningSource(QmllsStatus::Source newSource); + void setSemanticHighlightSource(QmllsStatus::Source newSource); + void setCompletionSource(QmllsStatus::Source newSource); +public slots: + void setSourcesWithCapabilities(const LanguageServerProtocol::ServerCapabilities &); + void settingsChanged(); + public: QmlJSEditorDocument *q = nullptr; QTimer m_updateDocumentTimer; // used to compress multiple document changes @@ -59,6 +79,10 @@ public: QVector m_diagnosticMarks; QVector m_semanticMarks; bool m_isDesignModePreferred = false; + QmllsStatus m_qmllsStatus = {QmllsStatus::Source::EmbeddedCodeModel, + QmllsStatus::Source::EmbeddedCodeModel, + QmllsStatus::Source::EmbeddedCodeModel, + {}}; }; } // Internal diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index 14560665630..b8be01aee27 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -114,6 +114,7 @@ QmlJSEditorPluginPrivate::QmlJSEditorPluginPrivate() &QmlJSEditorFactory::decorateEditor); QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); + QmllsSettingsManager::instance(); // QML task updating manager connect(modelManager, &QmlJS::ModelManagerInterface::documentChangedOnDisk, @@ -207,6 +208,7 @@ void QmlJSEditorPlugin::extensionsInitialized() TaskHub::addCategory(Constants::TASK_CATEGORY_QML, Tr::tr("QML")); TaskHub::addCategory(Constants::TASK_CATEGORY_QML_ANALYSIS, Tr::tr("QML Analysis"), false); + QmllsSettingsManager::instance()->setupAutoupdate(); } ExtensionSystem::IPlugin::ShutdownFlag QmlJSEditorPlugin::aboutToShutdown() diff --git a/src/plugins/qmljseditor/qmljssemantichighlighter.cpp b/src/plugins/qmljseditor/qmljssemantichighlighter.cpp index a011b0deddf..531f59b491f 100644 --- a/src/plugins/qmljseditor/qmljssemantichighlighter.cpp +++ b/src/plugins/qmljseditor/qmljssemantichighlighter.cpp @@ -152,9 +152,14 @@ protected: class CollectionTask : protected Visitor { public: + enum Flags { + AddMessagesHighlights, + SkipMessagesHighlights, + }; CollectionTask(QFutureInterface &futureInterface, const QmlJSTools::SemanticInfo &semanticInfo, - const TextEditor::FontSettings &fontSettings) + const TextEditor::FontSettings &fontSettings, + Flags flags) : m_futureInterface(futureInterface) , m_semanticInfo(semanticInfo) , m_fontSettings(fontSettings) @@ -172,9 +177,11 @@ public: m_delayedUses.reserve(nMessages); m_diagnosticRanges.reserve(nMessages); m_extraFormats.reserve(nMessages); - addMessages(m_scopeChain.document()->diagnosticMessages(), m_scopeChain.document()); - addMessages(m_semanticInfo.semanticMessages, m_semanticInfo.document); - addMessages(m_semanticInfo.staticAnalysisMessages, m_semanticInfo.document); + if (flags == AddMessagesHighlights) { + addMessages(m_scopeChain.document()->diagnosticMessages(), m_scopeChain.document()); + addMessages(m_semanticInfo.semanticMessages, m_semanticInfo.document); + addMessages(m_semanticInfo.staticAnalysisMessages, m_semanticInfo.document); + } Utils::sort(m_delayedUses, sortByLinePredicate); } @@ -563,8 +570,9 @@ void SemanticHighlighter::applyResults(int from, int to) if (m_startRevision != m_document->document()->revision()) return; - TextEditor::SemanticHighlighter::incrementalApplyExtraAdditionalFormats( - m_document->syntaxHighlighter(), m_watcher.future(), from, to, m_extraFormats); + if (m_enableHighlighting) + TextEditor::SemanticHighlighter::incrementalApplyExtraAdditionalFormats( + m_document->syntaxHighlighter(), m_watcher.future(), from, to, m_extraFormats); } void SemanticHighlighter::finished() @@ -574,17 +582,23 @@ void SemanticHighlighter::finished() if (m_startRevision != m_document->document()->revision()) return; - m_document->setDiagnosticRanges(m_diagnosticRanges); + if (m_enableWarnings) + m_document->setDiagnosticRanges(m_diagnosticRanges); - TextEditor::SemanticHighlighter::clearExtraAdditionalFormatsUntilEnd( - m_document->syntaxHighlighter(), m_watcher.future()); + if (m_enableHighlighting) + TextEditor::SemanticHighlighter::clearExtraAdditionalFormatsUntilEnd( + m_document->syntaxHighlighter(), m_watcher.future()); } void SemanticHighlighter::run(QFutureInterface &futureInterface, const QmlJSTools::SemanticInfo &semanticInfo, const TextEditor::FontSettings &fontSettings) { - CollectionTask task(futureInterface, semanticInfo, fontSettings); + CollectionTask task(futureInterface, + semanticInfo, + fontSettings, + (m_enableWarnings ? CollectionTask::AddMessagesHighlights + : CollectionTask::SkipMessagesHighlights)); reportMessagesInfo(task.diagnosticRanges(), task.extraFormats()); task.run(); } @@ -622,4 +636,14 @@ int SemanticHighlighter::startRevision() const return m_startRevision; } +void SemanticHighlighter::setEnableWarnings(bool e) +{ + m_enableWarnings = e; +} + +void SemanticHighlighter::setEnableHighlighting(bool e) +{ + m_enableHighlighting = e; +} + } // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmljssemantichighlighter.h b/src/plugins/qmljseditor/qmljssemantichighlighter.h index f7bacc6d8b6..a08e3de0023 100644 --- a/src/plugins/qmljseditor/qmljssemantichighlighter.h +++ b/src/plugins/qmljseditor/qmljssemantichighlighter.h @@ -52,6 +52,8 @@ public: void cancel(); int startRevision() const; + void setEnableWarnings(bool e); + void setEnableHighlighting(bool e); void updateFontSettings(const TextEditor::FontSettings &fontSettings); void reportMessagesInfo(const QVector &diagnosticMessages, @@ -71,6 +73,8 @@ private: QHash m_extraFormats; QVector m_diagnosticRanges; Utils::FutureSynchronizer m_futureSynchronizer; + bool m_enableWarnings = true; + bool m_enableHighlighting = true; }; } // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmllsclient.cpp b/src/plugins/qmljseditor/qmllsclient.cpp new file mode 100644 index 00000000000..c73115cea41 --- /dev/null +++ b/src/plugins/qmljseditor/qmllsclient.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "qmllsclient.h" + +#include +#include + +#include +#include + +#include +#include + +#include + +using namespace LanguageClient; +using namespace Utils; + +namespace { +Q_LOGGING_CATEGORY(qmllsLog, "qtc.qmlls.client", QtWarningMsg); +} + +namespace QmlJSEditor { + +static QHash &qmllsClients() +{ + static QHash clients; + return clients; +} + +QmllsClient *QmllsClient::clientForQmlls(const FilePath &qmlls) +{ + if (auto client = qmllsClients()[qmlls]) { + switch (client->state()) { + case Client::State::Uninitialized: + case Client::State::InitializeRequested: + case Client::State::Initialized: + return client; + case Client::State::ShutdownRequested: + case Client::State::Shutdown: + case Client::State::Error: + qCDebug(qmllsLog) << "client was stopping or failed, restarting"; + break; + } + } + auto interface = new StdIOClientInterface; + interface->setCommandLine(CommandLine(qmlls)); + auto client = new QmllsClient(interface); + client->setName(QmllsClient::tr("Qmlls (%1)").arg(qmlls.toUserOutput())); + client->setActivateDocumentAutomatically(true); + LanguageFilter filter; + using namespace QmlJSTools::Constants; + filter.mimeTypes = QStringList() + << QML_MIMETYPE << QMLUI_MIMETYPE << QBS_MIMETYPE << QMLPROJECT_MIMETYPE + << QMLTYPES_MIMETYPE << JS_MIMETYPE << JSON_MIMETYPE; + client->setSupportedLanguage(filter); + client->start(); + qmllsClients()[qmlls] = client; + return client; +} + +QmllsClient::QmllsClient(StdIOClientInterface *interface) + : Client(interface) +{ +} + +QmllsClient::~QmllsClient() +{ + qmllsClients().remove(qmllsClients().key(this)); +} + +} // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmllsclient.h b/src/plugins/qmljseditor/qmllsclient.h new file mode 100644 index 00000000000..10b775066b6 --- /dev/null +++ b/src/plugins/qmljseditor/qmllsclient.h @@ -0,0 +1,25 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qmljseditor_global.h" + +#include + +#include +#include + +namespace QmlJSEditor { + +class QMLJSEDITOR_EXPORT QmllsClient : public LanguageClient::Client +{ + Q_OBJECT +public: + explicit QmllsClient(LanguageClient::StdIOClientInterface *interface); + ~QmllsClient(); + + static QmllsClient *clientForQmlls(const Utils::FilePath &qmlls); +}; + +} // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmllssettings.cpp b/src/plugins/qmljseditor/qmllssettings.cpp new file mode 100644 index 00000000000..7542dd1883f --- /dev/null +++ b/src/plugins/qmljseditor/qmllssettings.cpp @@ -0,0 +1,106 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "qmllssettings.h" +#include "qmljseditingsettingspage.h" + +#include +#include + +#include +#include + +#include + +using namespace QtSupport; +using namespace Utils; + +namespace QmlJSEditor { + +namespace { +Q_LOGGING_CATEGORY(qmllsLog, "qtc.qmlls.settings", QtWarningMsg); +} + +static FilePath evaluateLatestQmlls() +{ + // find latest qmlls, i.e. vals + if (!QtVersionManager::instance()->isLoaded()) + return {}; + const QtVersions versions = QtVersionManager::instance()->versions(); + FilePath latestQmlls; + QVersionNumber latestVersion; + FilePath latestQmakeFilePath; + int latestUniqueId = std::numeric_limits::min(); + for (QtVersion *v : versions) { + // check if we find qmlls + QVersionNumber vNow = v->qtVersion(); + FilePath qmllsNow = QmlJS::ModelManagerInterface::qmllsForBinPath(v->hostBinPath(), vNow); + if (!qmllsNow.isExecutableFile()) + continue; + if (latestVersion > vNow) + continue; + FilePath qmakeNow = v->qmakeFilePath(); + int uniqueIdNow = v->uniqueId(); + if (latestVersion == vNow) { + if (latestQmakeFilePath > qmakeNow) + continue; + if (latestQmakeFilePath == qmakeNow && latestUniqueId >= v->uniqueId()) + continue; + } + latestQmlls = qmllsNow; + latestQmakeFilePath = qmakeNow; + latestUniqueId = uniqueIdNow; + } + return latestQmlls; +} + +QmllsSettingsManager *QmllsSettingsManager::instance() +{ + static QmllsSettingsManager *manager = new QmllsSettingsManager; + return manager; +} + +QmllsSettings QmllsSettingsManager::lastSettings() +{ + QMutexLocker l(&m_mutex); + return m_lastSettings; +} + +FilePath QmllsSettingsManager::latestQmlls() +{ + QMutexLocker l(&m_mutex); + return m_latestQmlls; +} + +void QmllsSettingsManager::setupAutoupdate() +{ + QObject::connect(QtVersionManager::instance(), + &QtVersionManager::qtVersionsChanged, + this, + &QmllsSettingsManager::checkForChanges); + if (QtVersionManager::instance()->isLoaded()) + checkForChanges(); + else + QObject::connect(QtVersionManager::instance(), + &QtVersionManager::qtVersionsLoaded, + this, + &QmllsSettingsManager::checkForChanges); +} + +void QmllsSettingsManager::checkForChanges() +{ + FilePath newLatest = evaluateLatestQmlls(); + QmllsSettings newSettings = QmlJsEditingSettings::get().qmllsSettigs(); + if (m_lastSettings == newSettings && newLatest == m_latestQmlls) + return; + qCDebug(qmllsLog) << "qmlls settings changed:" << newSettings.useQmlls + << newSettings.useLatestQmlls << newLatest; + { + QMutexLocker l(&m_mutex); + m_latestQmlls = newLatest; + m_lastSettings = newSettings; + } + emit settingsChanged(); +} + +} // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmllssettings.h b/src/plugins/qmljseditor/qmllssettings.h new file mode 100644 index 00000000000..4f3084e5319 --- /dev/null +++ b/src/plugins/qmljseditor/qmllssettings.h @@ -0,0 +1,45 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qmljseditor_global.h" + +#include +#include +#include + +namespace QmlJSEditor { + +struct QmllsSettings +{ + bool useQmlls = true; + bool useLatestQmlls = false; + + friend bool operator==(const QmllsSettings &s1, const QmllsSettings &s2) + { + return s1.useQmlls == s2.useQmlls && s1.useLatestQmlls == s2.useLatestQmlls; + } + friend bool operator!=(const QmllsSettings &s1, const QmllsSettings &s2) { return !(s1 == s2); } +}; + +class QMLJSEDITOR_EXPORT QmllsSettingsManager : public QObject +{ + Q_OBJECT +public: + static QmllsSettingsManager *instance(); + QmllsSettings lastSettings(); + Utils::FilePath latestQmlls(); + void setupAutoupdate(); +public slots: + void checkForChanges(); +signals: + void settingsChanged(); + +private: + QMutex m_mutex; + QmllsSettings m_lastSettings; + Utils::FilePath m_latestQmlls; +}; + +} // namespace QmlJSEditor diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp index 19fc3aa4a67..0fd18bd44c2 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.cpp +++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp @@ -169,9 +169,13 @@ ModelManagerInterface::ProjectInfo ModelManager::defaultProjectInfoForProject( if (qtVersion && qtVersion->isValid()) { projectInfo.tryQmlDump = project && qtVersion->type() == QLatin1String(QtSupport::Constants::DESKTOPQT); projectInfo.qtQmlPath = qtVersion->qmlPath(); + auto v = qtVersion->qtVersion(); + projectInfo.qmllsPath = ModelManagerInterface::qmllsForBinPath(qtVersion->hostBinPath(), v); projectInfo.qtVersionString = qtVersion->qtVersionString(); } else if (!activeKit || !activeKit->value(QtSupport::SuppliesQtQuickImportPath::id(), false).toBool()) { projectInfo.qtQmlPath = FilePath::fromUserInput(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)); + projectInfo.qmllsPath = ModelManagerInterface::qmllsForBinPath( + FilePath::fromUserInput(QLibraryInfo::location(QLibraryInfo::BinariesPath)), QLibraryInfo::version()); projectInfo.qtVersionString = QLatin1String(qVersion()); } diff --git a/src/plugins/qmlprojectmanager/qmlproject.cpp b/src/plugins/qmlprojectmanager/qmlproject.cpp index 0786e2b5805..116859cd7b3 100644 --- a/src/plugins/qmlprojectmanager/qmlproject.cpp +++ b/src/plugins/qmlprojectmanager/qmlproject.cpp @@ -212,12 +212,12 @@ void QmlBuildSystem::parseProject(RefreshOptions options) if (auto modelManager = QmlJS::ModelManagerInterface::instance()) { QStringList files = m_projectItem->files(); - Utils::FilePaths filePaths(files.size()); - std::transform(files.cbegin(), - files.cend(), - filePaths.begin(), - [](const QString &p) { return Utils::FilePath::fromString(p); }); - modelManager->updateSourceFiles(filePaths, true); + modelManager + ->updateSourceFiles(Utils::transform(files, + [](const QString &p) { + return Utils::FilePath::fromString(p); + }), + true); } QString mainFilePath = m_projectItem->mainFile(); if (!mainFilePath.isEmpty()) { @@ -504,12 +504,10 @@ void QmlBuildSystem::refreshFiles(const QSet &/*added*/, const QSetremoveFiles(pathsRemoved); + modelManager->removeFiles( + Utils::transform>(removed, [](const QString &s) { + return Utils::FilePath::fromString(s); + })); } } refreshTargetDirectory(); @@ -797,4 +795,3 @@ bool QmlBuildSystem::renameFile(Node * context, const FilePath &oldFilePath, con } } // namespace QmlProjectManager -