// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmljseditorsettings.h" #include "qmljseditorconstants.h" #include "qmljseditortr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace QmlJSEditor::Internal; using namespace QtSupport; using namespace Utils; namespace QmlJSEditor::Internal { static Q_LOGGING_CATEGORY(qmllsLog, "qtc.qmlls.settings", QtWarningMsg) const char AUTO_FORMAT_ON_SAVE[] = "QmlJSEditor.AutoFormatOnSave"; const char AUTO_FORMAT_ONLY_CURRENT_PROJECT[] = "QmlJSEditor.AutoFormatOnlyCurrentProject"; 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 IGNORE_MINIMUM_QMLLS_VERSION[] = "QmlJSEditor.IgnoreMinimumQmllsVersion"; 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"; const char CUSTOM_COMMAND[] = "QmlJSEditor.useCustomFormatCommand"; const char CUSTOM_ANALYZER[] = "QmlJSEditor.useCustomAnalyzer"; const char DISABLED_MESSAGES[] = "QmlJSEditor.disabledMessages"; const char DISABLED_MESSAGES_NONQUICKUI[] = "QmlJSEditor.disabledMessagesNonQuickUI"; const char DEFAULT_CUSTOM_FORMAT_COMMAND[] = "%{CurrentDocument:Project:QT_HOST_BINS}/qmlformat%{HostOs:ExecutableSuffix}"; QmlJsEditingSettings &settings() { static 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() && 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_generateQmllsIniFiles = newSettings.generateQmllsIniFiles(); } emit settingsChanged(); } bool QmllsSettingsManager::useLatestQmlls() const { return m_useLatestQmlls; } bool QmllsSettingsManager::useQmlls() const { return m_useQmlls; } static QList defaultDisabledMessages() { static const QList disabledByDefault = Utils::transform( QmlJS::Check::defaultDisabledMessages(), [](QmlJS::StaticAnalysis::Type t) { return int(t); }); return disabledByDefault; } static QList defaultDisabledMessagesNonQuickUi() { static const QList disabledForNonQuickUi = Utils::transform( QmlJS::Check::defaultDisabledMessagesForNonQuickUi(), [](QmlJS::StaticAnalysis::Type t){ return int(t); }); return disabledForNonQuickUi; } static QVariant toSettingsTransformation(const QVariant &v) { QList list = v.value>(); QStringList result = Utils::transform(list, [](int i) { return QString::number(i); }); return QVariant::fromValue(result); } static QVariant fromSettingsTransformation(const QVariant &v) { QStringList list = v.value(); QList result = Utils::transform>(list, [](const QString &s) { return s.toInt(); }); return QVariant::fromValue(result); } 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")); pinContextPane.setSettingsKey(group, QML_CONTEXTPANEPIN_KEY); pinContextPane.setLabelText(Tr::tr("Pin Qt Quick Toolbar")); autoFormatOnSave.setSettingsKey(group, AUTO_FORMAT_ON_SAVE); autoFormatOnSave.setLabelText(Tr::tr("Enable auto format on file save")); autoFormatOnlyCurrentProject.setSettingsKey(group, AUTO_FORMAT_ONLY_CURRENT_PROJECT); autoFormatOnlyCurrentProject.setLabelText( Tr::tr("Restrict to files contained in the current project")); foldAuxData.setSettingsKey(group, FOLD_AUX_DATA); foldAuxData.setDefaultValue(true); foldAuxData.setLabelText(Tr::tr("Auto-fold auxiliary data")); uiQmlOpenMode.setSettingsKey(group, UIQML_OPEN_MODE); uiQmlOpenMode.setUseDataAsSavedValue(); uiQmlOpenMode.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); uiQmlOpenMode.setLabelText(Tr::tr("Open .ui.qml files with:")); uiQmlOpenMode.addOption({Tr::tr("Always Ask")}); 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())); useCustomFormatCommand.setSettingsKey(group, CUSTOM_COMMAND); useCustomFormatCommand.setLabelText( Tr::tr("Use custom command instead of built-in formatter")); formatCommand.setSettingsKey(group, FORMAT_COMMAND); formatCommand.setDisplayStyle(StringAspect::LineEditDisplay); formatCommand.setPlaceHolderText(defaultFormatCommand()); formatCommand.setLabelText(Tr::tr("Command:")); formatCommandOptions.setSettingsKey(group, FORMAT_COMMAND_OPTIONS); formatCommandOptions.setDisplayStyle(StringAspect::LineEditDisplay); formatCommandOptions.setLabelText(Tr::tr("Arguments:")); useCustomAnalyzer.setSettingsKey(group, CUSTOM_ANALYZER); useCustomAnalyzer.setLabelText(Tr::tr("Use customized static analyzer")); disabledMessages.setSettingsKey(group, DISABLED_MESSAGES); disabledMessages.setDefaultValue(defaultDisabledMessages()); disabledMessages.setFromSettingsTransformation(&fromSettingsTransformation); disabledMessages.setToSettingsTransformation(&toSettingsTransformation); disabledMessagesForNonQuickUi.setSettingsKey(group, DISABLED_MESSAGES_NONQUICKUI); disabledMessagesForNonQuickUi.setDefaultValue(defaultDisabledMessagesNonQuickUi()); disabledMessagesForNonQuickUi.setFromSettingsTransformation(&fromSettingsTransformation); disabledMessagesForNonQuickUi.setToSettingsTransformation(&toSettingsTransformation); readSettings(); autoFormatOnlyCurrentProject.setEnabler(&autoFormatOnSave); useLatestQmlls.setEnabler(&useQmlls); disableBuiltinCodemodel.setEnabler(&useQmlls); generateQmllsIniFiles.setEnabler(&useQmlls); ignoreMinimumQmllsVersion.setEnabler(&useQmlls); formatCommand.setEnabler(&useCustomFormatCommand); formatCommandOptions.setEnabler(&useCustomFormatCommand); } QString QmlJsEditingSettings::defaultFormatCommand() const { return DEFAULT_CUSTOM_FORMAT_COMMAND; } class AnalyzerMessageItem final : public Utils::TreeItem { public: AnalyzerMessageItem() = default; AnalyzerMessageItem(int number, const QString &message) : m_messageNumber(number) , m_message(message) {} QVariant data(int column, int role) const final { if (role == Qt::DisplayRole) { if (column == 0) return QString("M%1").arg(m_messageNumber); if (column == 2) return m_message.split('\n').first(); } else if (role == Qt::CheckStateRole) { if (column == 0) return m_checked ? Qt::Checked : Qt::Unchecked; if (column == 1) return m_disabledInNonQuickUi ? Qt::Checked : Qt::Unchecked; } return TreeItem::data(column, role); } bool setData(int column, const QVariant &value, int role) final { if (role == Qt::CheckStateRole) { if (column == 0) { m_checked = value.toBool(); return true; } if (column == 1) { m_disabledInNonQuickUi = value.toBool(); return true; } } return false; } Qt::ItemFlags flags(int column) const final { if (column == 0 || column == 1) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; else return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } int messageNumber() const { return m_messageNumber; } private: int m_messageNumber = -1; QString m_message; bool m_checked = true; bool m_disabledInNonQuickUi = false; }; class QmlJsEditingSettingsPageWidget final : public Core::IOptionsPageWidget { public: QmlJsEditingSettingsPageWidget() { QmlJsEditingSettings &s = settings(); analyzerMessageModel.setHeader({Tr::tr("Enabled"), Tr::tr("Disabled for non Qt Quick UI"), Tr::tr("Message")}); analyzerMessagesView = new QTreeView; analyzerMessagesView->setModel(&analyzerMessageModel); analyzerMessagesView->setEnabled(s.useCustomAnalyzer()); QObject::connect(&s.useCustomAnalyzer, &BoolAspect::volatileValueChanged, this, [this, &s] { analyzerMessagesView->setEnabled(s.useCustomAnalyzer.volatileValue()); }); analyzerMessagesView->setToolTip( "" + Tr::tr("Enabled checks can be disabled for non Qt Quick UI" " files, but disabled checks cannot get explicitly" " enabled for non Qt Quick UI files.")); analyzerMessagesView->setContextMenuPolicy(Qt::CustomContextMenu); connect(analyzerMessagesView, &QTreeView::customContextMenuRequested, this, &QmlJsEditingSettingsPageWidget::showContextMenu); using namespace Layouting; // clang-format off QWidget *formattingGroup = nullptr; Column { Group { bindTo(&formattingGroup), title(Tr::tr("Automatic Formatting on File Save")), Column { s.autoFormatOnSave, s.autoFormatOnlyCurrentProject, s.useCustomFormatCommand, Form { s.formatCommand, br, s.formatCommandOptions } }, }, Group { title(Tr::tr("Qt Quick Toolbars")), Column { s.pinContextPane, s.enableContextPane }, }, Group { title(Tr::tr("Features")), Column { s.foldAuxData, Row { s.uiQmlOpenMode, st } }, }, Group{ title(Tr::tr("QML Language Server")), Column { s.useQmlls, s.ignoreMinimumQmllsVersion, s.disableBuiltinCodemodel, s.useLatestQmlls, s.generateQmllsIniFiles }, }, Group { title(Tr::tr("Static Analyzer")), Column { s.useCustomAnalyzer, analyzerMessagesView }, }, st, }.attachTo(this); // clang-format on Utils::VariableChooser::addSupportForChildWidgets(formattingGroup, Utils::globalMacroExpander()); populateAnalyzerMessages(s.disabledMessages(), s.disabledMessagesForNonQuickUi()); } void apply() final { QmlJsEditingSettings &s = settings(); s.apply(); QList disabled; QList disabledForNonQuickUi; analyzerMessageModel.forAllItems( [&disabled, &disabledForNonQuickUi](AnalyzerMessageItem *item){ if (item->data(0, Qt::CheckStateRole) == Qt::Unchecked) disabled.append(item->messageNumber()); if (item->data(1, Qt::CheckStateRole) == Qt::Checked) disabledForNonQuickUi.append(item->messageNumber()); }); s.disabledMessages.setValue(disabled); s.disabledMessagesForNonQuickUi.setValue(disabledForNonQuickUi); s.writeSettings(); QmllsSettingsManager::instance()->checkForChanges(); } private: void populateAnalyzerMessages(const QList &disabled, const QList &disabledForNonQuickUi) { using namespace QmlJS::StaticAnalysis; auto knownMessages = Utils::sorted(Message::allMessageTypes()); auto root = analyzerMessageModel.rootItem(); for (auto msgType : knownMessages) { const QString msg = Message::prototypeForMessageType(msgType).message; auto item = new AnalyzerMessageItem(msgType, msg); item->setData(0, !disabled.contains(msgType), Qt::CheckStateRole); item->setData(1, disabledForNonQuickUi.contains(msgType), Qt::CheckStateRole); root->appendChild(item); } for (int column = 0; column < 3; ++column) analyzerMessagesView->resizeColumnToContents(column); } void showContextMenu(const QPoint &position) { QMenu menu; QAction *reset = new QAction(Tr::tr("Reset to Default"), &menu); menu.addAction(reset); connect(reset, &QAction::triggered, this, [this](){ analyzerMessageModel.clear(); populateAnalyzerMessages(defaultDisabledMessages(), defaultDisabledMessagesNonQuickUi()); }); menu.exec(analyzerMessagesView->mapToGlobal(position)); } QTreeView *analyzerMessagesView; Utils::TreeModel analyzerMessageModel; }; QmlJsEditingSettingsPage::QmlJsEditingSettingsPage() { setId("C.QmlJsEditing"); setDisplayName(::QmlJSEditor::Tr::tr("QML/JS Editing")); setCategory(Constants::SETTINGS_CATEGORY_QML); setWidgetCreator([] { return new QmlJsEditingSettingsPageWidget; }); } } // QmlJsEditor::Internal