From a737828d7d6b385ef09355e0c56b4ff9620b4f0a Mon Sep 17 00:00:00 2001 From: Sami Shalayel Date: Thu, 14 Nov 2024 16:06:40 +0100 Subject: [PATCH] qmlls: Make LanguageClientManager manage qmlls Remove the custom unfit qmlls-managing functionality from QmlJSEditorDocumentPrivate and use the LanguageClient implementation instead. Move the settings page from Qt Quick to the Language Server settings page to have all language servers in one central point. Introduce new class for QmllsClientSettings and QmllsClientSettingsWidget that allows the LanguageClient code to manage the qmlls state (starting, restarting, shutdown etc) and to configure qmlls from the Language Server settings page. Split qmlls specific functionality away from QmlJSEditingSettings into QmllsClientSettings and squash QmllsSettingsManager into QmllsClientSettings as its not needed anymore. Remove QmlJSEditorDocumentPrivate::settingsChanged() because thats almost completely handled by the LanguageClient generic implementation. The missing bits (enabling/disabling of the builtin code model) will be re-added in a future commit, tracked by QTCREATORBUG-32025. Currently the ProjectSettings::save() does not do anything, such that the project specific settings under Qt Quick does not do anything anymore (before the checkbox there allowed to disable/enable qmlls project-wise), tracked by QTCREATORBUG-32026. Task-number: QTCREATORBUG-31897 Change-Id: I62d807e478ab105e67ec7d94af6782810e6bd2ed Reviewed-by: David Schulz --- src/plugins/qmljseditor/CMakeLists.txt | 1 + src/plugins/qmljseditor/qmljseditor.cpp | 7 +- .../qmljseditor/qmljseditorconstants.h | 2 + .../qmljseditor/qmljseditordocument.cpp | 108 +----- .../qmljseditor/qmljseditordocument_p.h | 1 - src/plugins/qmljseditor/qmljseditorplugin.cpp | 4 +- .../qmljseditor/qmljseditorsettings.cpp | 165 +-------- src/plugins/qmljseditor/qmljseditorsettings.h | 36 -- src/plugins/qmljseditor/qmljshoverhandler.cpp | 5 +- src/plugins/qmljseditor/qmllsclient.cpp | 36 +- src/plugins/qmljseditor/qmllsclient.h | 1 - .../qmljseditor/qmllsclientsettings.cpp | 332 ++++++++++++++++++ src/plugins/qmljseditor/qmllsclientsettings.h | 46 +++ src/plugins/qmljseditor/qmltaskmanager.cpp | 4 +- 14 files changed, 409 insertions(+), 339 deletions(-) create mode 100644 src/plugins/qmljseditor/qmllsclientsettings.cpp create mode 100644 src/plugins/qmljseditor/qmllsclientsettings.h diff --git a/src/plugins/qmljseditor/CMakeLists.txt b/src/plugins/qmljseditor/CMakeLists.txt index fcba4765f3b..06e848b8b83 100644 --- a/src/plugins/qmljseditor/CMakeLists.txt +++ b/src/plugins/qmljseditor/CMakeLists.txt @@ -4,6 +4,7 @@ add_qtc_plugin(QmlJSEditor SOURCES qmlexpressionundercursor.cpp qmlexpressionundercursor.h qmllsclient.h qmllsclient.cpp + qmllsclientsettings.h qmllsclientsettings.cpp qmljsautocompleter.cpp qmljsautocompleter.h qmljscompletionassist.cpp qmljscompletionassist.h qmljscomponentfromobjectdef.cpp qmljscomponentfromobjectdef.h diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index a079fdf3b75..b09a9f57e06 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -5,15 +5,16 @@ #include "qmljsautocompleter.h" #include "qmljscompletionassist.h" -#include "qmljseditorsettings.h" #include "qmljseditorconstants.h" #include "qmljseditordocument.h" #include "qmljseditorplugin.h" +#include "qmljseditorsettings.h" #include "qmljseditortr.h" #include "qmljsfindreferences.h" #include "qmljshighlighter.h" #include "qmljshoverhandler.h" #include "qmljsquickfixassist.h" +#include "qmllsclientsettings.h" #include "qmloutlinemodel.h" #include "quicktoolbar.h" @@ -98,15 +99,13 @@ namespace QmlJSEditor { static LanguageClient::Client *getQmllsClient(const Utils::FilePath &fileName) { - // the value in disableBuiltinCodemodel is only valid when useQmlls is enabled - if (settings().useQmlls() && !settings().disableBuiltinCodemodel()) + if (qmllsSettings()->useQmllsWithBuiltinCodemodelOnProject(fileName)) return nullptr; auto client = LanguageClient::LanguageClientManager::clientForFilePath(fileName); return client; } - // // QmlJSEditorWidget // diff --git a/src/plugins/qmljseditor/qmljseditorconstants.h b/src/plugins/qmljseditor/qmljseditorconstants.h index 5ffbbc98c3b..75500d36f56 100644 --- a/src/plugins/qmljseditor/qmljseditorconstants.h +++ b/src/plugins/qmljseditor/qmljseditorconstants.h @@ -25,5 +25,7 @@ const char QML_SNIPPETS_GROUP_ID[] = "QML"; const char QMLLINT_BUILD_TARGET[] = "all_qmllint"; +const char QMLLS_CLIENT_SETTINGS_ID[] = "LanguageClient::QmllsClientSettingsID"; + } // namespace Constants } // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmljseditordocument.cpp b/src/plugins/qmljseditor/qmljseditordocument.cpp index 1bec169fe1e..b9460ad3c21 100644 --- a/src/plugins/qmljseditor/qmljseditordocument.cpp +++ b/src/plugins/qmljseditor/qmljseditordocument.cpp @@ -6,13 +6,12 @@ #include "qmljseditordocument_p.h" #include "qmljseditorplugin.h" #include "qmljseditortr.h" -#include "qmljseditorsettings.h" #include "qmljshighlighter.h" #include "qmljsquickfixassist.h" #include "qmljssemantichighlighter.h" #include "qmljssemanticinfoupdater.h" #include "qmljstextmark.h" -#include "qmllsclient.h" +#include "qmllsclientsettings.h" #include "qmloutlinemodel.h" #include @@ -43,8 +42,6 @@ using namespace Utils; namespace { -Q_LOGGING_CATEGORY(qmllsLog, "qtc.qmlls.editor", QtWarningMsg); - enum { UPDATE_DOCUMENT_DEFAULT_INTERVAL = 100, UPDATE_OUTLINE_INTERVAL = 500 @@ -480,13 +477,6 @@ QmlJSEditorDocumentPrivate::QmlJSEditorDocumentPrivate(QmlJSEditorDocument *pare this, &QmlJSEditorDocumentPrivate::reparseDocument); connect(modelManager, &ModelManagerInterface::documentUpdated, this, &QmlJSEditorDocumentPrivate::onDocumentUpdated); - connect(QmllsSettingsManager::instance(), &QmllsSettingsManager::settingsChanged, - this, &QmlJSEditorDocumentPrivate::settingsChanged); - connect( - modelManager, - &ModelManagerInterface::projectInfoUpdated, - this, - &QmlJSEditorDocumentPrivate::settingsChanged); // semantic info m_semanticInfoUpdater = new SemanticInfoUpdater(); @@ -734,104 +724,12 @@ void QmlJSEditorDocumentPrivate::setSourcesWithCapabilities( setSemanticWarningSource(QmllsStatus::Source::Qmlls); else setSemanticWarningSource(QmllsStatus::Source::EmbeddedCodeModel); - if (cap.semanticTokensProvider() && settings().enableQmllsSemanticHighlighting()) + if (cap.semanticTokensProvider() && qmllsSettings()->m_useQmllsSemanticHighlighting) setSemanticHighlightSource(QmllsStatus::Source::Qmlls); else setSemanticHighlightSource(QmllsStatus::Source::EmbeddedCodeModel); } -static FilePath qmllsForFile(const FilePath &file, QmlJS::ModelManagerInterface *modelManager) -{ - QmllsSettingsManager *settingsManager = QmllsSettingsManager::instance(); - ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(file); - const bool enabled = settingsManager->useQmlls(project); - if (!enabled) - return {}; - if (settingsManager->useLatestQmlls()) - return settingsManager->latestQmlls(); - QmlJS::ModelManagerInterface::ProjectInfo pInfo = modelManager->projectInfoForPath(file); - - if (!settings().ignoreMinimumQmllsVersion() - && QVersionNumber::fromString(pInfo.qtVersionString) - < QmlJsEditingSettings::mininumQmllsVersion) { - return {}; - } - return pInfo.qmllsPath.exists() ? pInfo.qmllsPath : Utils::FilePath(); -} - -void QmlJSEditorDocumentPrivate::settingsChanged() -{ - if (q->isTemporary()) - return; - - FilePath newQmlls = qmllsForFile(q->filePath(), ModelManagerInterface::instance()); - if (m_qmllsStatus.qmllsPath == newQmlls) { - if (QmllsClient *client = QmllsClient::clientForQmlls(newQmlls)) { - client->updateQmllsSemanticHighlightingCapability(); - setSourcesWithCapabilities(client->capabilities()); - client->activateDocument(q); - } - return; - } - - using namespace LanguageClient; - m_qmllsStatus.qmllsPath = newQmlls; - if (newQmlls.isEmpty()) { - qCDebug(qmllsLog) << "disabling qmlls for" << q->filePath(); - if (LanguageClientManager::clientForDocument(q) != nullptr) { - qCDebug(qmllsLog) << "deactivating " << q->filePath() << "in qmlls" << newQmlls; - LanguageClientManager::openDocumentWithClient(q, nullptr); - } 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 = LanguageClientManager::clientForDocument(q)) { - // check if it was disabled - if (client == oldClient) - shouldActivate = true; - } - client->updateQmllsSemanticHighlightingCapability(); - switch (client->state()) { - case Client::State::Uninitialized: - case Client::State::InitializeRequested: - connect(client, - &Client::initialized, - this, - &QmlJSEditorDocumentPrivate::setSourcesWithCapabilities); - break; - case Client::State::Initialized: - setSourcesWithCapabilities(client->capabilities()); - break; - case Client::State::FailedToInitialize: - case Client::State::Error: - qCWarning(qmllsLog) << "qmlls" << newQmlls << "requested for document" << q->filePath() - << "had errors, skipping setSourcesWithCababilities"; - break; - case Client::State::Shutdown: - qCWarning(qmllsLog) << "qmlls" << newQmlls << "requested for document" << q->filePath() - << "did stop, skipping setSourcesWithCababilities"; - break; - case 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; - LanguageClientManager::openDocumentWithClient(q, client); - } - } else { - qCWarning(qmllsLog) << "could not start qmlls " << newQmlls << "for" << q->filePath(); - } -} - } // Internal QmlJSEditorDocument::QmlJSEditorDocument(Utils::Id id) @@ -840,8 +738,6 @@ QmlJSEditorDocument::QmlJSEditorDocument(Utils::Id id) setId(id); connect(this, &TextEditor::TextDocument::tabSettingsChanged, d, &Internal::QmlJSEditorDocumentPrivate::invalidateFormatterCache); - connect(this, &TextEditor::TextDocument::openFinishedSuccessfully, - d, &Internal::QmlJSEditorDocumentPrivate::settingsChanged); resetSyntaxHighlighter([] { return new QmlJSHighlighter(); }); setCodec(QTextCodec::codecForName("UTF-8")); // qml files are defined to be utf-8 setIndenter(createQmlJsIndenter(document())); diff --git a/src/plugins/qmljseditor/qmljseditordocument_p.h b/src/plugins/qmljseditor/qmljseditordocument_p.h index 6c4d5bb78c2..fcddf1d5ac9 100644 --- a/src/plugins/qmljseditor/qmljseditordocument_p.h +++ b/src/plugins/qmljseditor/qmljseditordocument_p.h @@ -62,7 +62,6 @@ public: void setCompletionSource(QmllsStatus::Source newSource); public slots: void setSourcesWithCapabilities(const LanguageServerProtocol::ServerCapabilities &); - void settingsChanged(); public: QmlJSEditorDocument *q = nullptr; diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index 3839765008d..b95c19f0689 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -9,6 +9,7 @@ #include "qmljseditortr.h" #include "qmljsoutline.h" #include "qmljsquickfixassist.h" +#include "qmllsclientsettings.h" #include "qmltaskmanager.h" #include @@ -89,7 +90,7 @@ static QmlJSEditorPluginPrivate *dd = nullptr; QmlJSEditorPluginPrivate::QmlJSEditorPluginPrivate() { QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); - QmllsSettingsManager::instance(); + setupQmllsClientSettings(); // QML task updating manager connect(modelManager, &QmlJS::ModelManagerInterface::documentChangedOnDisk, @@ -346,7 +347,6 @@ class QmlJSEditorPlugin final : public ExtensionSystem::IPlugin Tr::tr("QML Analysis"), Tr::tr("Issues that the QML static analyzer found."), false}); - QmllsSettingsManager::instance()->setupAutoupdate(); } }; diff --git a/src/plugins/qmljseditor/qmljseditorsettings.cpp b/src/plugins/qmljseditor/qmljseditorsettings.cpp index f92a062ff98..7d34f296df5 100644 --- a/src/plugins/qmljseditor/qmljseditorsettings.cpp +++ b/src/plugins/qmljseditor/qmljseditorsettings.cpp @@ -4,10 +4,17 @@ #include "qmljseditorsettings.h" #include "qmljseditorconstants.h" #include "qmljseditortr.h" +#include "qmllsclient.h" +#include "qmllsclientsettings.h" #include #include +#include +#include +#include + +#include #include #include #include @@ -49,8 +56,6 @@ using namespace ProjectExplorer; namespace QmlJSEditor::Internal { -static Q_LOGGING_CATEGORY(qmllsLog, "qtc.qmlls.settings", QtWarningMsg) - const char SETTINGS_KEY_MAIN[] = "QmlJSEditor"; const char AUTO_FORMAT_ON_SAVE[] = "QmlJSEditor.AutoFormatOnSave"; const char AUTO_FORMAT_ONLY_CURRENT_PROJECT[] = "QmlJSEditor.AutoFormatOnlyCurrentProject"; @@ -59,11 +64,6 @@ const char QML_CONTEXTPANEPIN_KEY[] = "QmlJSEditor.ContextPanePinned"; const char FOLD_AUX_DATA[] = "QmlJSEditor.FoldAuxData"; const char USE_GLOBAL_SETTINGS[] = "QmlJSEditor.UseGlobalSettings"; const char USE_QMLLS[] = "QmlJSEditor.UseQmlls"; -const char USE_LATEST_QMLLS[] = "QmlJSEditor.UseLatestQmlls"; -const char IGNORE_MINIMUM_QMLLS_VERSION[] = "QmlJSEditor.IgnoreMinimumQmllsVersion"; -const char USE_QMLLS_SEMANTIC_HIGHLIGHTING[] = "QmlJSEditor.EnableQmllsSemanticHighlighting"; -const char DISABLE_BUILTIN_CODEMODEL[] = "QmlJSEditor.DisableBuiltinCodemodel"; -const char GENERATE_QMLLS_INI_FILES[] = "QmlJSEditor.GenerateQmllsIniFiles"; const char UIQML_OPEN_MODE[] = "QmlJSEditor.openUiQmlMode"; const char FORMAT_COMMAND[] = "QmlJSEditor.formatCommand"; const char FORMAT_COMMAND_OPTIONS[] = "QmlJSEditor.formatCommandOptions"; @@ -81,110 +81,7 @@ QmlJsEditingSettings &settings() return settings; } -static FilePath evaluateLatestQmlls() -{ - // find latest qmlls, i.e. vals - if (!QtVersionManager::isLoaded()) - return {}; - const QtVersions versions = QtVersionManager::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; - } - latestVersion = vNow; - latestQmlls = qmllsNow; - latestQmakeFilePath = qmakeNow; - latestUniqueId = uniqueIdNow; - } - return latestQmlls; -} - -QmllsSettingsManager *QmllsSettingsManager::instance() -{ - static QmllsSettingsManager *manager = new QmllsSettingsManager; - return manager; -} - -FilePath QmllsSettingsManager::latestQmlls() -{ - QMutexLocker l(&m_mutex); - return m_latestQmlls; -} - -void QmllsSettingsManager::setupAutoupdate() -{ - QObject::connect(QtVersionManager::instance(), - &QtVersionManager::qtVersionsChanged, - this, - &QmllsSettingsManager::checkForChanges); - if (QtVersionManager::isLoaded()) - checkForChanges(); - else - QObject::connect(QtVersionManager::instance(), - &QtVersionManager::qtVersionsLoaded, - this, - &QmllsSettingsManager::checkForChanges); -} - -void QmllsSettingsManager::checkForChanges() -{ - const QmlJsEditingSettings &newSettings = settings(); - FilePath newLatest = newSettings.useLatestQmlls() && newSettings.useQmlls() - ? evaluateLatestQmlls() : m_latestQmlls; - if (m_useQmlls == newSettings.useQmlls() - && m_useLatestQmlls == newSettings.useLatestQmlls() - && m_disableBuiltinCodemodel == newSettings.disableBuiltinCodemodel() - && m_generateQmllsIniFiles == newSettings.generateQmllsIniFiles() - && m_enableQmllsSemanticHighlighting == newSettings.enableQmllsSemanticHighlighting() - && newLatest == m_latestQmlls) - return; - qCDebug(qmllsLog) << "qmlls settings changed:" << newSettings.useQmlls() - << newSettings.useLatestQmlls() << newLatest; - { - QMutexLocker l(&m_mutex); - m_latestQmlls = newLatest; - m_useQmlls = newSettings.useQmlls(); - m_useLatestQmlls = newSettings.useLatestQmlls(); - m_disableBuiltinCodemodel = newSettings.disableBuiltinCodemodel(); - m_enableQmllsSemanticHighlighting = newSettings.enableQmllsSemanticHighlighting(); - m_generateQmllsIniFiles = newSettings.generateQmllsIniFiles(); - } - emit settingsChanged(); -} - -bool QmllsSettingsManager::useLatestQmlls() const -{ - return m_useLatestQmlls; -} - -bool QmllsSettingsManager::useQmlls(Project* onProject) const -{ - if (!onProject) - return m_useQmlls; - // check if disabled via project specific settings - ProjectSettings projectSettings{onProject}; - - const bool result = projectSettings.useGlobalSettings() ? m_useQmlls - : projectSettings.useQmlls(); - - return result; -} +using namespace LanguageClient; static QList defaultDisabledMessages() { @@ -220,10 +117,6 @@ QmlJsEditingSettings::QmlJsEditingSettings() { const Key group = QmlJSEditor::Constants::SETTINGS_CATEGORY_QML; - useQmlls.setSettingsKey(group, USE_QMLLS); - useQmlls.setDefaultValue(true); - useQmlls.setLabelText(Tr::tr("Turn on")); - enableContextPane.setSettingsKey(group, QML_CONTEXTPANE_KEY); enableContextPane.setLabelText(Tr::tr("Always show Qt Quick Toolbar")); @@ -249,27 +142,6 @@ QmlJsEditingSettings::QmlJsEditingSettings() uiQmlOpenMode.addOption({Tr::tr("Qt Design Studio"), {}, Core::Constants::MODE_DESIGN}); uiQmlOpenMode.addOption({Tr::tr("Qt Creator"), {}, Core::Constants::MODE_EDIT}); - useLatestQmlls.setSettingsKey(group, USE_LATEST_QMLLS); - useLatestQmlls.setLabelText(Tr::tr("Use from latest Qt version")); - - disableBuiltinCodemodel.setSettingsKey(group, DISABLE_BUILTIN_CODEMODEL); - disableBuiltinCodemodel.setLabelText( - Tr::tr("Use advanced features (renaming, find usages, and so on) " - "(experimental)")); - - generateQmllsIniFiles.setSettingsKey(group, GENERATE_QMLLS_INI_FILES); - generateQmllsIniFiles.setLabelText( - Tr::tr("Create .qmlls.ini files for new projects")); - - ignoreMinimumQmllsVersion.setSettingsKey(group, IGNORE_MINIMUM_QMLLS_VERSION); - ignoreMinimumQmllsVersion.setLabelText( - Tr::tr("Allow versions below Qt %1") - .arg(QmlJsEditingSettings::mininumQmllsVersion.toString())); - - enableQmllsSemanticHighlighting.setSettingsKey(group, USE_QMLLS_SEMANTIC_HIGHLIGHTING); - enableQmllsSemanticHighlighting.setLabelText( - Tr::tr("Enable semantic highlighting (experimental)")); - useCustomFormatCommand.setSettingsKey(group, CUSTOM_COMMAND); useCustomFormatCommand.setLabelText( Tr::tr("Use custom command instead of built-in formatter")); @@ -299,11 +171,6 @@ QmlJsEditingSettings::QmlJsEditingSettings() readSettings(); autoFormatOnlyCurrentProject.setEnabler(&autoFormatOnSave); - useLatestQmlls.setEnabler(&useQmlls); - disableBuiltinCodemodel.setEnabler(&useQmlls); - generateQmllsIniFiles.setEnabler(&useQmlls); - ignoreMinimumQmllsVersion.setEnabler(&useQmlls); - enableQmllsSemanticHighlighting.setEnabler(&useQmlls); formatCommand.setEnabler(&useCustomFormatCommand); formatCommandOptions.setEnabler(&useCustomFormatCommand); } @@ -426,14 +293,7 @@ public: }, Group{ title(Tr::tr("QML Language Server")), - Column { - s.useQmlls, - s.ignoreMinimumQmllsVersion, - s.disableBuiltinCodemodel, - s.enableQmllsSemanticHighlighting, - s.useLatestQmlls, - s.generateQmllsIniFiles - }, + // TODO: link to new settings }, Group { title(Tr::tr("Static Analyzer")), @@ -469,7 +329,6 @@ public: s.disabledMessages.setValue(disabled); s.disabledMessagesForNonQuickUi.setValue(disabledForNonQuickUi); s.writeSettings(); - QmllsSettingsManager::instance()->checkForChanges(); } private: @@ -544,7 +403,11 @@ void ProjectSettings::save(Project *project) toMap(map); project->setNamedSettings(SETTINGS_KEY_MAIN, variantFromStore(map)); - emit QmllsSettingsManager::instance()->settingsChanged(); + // TODO: this does not do anything for now. Either force re-apply when the functionality + // is available in LanguageClient (tracked in QTCREATORBUG-32015) or remove ProjectSettings + // class completely in favor of the LanguageClient project specific settings implementation + // (tracked in QTCREATORBUG-31987). + LanguageClientManager::applySettings(); } class QmlJsEditingProjectSettingsWidget final : public ProjectSettingsWidget diff --git a/src/plugins/qmljseditor/qmljseditorsettings.h b/src/plugins/qmljseditor/qmljseditorsettings.h index 4aa1608d7c3..f2bf64d1a9d 100644 --- a/src/plugins/qmljseditor/qmljseditorsettings.h +++ b/src/plugins/qmljseditor/qmljseditorsettings.h @@ -18,8 +18,6 @@ namespace QmlJSEditor::Internal { class QmlJsEditingSettings final : public Utils::AspectContainer { public: - static const inline QVersionNumber mininumQmllsVersion = QVersionNumber(6, 8); - QmlJsEditingSettings(); QString defaultFormatCommand() const; @@ -31,12 +29,6 @@ public: Utils::BoolAspect foldAuxData{this}; Utils::BoolAspect useCustomFormatCommand{this}; Utils::BoolAspect useCustomAnalyzer{this}; - Utils::BoolAspect useQmlls{this}; - Utils::BoolAspect useLatestQmlls{this}; - Utils::BoolAspect ignoreMinimumQmllsVersion{this}; - Utils::BoolAspect enableQmllsSemanticHighlighting{this}; - Utils::BoolAspect disableBuiltinCodemodel{this}; - Utils::BoolAspect generateQmllsIniFiles{this}; Utils::SelectionAspect uiQmlOpenMode{this}; Utils::StringAspect formatCommand{this}; Utils::StringAspect formatCommandOptions{this}; @@ -44,34 +36,6 @@ public: Utils::IntegersAspect disabledMessagesForNonQuickUi{this}; }; -class QmllsSettingsManager : public QObject -{ - Q_OBJECT - -public: - static QmllsSettingsManager *instance(); - - Utils::FilePath latestQmlls(); - void setupAutoupdate(); - - bool useQmlls(ProjectExplorer::Project* project) const; - bool useLatestQmlls() const; - -public slots: - void checkForChanges(); -signals: - void settingsChanged(); - -private: - QMutex m_mutex; - bool m_useQmlls = true; - bool m_useLatestQmlls = false; - bool m_disableBuiltinCodemodel = false; - bool m_generateQmllsIniFiles = false; - bool m_enableQmllsSemanticHighlighting = false; - Utils::FilePath m_latestQmlls; -}; - QmlJsEditingSettings &settings(); class QmlJsEditingSettingsPage : public Core::IOptionsPage diff --git a/src/plugins/qmljseditor/qmljshoverhandler.cpp b/src/plugins/qmljseditor/qmljshoverhandler.cpp index f17ec289254..cf61970974e 100644 --- a/src/plugins/qmljseditor/qmljshoverhandler.cpp +++ b/src/plugins/qmljseditor/qmljshoverhandler.cpp @@ -5,8 +5,8 @@ #include "qmljseditor.h" #include "qmljseditordocument.h" -#include "qmljseditorsettings.h" #include "qmljseditortr.h" +#include "qmllsclientsettings.h" #include #include @@ -382,7 +382,8 @@ void QmlJSHoverHandler::reset() void QmlJSHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point) { // disable hoverhandling in case qmlls is enabled - if (settings().useQmlls()) { + if (editorWidget->textDocument() + && qmllsSettings()->isEnabledOnProjectFile(editorWidget->textDocument()->filePath())) { BaseHoverHandler::operateTooltip(editorWidget, point); return; } diff --git a/src/plugins/qmljseditor/qmllsclient.cpp b/src/plugins/qmljseditor/qmllsclient.cpp index 1fdc30a124a..5976ce7880f 100644 --- a/src/plugins/qmljseditor/qmllsclient.cpp +++ b/src/plugins/qmljseditor/qmllsclient.cpp @@ -4,9 +4,8 @@ #include "qmllsclient.h" #include "qmljseditorconstants.h" -#include "qmljseditortr.h" -#include "qmljseditorsettings.h" #include "qmljsquickfix.h" +#include "qmllsclientsettings.h" #include #include @@ -43,37 +42,6 @@ static QHash &qmllsClients() 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::FailedToInitialize: - 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(Tr::tr("Qmlls (%1)").arg(qmlls.toUserOutput())); - client->setActivateDocumentAutomatically(true); - LanguageFilter filter; - using namespace Utils::Constants; - filter.mimeTypes = {QML_MIMETYPE, QMLUI_MIMETYPE, QBS_MIMETYPE, QMLPROJECT_MIMETYPE, - QMLTYPES_MIMETYPE, JS_MIMETYPE, JSON_MIMETYPE}; - client->setSupportedLanguage(filter); - client->start(); - qmllsClients()[qmlls] = client; - return client; -} - QMap QmllsClient::semanticTokenTypesMap() { QMap result; @@ -90,7 +58,7 @@ QMap QmllsClient::semanticTokenTypesMap() void QmllsClient::updateQmllsSemanticHighlightingCapability() { const QString methodName = QStringLiteral("textDocument/semanticTokens"); - if (!QmlJSEditor::Internal::settings().enableQmllsSemanticHighlighting()) { + if (!qmllsSettings()->m_useQmllsSemanticHighlighting) { LanguageServerProtocol::Unregistration unregister; unregister.setMethod(methodName); unregister.setId({}); diff --git a/src/plugins/qmljseditor/qmllsclient.h b/src/plugins/qmljseditor/qmllsclient.h index a174d0f0e2d..8ea9dfbeec7 100644 --- a/src/plugins/qmljseditor/qmllsclient.h +++ b/src/plugins/qmljseditor/qmllsclient.h @@ -54,7 +54,6 @@ public: ~QmllsClient(); void startImpl() override; - static QmllsClient *clientForQmlls(const Utils::FilePath &qmlls); void updateQmllsSemanticHighlightingCapability(); private: static QMap semanticTokenTypesMap(); diff --git a/src/plugins/qmljseditor/qmllsclientsettings.cpp b/src/plugins/qmljseditor/qmllsclientsettings.cpp new file mode 100644 index 00000000000..663ed0ed91c --- /dev/null +++ b/src/plugins/qmljseditor/qmllsclientsettings.cpp @@ -0,0 +1,332 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmllsclientsettings.h" +#include "qmljseditorconstants.h" +#include "qmljseditortr.h" +#include "qmllsclient.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace LanguageClient; +using namespace QtSupport; +using namespace Utils; +using namespace ProjectExplorer; + +namespace QmlJSEditor { + +constexpr char useLatestQmllsKey[] = "useLatestQmlls"; +constexpr char disableBuiltinCodemodelKey[] = "disableBuiltinCodemodel"; +constexpr char generateQmllsIniFilesKey[] = "generateQmllsIniFiles"; +constexpr char ignoreMinimumQmllsVersionKey[] = "ignoreMinimumQmllsVersion"; +constexpr char useQmllsSemanticHighlightingKey[] = "enableQmllsSemanticHighlighting"; + +QmllsClientSettings *qmllsSettings() +{ + BaseSettings *qmllsSettings + = Utils::findOrDefault(LanguageClientManager::currentSettings(), [](BaseSettings *setting) { + return setting->m_settingsTypeId == Constants::QMLLS_CLIENT_SETTINGS_ID; + }); + return static_cast(qmllsSettings); +} + +QmllsClientSettings::QmllsClientSettings() +{ + m_name = "QML Language Server"; + + using namespace Utils::Constants; + m_languageFilter.mimeTypes + = {QML_MIMETYPE, + QMLUI_MIMETYPE, + QBS_MIMETYPE, + QMLPROJECT_MIMETYPE, + QMLTYPES_MIMETYPE, + JS_MIMETYPE, + JSON_MIMETYPE}; + + m_settingsTypeId = Constants::QMLLS_CLIENT_SETTINGS_ID; + m_startBehavior = RequiresProject; +} + +static QtVersion *qtVersionFromProject(const Project *project) +{ + if (!project) + return {}; + auto *target = project->activeTarget(); + if (!target) + return {}; + auto *kit = target->kit(); + if (!kit) + return {}; + auto qtVersion = QtKitAspect::qtVersion(kit); + return qtVersion; +} + +static std::pair evaluateLatestQmlls() +{ + // find latest qmlls, i.e. vals + if (!QtVersionManager::isLoaded()) + return {}; + const QtVersions versions = QtVersionManager::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; + } + latestVersion = vNow; + latestQmlls = qmllsNow; + latestQmakeFilePath = qmakeNow; + latestUniqueId = uniqueIdNow; + } + return std::make_pair(latestQmlls, latestVersion); +} + +static CommandLine commandLineForQmlls(const Project *project) +{ + const auto *qtVersion = qtVersionFromProject(project); + if (!qtVersion) + return {}; + + auto [executable, version] + = qmllsSettings()->m_useLatestQmlls + ? evaluateLatestQmlls() + : std::make_pair(qtVersion->binPath() / "qmlls", qtVersion->qtVersion()); + + CommandLine result{executable, {}}; + + if (auto *configuration = project->activeTarget()->activeBuildConfiguration()) + result.addArgs({"-b", configuration->buildDirectory().path()}); + + // qmlls 6.8 and later require the import path + if (version >= QVersionNumber(6, 8, 0)) + result.addArgs({"-I", qtVersion->qmlPath().path()}); + + // qmlls 6.8.1 and later require the documentation path + if (version >= QVersionNumber(6, 8, 1)) + result.addArgs({"-d", qtVersion->docsPath().path()}); + + return result; +} + +BaseClientInterface *QmllsClientSettings::createInterface(Project *project) const +{ + auto interface = new StdIOClientInterface; + interface->setCommandLine(commandLineForQmlls(project)); + return interface; +} + +Client *QmllsClientSettings::createClient(BaseClientInterface *interface) const +{ + return new QmllsClient(static_cast(interface)); +} + +class QmllsClientSettingsWidget : public QWidget +{ + Q_OBJECT +public: + explicit QmllsClientSettingsWidget( + const QmllsClientSettings *settings, QWidget *parent = nullptr); + + bool useLatestQmlls() const; + bool disableBuiltinCodemodel() const; + bool generateQmllsIniFiles() const; + bool ignoreMinimumQmllsVersion() const; + bool useQmllsSemanticHighlighting() const; + +private: + QCheckBox *m_useLatestQmlls; + QCheckBox *m_disableBuiltinCodemodel; + QCheckBox *m_generateQmllsIniFiles; + QCheckBox *m_ignoreMinimumQmllsVersion; + QCheckBox *m_useQmllsSemanticHighlighting; +}; + +QWidget *QmllsClientSettings::createSettingsWidget(QWidget *parent) const +{ + return new QmllsClientSettingsWidget(this, parent); +} + +bool QmllsClientSettings::applyFromSettingsWidget(QWidget *widget) +{ + bool changed = BaseSettings::applyFromSettingsWidget(widget); + + QmllsClientSettingsWidget *qmllsWidget = qobject_cast(widget); + if (!qmllsWidget) + return changed; + + if (m_useLatestQmlls != qmllsWidget->useLatestQmlls()) { + m_useLatestQmlls = qmllsWidget->useLatestQmlls(); + changed = true; + } + + if (m_disableBuiltinCodemodel != qmllsWidget->disableBuiltinCodemodel()) { + m_disableBuiltinCodemodel = qmllsWidget->disableBuiltinCodemodel(); + changed = true; + } + + if (m_generateQmllsIniFiles != qmllsWidget->generateQmllsIniFiles()) { + m_generateQmllsIniFiles = qmllsWidget->generateQmllsIniFiles(); + changed = true; + } + + if (m_ignoreMinimumQmllsVersion != qmllsWidget->ignoreMinimumQmllsVersion()) { + m_ignoreMinimumQmllsVersion = qmllsWidget->ignoreMinimumQmllsVersion(); + changed = true; + } + + if (m_useQmllsSemanticHighlighting != qmllsWidget->useQmllsSemanticHighlighting()) { + m_useQmllsSemanticHighlighting = qmllsWidget->useQmllsSemanticHighlighting(); + changed = true; + } + + return changed; +} + +void QmllsClientSettings::toMap(Store &map) const +{ + BaseSettings::toMap(map); + + map.insert(useLatestQmllsKey, m_useLatestQmlls); + map.insert(disableBuiltinCodemodelKey, m_disableBuiltinCodemodel); + map.insert(generateQmllsIniFilesKey, m_generateQmllsIniFiles); + map.insert(ignoreMinimumQmllsVersionKey, m_ignoreMinimumQmllsVersion); + map.insert(useQmllsSemanticHighlightingKey, m_useQmllsSemanticHighlighting); +} + +void QmllsClientSettings::fromMap(const Store &map) +{ + BaseSettings::fromMap(map); + + m_useLatestQmlls = map[useLatestQmllsKey].toBool(); + m_disableBuiltinCodemodel = map[disableBuiltinCodemodelKey].toBool(); + m_generateQmllsIniFiles = map[generateQmllsIniFilesKey].toBool(); + m_ignoreMinimumQmllsVersion = map[ignoreMinimumQmllsVersionKey].toBool(); + m_useQmllsSemanticHighlighting = map[useQmllsSemanticHighlightingKey].toBool(); + return; +} + +bool QmllsClientSettings::isEnabledOnProjectFile(const Utils::FilePath &file) const +{ + Project *project = ProjectManager::projectForFile(file); + return isEnabledOnProject(project); +} + +bool QmllsClientSettings::useQmllsWithBuiltinCodemodelOnProject(const Utils::FilePath &file) const +{ + if (m_disableBuiltinCodemodel) + return false; + + // disableBuitinCodemodel only makes sense when qmlls is enabled + Project *project = ProjectManager::projectForFile(file); + return isEnabledOnProject(project); +} + +void setupQmllsClientSettings() +{ + using namespace LanguageClient; + QmllsClientSettings *clientSettings = new QmllsClientSettings(); + + const ClientType type{ + Constants::QMLLS_CLIENT_SETTINGS_ID, + clientSettings->m_name, + []() { return new QmllsClientSettings; }, + false}; + + const QList savedSettings = LanguageClientSettings::storesBySettingsType(type.id); + + if (!savedSettings.isEmpty()) + clientSettings->fromMap(savedSettings.first()); + + LanguageClientManager::registerClientSettings(clientSettings); + LanguageClientSettings::registerClientType(type); +} + +QmllsClientSettingsWidget::QmllsClientSettingsWidget( + const QmllsClientSettings *settings, QWidget *parent) + : QWidget(parent) + , m_useLatestQmlls(new QCheckBox(Tr::tr("Use from latest Qt version"), this)) + , m_disableBuiltinCodemodel(new QCheckBox( + Tr::tr("Use advanced features (renaming, find usages, and so on) (experimental)"), this)) + , m_generateQmllsIniFiles( + new QCheckBox(Tr::tr("Create .qmlls.ini files for new projects"), this)) + , m_ignoreMinimumQmllsVersion(new QCheckBox( + Tr::tr("Allow versions below Qt %1") + .arg(QmllsClientSettings::mininumQmllsVersion.toString()), + this)) + , m_useQmllsSemanticHighlighting( + new QCheckBox(Tr::tr("Enable semantic highlighting (experimental)"), this)) +{ + m_useLatestQmlls->setChecked(settings->m_useLatestQmlls); + m_disableBuiltinCodemodel->setChecked(settings->m_disableBuiltinCodemodel); + m_generateQmllsIniFiles->setChecked(settings->m_generateQmllsIniFiles); + m_ignoreMinimumQmllsVersion->setChecked(settings->m_ignoreMinimumQmllsVersion); + m_useQmllsSemanticHighlighting->setChecked(settings->m_useQmllsSemanticHighlighting); + + using namespace Layouting; + // clang-format off + auto form = Form { + m_ignoreMinimumQmllsVersion, br, + m_disableBuiltinCodemodel, br, + m_useQmllsSemanticHighlighting, br, + m_useLatestQmlls, br, + m_generateQmllsIniFiles, br, + }; + // clang-format on + + form.attachTo(this); +} +bool QmllsClientSettingsWidget::useLatestQmlls() const +{ + return m_useLatestQmlls->isChecked(); +} +bool QmllsClientSettingsWidget::disableBuiltinCodemodel() const +{ + return m_disableBuiltinCodemodel->isChecked(); +} +bool QmllsClientSettingsWidget::generateQmllsIniFiles() const +{ + return m_generateQmllsIniFiles->isChecked(); +} +bool QmllsClientSettingsWidget::ignoreMinimumQmllsVersion() const +{ + return m_ignoreMinimumQmllsVersion->isChecked(); +} +bool QmllsClientSettingsWidget::useQmllsSemanticHighlighting() const +{ + return m_useQmllsSemanticHighlighting->isChecked(); +} + +} // namespace QmlJSEditor + +#include "qmllsclientsettings.moc" diff --git a/src/plugins/qmljseditor/qmllsclientsettings.h b/src/plugins/qmljseditor/qmllsclientsettings.h new file mode 100644 index 00000000000..d27f0ad350b --- /dev/null +++ b/src/plugins/qmljseditor/qmllsclientsettings.h @@ -0,0 +1,46 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +#include + +namespace QmlJSEditor { + +class QmllsClientSettings : public LanguageClient::BaseSettings +{ +public: + static const inline QVersionNumber mininumQmllsVersion = QVersionNumber(6, 8); + + QmllsClientSettings(); + BaseSettings *copy() const override { return new QmllsClientSettings(*this); } + + QWidget *createSettingsWidget(QWidget *parent = nullptr) const override; + bool applyFromSettingsWidget(QWidget *widget) override; + + void toMap(Utils::Store &map) const override; + void fromMap(const Utils::Store &map) override; + + // helpers: + bool isEnabledOnProjectFile(const Utils::FilePath &file) const; + bool useQmllsWithBuiltinCodemodelOnProject(const Utils::FilePath &file) const; + + bool m_useLatestQmlls = false; + bool m_ignoreMinimumQmllsVersion = false; + bool m_useQmllsSemanticHighlighting = false; + bool m_disableBuiltinCodemodel = false; + bool m_generateQmllsIniFiles = false; + +protected: + LanguageClient::BaseClientInterface *createInterface(ProjectExplorer::Project *) const override; + LanguageClient::Client *createClient( + LanguageClient::BaseClientInterface *interface) const override; +}; + +QmllsClientSettings *qmllsSettings(); +void setupQmllsClientSettings(); + +} // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmltaskmanager.cpp b/src/plugins/qmljseditor/qmltaskmanager.cpp index 43c908a6445..9f7423b794c 100644 --- a/src/plugins/qmljseditor/qmltaskmanager.cpp +++ b/src/plugins/qmljseditor/qmltaskmanager.cpp @@ -3,7 +3,7 @@ #include "qmltaskmanager.h" #include "qmljseditorconstants.h" -#include "qmljseditorsettings.h" +#include "qmllsclientsettings.h" #include #include @@ -122,7 +122,7 @@ void QmlTaskManager::updateSemanticMessagesNow() const bool isCMake = buildSystem->name() == "cmake"; // heuristic: qmllint will output meaningful warnings if qmlls is enabled - if (isCMake && QmllsSettingsManager::instance()->useQmlls(buildSystem->project())) { + if (isCMake && qmllsSettings()->isEnabledOnProject(buildSystem->project())) { // abort any update that's going on already, and remove old codemodel warnings m_messageCollector.cancel(); removeAllTasks(true);