Files
qt-creator/src/plugins/qmljseditor/qmljseditorsettings.cpp
Eike Ziller 3bbfd66bad Merge remote-tracking branch 'origin/14.0'
Conflicts:
	src/plugins/android/androidmanager.cpp
	src/plugins/android/androidrunner.cpp
	src/plugins/android/androidrunner.h
	src/plugins/qmldesigner/components/texteditor/texteditorview.cpp
	src/plugins/qmldesigner/components/texteditor/texteditorview.h
	src/plugins/qmldesigner/designmodecontext.cpp
	src/plugins/qmldesigner/designmodecontext.h
	src/plugins/qmljseditor/qmljseditingsettingspage.cpp

Change-Id: Idada49bb7441e8c5b748bd75b2e6bb2351bd323e
2024-07-25 12:56:53 +02:00

495 lines
18 KiB
C++

// 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 <coreplugin/coreconstants.h>
#include <coreplugin/icore.h>
#include <qmljs/qmljscheck.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljs/qmljsstaticanalysismessage.h>
#include <qmljstools/qmljstoolsconstants.h>
#include <qtsupport/qtversionmanager.h>
#include <utils/algorithm.h>
#include <utils/hostosinfo.h>
#include <utils/layoutbuilder.h>
#include <utils/macroexpander.h>
#include <utils/qtcsettings.h>
#include <utils/treemodel.h>
#include <utils/variablechooser.h>
#include <QCheckBox>
#include <QComboBox>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QLoggingCategory>
#include <QMenu>
#include <QTextStream>
#include <QTreeView>
#include <nanotrace/nanotrace.h>
#include <limits>
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<int>::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<int> defaultDisabledMessages()
{
static const QList<int> disabledByDefault = Utils::transform(
QmlJS::Check::defaultDisabledMessages(),
[](QmlJS::StaticAnalysis::Type t) { return int(t); });
return disabledByDefault;
}
static QList<int> defaultDisabledMessagesNonQuickUi()
{
static const QList<int> disabledForNonQuickUi = Utils::transform(
QmlJS::Check::defaultDisabledMessagesForNonQuickUi(),
[](QmlJS::StaticAnalysis::Type t){ return int(t); });
return disabledForNonQuickUi;
}
static QVariant toSettingsTransformation(const QVariant &v)
{
QList<int> list = v.value<QList<int>>();
QStringList result = Utils::transform<QStringList>(list, [](int i) { return QString::number(i); });
return QVariant::fromValue(result);
}
static QVariant fromSettingsTransformation(const QVariant &v)
{
QStringList list = v.value<QStringList>();
QList<int> result = Utils::transform<QList<int>>(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(
"<html>"
+ 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<int> disabled;
QList<int> 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<int> &disabled, const QList<int> &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<AnalyzerMessageItem> analyzerMessageModel;
};
QmlJsEditingSettingsPage::QmlJsEditingSettingsPage()
{
setId("C.QmlJsEditing");
setDisplayName(::QmlJSEditor::Tr::tr("QML/JS Editing"));
setCategory(Constants::SETTINGS_CATEGORY_QML);
setWidgetCreator([] { return new QmlJsEditingSettingsPageWidget; });
}
} // QmlJsEditor::Internal