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 -