integrate qmlformat and improve formatter settings UX

- Add reformat action to trigger full document formatting via qmlformat
- Integrate reformat action into context menu and qmljstools menu
- Remove custom formatter command from preferences, default to
  qmlformat when LSP formatting fails
- Group tabSettings and other options under 'Built-In Formatter Settings'
  for clarity
- Move formatter selection to Code Style page with a new radio button
  for selection
- Ensure preview updates according to the selected formatter
- Populate qmlformat configuration widget automatically for Qt's built-in
code style option by running qmlformat --write-defaults.
- TabSettings is a data structure being considered by the text editor
while rewriting the formatted data. Overwrite tabSettings with the
values in the .qmlformat.ini.
- fix the issue of builtin formatter being not respecting the
current codestyle's tab settings.

Task-number: QTCREATORBUG-26602
Change-Id: I2ec1b4a69712eedfafab358aaabb25c6b43ffa8e
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
This commit is contained in:
Semih Yavuz
2025-03-20 12:32:04 +01:00
parent 3805e2f191
commit a7e5b68bed
25 changed files with 875 additions and 252 deletions

View File

@@ -19,11 +19,12 @@
#include <projectexplorer/projectmanager.h>
#include <qmljs/parser/qmljsast_p.h>
#include <qmljstools/qmljsindenter.h>
#include <qmljstools/qmljsmodelmanager.h>
#include <qmljstools/qmljsqtstylecodeformatter.h>
#include <qmljs/parser/qmljsast_p.h>
#include <texteditor/tabsettings.h>
#include <utils/fileutils.h>
#include <utils/infobar.h>
@@ -466,6 +467,7 @@ QmlJSEditorDocumentPrivate::QmlJSEditorDocumentPrivate(QmlJSEditorDocument *pare
: q(parent)
, m_semanticHighlighter(new SemanticHighlighter(parent))
, m_outlineModel(new QmlOutlineModel(parent))
, m_tabSettings(parent->TextDocument::tabSettings())
{
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
@@ -838,5 +840,17 @@ void QmlJSEditorDocument::setSourcesWithCapabilities(
d->setSourcesWithCapabilities(cap);
}
TextEditor::TabSettings QmlJSEditorDocument::tabSettings() const
{
return d->m_tabSettings;
}
void QmlJSEditorDocument::setTabSettings(const TextEditor::TabSettings &tabSettings)
{
if (tabSettings != d->m_tabSettings) {
d->m_tabSettings = tabSettings;
emit tabSettingsChanged();
}
}
} // QmlJSEditor

View File

@@ -40,6 +40,8 @@ public:
void setSourcesWithCapabilities(const LanguageServerProtocol::ServerCapabilities &cap);
virtual TextEditor::TabSettings tabSettings() const override;
void setTabSettings(const TextEditor::TabSettings &tabSettings);
signals:
void updateCodeWarnings(QmlJS::Document::Ptr doc);
void semanticInfoUpdated(const QmlJSTools::SemanticInfo &semanticInfo);

View File

@@ -6,6 +6,8 @@
#include <languageserverprotocol/servercapabilities.h>
#include <qmljs/qmljsdocument.h>
#include <qmljstools/qmljssemanticinfo.h>
#include <texteditor/tabsettings.h>
#include <texteditor/textdocument.h>
#include <QObject>
#include <QTextLayout>
@@ -83,6 +85,7 @@ public:
QmllsStatus::Source::EmbeddedCodeModel,
QmllsStatus::Source::EmbeddedCodeModel,
{}};
TextEditor::TabSettings m_tabSettings;
};
} // Internal

View File

@@ -16,6 +16,8 @@
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljs/qmljsreformatter.h>
#include <qmljstools/qmlformatsettings.h>
#include <qmljstools/qmljscodestylesettings.h>
#include <qmljstools/qmljstoolsconstants.h>
#include <qmljstools/qmljstoolssettings.h>
@@ -25,6 +27,7 @@
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <extensionsystem/iplugin.h>
@@ -42,6 +45,7 @@
#include <texteditor/texteditor.h>
#include <texteditor/texteditorconstants.h>
#include <utils/filepath.h>
#include <utils/fsengine/fileiconprovider.h>
#include <utils/macroexpander.h>
#include <utils/mimeconstants.h>
@@ -54,6 +58,7 @@
using namespace ProjectExplorer;
using namespace Core;
using namespace Utils;
using namespace QmlJSTools;
namespace QmlJSEditor::Internal {
@@ -70,7 +75,7 @@ public:
Command *addToolAction(QAction *a, Context &context, Id id,
ActionContainer *c1, const QString &keySequence);
void reformatFile();
FormatResult reformatFile();
QmlJS::JsonSchemaManager *jsonManager() { return &m_jsonManager;}
QmlJSQuickFixAssistProvider *quickFixAssistProvider() { return &m_quickFixAssistProvider; }
@@ -140,11 +145,15 @@ QmlJSEditorPluginPrivate::QmlJSEditorPluginPrivate()
connect(semanticScan, &QAction::triggered, this, &QmlJSEditorPluginPrivate::runSemanticScan);
qmlToolsMenu->addAction(cmd);
m_reformatFileAction = new QAction(Tr::tr("Reformat File"), this);
cmd = ActionManager::registerAction(m_reformatFileAction,
Id("QmlJSEditor.ReformatFile"),
context);
connect(m_reformatFileAction, &QAction::triggered, this, &QmlJSEditorPluginPrivate::reformatFile);
m_reformatFileAction = ActionBuilder(this, TextEditor::Constants::REFORMAT_FILE)
.setContext(context)
.addOnTriggered([this] { reformatFile(); })
.setDefaultKeySequence(QKeySequence(Tr::tr("Ctrl+Shift+;")))
.setText(Tr::tr("Reformat Document"))
.addToContainer(Core::Constants::M_EDIT_ADVANCED, Core::Constants::G_EDIT_FORMAT)
.contextAction();
cmd = ActionManager::command(TextEditor::Constants::REFORMAT_FILE);
qmlToolsMenu->addAction(cmd);
QAction *inspectElementAction = new QAction(Tr::tr("Inspect API for Element Under Cursor"), this);
@@ -171,6 +180,9 @@ QmlJSEditorPluginPrivate::QmlJSEditorPluginPrivate()
cmd = ActionManager::command(TextEditor::Constants::AUTO_INDENT_SELECTION);
contextMenu->addAction(cmd);
cmd = ActionManager::command(TextEditor::Constants::REFORMAT_FILE);
contextMenu->addAction(cmd);
cmd = ActionManager::command(TextEditor::Constants::UN_COMMENT_SELECTION);
contextMenu->addAction(cmd);
@@ -188,35 +200,56 @@ QmlJS::JsonSchemaManager *jsonManager()
return dd->jsonManager();
}
static void reformatByQmlFormat(QPointer<QmlJSEditorDocument> document)
static void overrideTabSettings(QPointer<QmlJSEditorDocument> document)
{
QString formatCommand = settings().formatCommand();
if (formatCommand.isEmpty())
formatCommand = settings().defaultFormatCommand();
const auto exe = FilePath::fromUserInput(globalMacroExpander()->expand(formatCommand));
const QString args = globalMacroExpander()->expand(
settings().formatCommandOptions());
const CommandLine commandLine(exe, args, CommandLine::Raw);
// Search .qmlformat.ini and read the tab settings from it
if (!document)
return;
TextEditor::TabSettings tabSettings = document->tabSettings();
QSettings settings(
QmlJSTools::QmlFormatSettings::currentQmlFormatIniFile(document->filePath()).toUrlishString(),
QSettings::IniFormat);
if (settings.contains("IndentWidth"))
tabSettings.m_indentSize = settings.value("IndentWidth").toInt();
if (settings.contains("UseTabs"))
tabSettings.m_tabPolicy = settings.value("UseTabs").toBool()
? TextEditor::TabSettings::TabPolicy::TabsOnlyTabPolicy
: TextEditor::TabSettings::TabPolicy::SpacesOnlyTabPolicy;
document->setTabSettings(tabSettings);
}
static FormatResult reformatByQmlFormat(QPointer<QmlJSEditorDocument> document)
{
const FilePath &qmlformatPath = QmlFormatSettings::instance().latestQmlFormatPath();
if (!qmlformatPath.isExecutableFile()) {
Core::MessageManager::writeSilently(
Tr::tr("QmlFormat not found."));
return FormatResult::Failed;
}
const CommandLine commandLine(qmlformatPath, {});
TextEditor::Command command;
command.setExecutable(commandLine.executable());
command.setProcessing(TextEditor::Command::FileProcessing);
command.addOptions(commandLine.splitArguments());
command.addOption("--inplace");
command.addOption("%file");
if (!command.isValid())
return;
return FormatResult::Failed;
const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForDocument(document);
if (editors.isEmpty())
return;
return FormatResult::Failed;
IEditor *currentEditor = EditorManager::currentEditor();
IEditor *editor = editors.contains(currentEditor) ? currentEditor : editors.first();
if (auto widget = TextEditor::TextEditorWidget::fromEditor(editor))
if (auto widget = TextEditor::TextEditorWidget::fromEditor(editor)) {
TextEditor::formatEditor(widget, command);
return FormatResult::Success;
}
return FormatResult::Failed;
}
static void reformatByBuiltInFormatter(QPointer<QmlJSEditorDocument> document)
static FormatResult reformatByBuiltInFormatter(QPointer<QmlJSEditorDocument> document)
{
QmlJS::Document::Ptr documentPtr = document->semanticInfo().document;
QmlJS::Snapshot snapshot = QmlJS::ModelManagerInterface::instance()->snapshot();
@@ -224,7 +257,7 @@ static void reformatByBuiltInFormatter(QPointer<QmlJSEditorDocument> document)
if (document->isSemanticInfoOutdated()) {
QmlJS::Document::MutablePtr latestDocument;
const Utils::FilePath fileName = document->filePath();
const FilePath fileName = document->filePath();
latestDocument = snapshot.documentFromSource(
QString::fromUtf8(document->contents()),
fileName,
@@ -235,14 +268,16 @@ static void reformatByBuiltInFormatter(QPointer<QmlJSEditorDocument> document)
}
if (!documentPtr->isParsedCorrectly())
return;
return FormatResult::Failed;
TextEditor::TabSettings tabSettings = document->tabSettings();
QmlJSTools::QmlJSCodeStylePreferences *codeStyle
= QmlJSTools::QmlJSToolsSettings::globalCodeStyle();
TextEditor::TabSettings tabSettings = codeStyle->currentTabSettings();
const QString newText = QmlJS::reformat(
documentPtr,
tabSettings.m_indentSize,
tabSettings.m_tabSize,
QmlJSTools::QmlJSToolsSettings::globalCodeStyle()->currentCodeStyleSettings().lineLength);
codeStyle->currentCodeStyleSettings().lineLength);
auto ed = qobject_cast<TextEditor::BaseTextEditor *>(EditorManager::currentEditor());
if (ed) {
TextEditor::updateEditorText(ed->editorWidget(), newText);
@@ -252,45 +287,98 @@ static void reformatByBuiltInFormatter(QPointer<QmlJSEditorDocument> document)
tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
tc.insertText(newText);
}
return FormatResult::Success;
}
static bool reformatUsingLanguageServer(QPointer<QmlJSEditorDocument> document)
static FormatResult reformatUsingLanguageServer(QPointer<QmlJSEditorDocument> document)
{
if (!document)
return false;
return FormatResult::Failed;
if (!document->formatter())
return false;
return FormatResult::Failed;
TextEditor::BaseTextEditor *editor = qobject_cast<TextEditor::BaseTextEditor *>(
EditorManager::currentEditor());
if (!editor)
return false;
return FormatResult::Failed;
TextEditor::TextEditorWidget *editorWidget = editor->editorWidget();
if (!editorWidget)
return false;
return FormatResult::Failed;
overrideTabSettings(document);
document->setFormatterMode(TextEditor::Formatter::FormatMode::FullDocument);
editorWidget->autoFormat();
return true;
return FormatResult::Success;
}
void QmlJSEditorPluginPrivate::reformatFile()
static FormatResult reformatByCustomFormatter(
QPointer<QmlJSEditorDocument> document, const QmlJSTools::QmlJSCodeStyleSettings &settings)
{
if (!m_currentDocument)
return;
const FilePath &formatter = settings.customFormatterPath;
const QStringList &args = settings.customFormatterArguments.split(" ", Qt::SkipEmptyParts);
if (!formatter.isExecutableFile()) {
MessageManager::writeSilently(
Tr::tr("Custom Formatter path not found."));
return FormatResult::Failed;
}
const CommandLine commandLine(formatter, args);
TextEditor::Command command;
command.setExecutable(commandLine.executable());
command.setProcessing(TextEditor::Command::FileProcessing);
command.addOptions(commandLine.splitArguments());
command.addOption("--inplace");
command.addOption("%file");
if (!command.isValid())
return FormatResult::Failed;
const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForDocument(document);
if (editors.isEmpty())
return FormatResult::Failed;
IEditor *currentEditor = EditorManager::currentEditor();
IEditor *editor = editors.contains(currentEditor) ? currentEditor : editors.first();
if (auto widget = TextEditor::TextEditorWidget::fromEditor(editor)) {
TextEditor::formatEditor(widget, command);
return FormatResult::Success;
}
return FormatResult::Failed;
}
if (reformatUsingLanguageServer(m_currentDocument))
return;
if (settings().useCustomFormatCommand()) {
reformatByQmlFormat(m_currentDocument);
return;
FormatResult QmlJSEditorPluginPrivate::reformatFile()
{
if (!m_currentDocument) {
MessageManager::writeSilently(Tr::tr("Error: No current document to format."));
return FormatResult::Failed;
}
reformatByBuiltInFormatter(m_currentDocument);
const QmlJSCodeStyleSettings settings
= QmlJSToolsSettings::globalCodeStyle()->currentCodeStyleSettings();
const auto tryReformat = [this](auto formatterFunction) {
const FormatResult result = formatterFunction(m_currentDocument);
if (result != FormatResult::Success) {
MessageManager::writeSilently(
Tr::tr("Error: Formatting failed with the selected formatter."));
}
return result;
};
switch (settings.formatter) {
case QmlJSCodeStyleSettings::Formatter::QmlFormat:
return tryReformat([](auto doc) {
return reformatUsingLanguageServer(doc) == FormatResult::Success
? FormatResult::Success
: reformatByQmlFormat(doc);
});
case QmlJSCodeStyleSettings::Formatter::Custom:
return tryReformat(
[&settings](auto doc) { return reformatByCustomFormatter(doc, settings); });
case QmlJSCodeStyleSettings::Formatter::Builtin:
default:
return tryReformat([](auto doc) { return reformatByBuiltInFormatter(doc); });
}
}
Command *QmlJSEditorPluginPrivate::addToolAction(QAction *a,

View File

@@ -16,4 +16,9 @@ void setupQmlJSEditor();
void inspectElement();
void showContextPane();
enum class FormatResult {
Success,
Failed
};
} // QmlJSEditor::Internal

View File

@@ -63,15 +63,10 @@ const char QML_CONTEXTPANE_KEY[] = "QmlJSEditor.ContextPaneEnabled";
const char QML_CONTEXTPANEPIN_KEY[] = "QmlJSEditor.ContextPanePinned";
const char FOLD_AUX_DATA[] = "QmlJSEditor.FoldAuxData";
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 QDS_COMMAND[] = "QmlJSEditor.qdsCommand";
const char DEFAULT_CUSTOM_FORMAT_COMMAND[]
= "%{CurrentDocument:Project:QT_HOST_BINS}/qmlformat%{HostOs:ExecutableSuffix}";
const char SETTINGS_PAGE[] = "C.QmlJsEditing";
QmlJsEditingSettings &settings()
@@ -142,19 +137,6 @@ QmlJsEditingSettings::QmlJsEditingSettings()
uiQmlOpenMode.addOption({Tr::tr("Qt Design Studio"), {}, Core::Constants::MODE_DESIGN});
uiQmlOpenMode.addOption({Tr::tr("Qt Creator"), {}, Core::Constants::MODE_EDIT});
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"));
@@ -174,15 +156,6 @@ QmlJsEditingSettings::QmlJsEditingSettings()
qdsCommand.setVisible(false);
readSettings();
autoFormatOnlyCurrentProject.setEnabler(&autoFormatOnSave);
formatCommand.setEnabler(&useCustomFormatCommand);
formatCommandOptions.setEnabler(&useCustomFormatCommand);
}
QString QmlJsEditingSettings::defaultFormatCommand() const
{
return DEFAULT_CUSTOM_FORMAT_COMMAND;
}
FilePath QmlJsEditingSettings::defaultQdsCommand() const
@@ -285,15 +258,10 @@ public:
Column {
Group {
bindTo(&formattingGroup),
title(Tr::tr("Automatic Formatting on File Save")),
title(Tr::tr("Formatting")),
Column {
s.autoFormatOnSave,
s.autoFormatOnlyCurrentProject,
s.useCustomFormatCommand,
Form {
s.formatCommand, br,
s.formatCommandOptions
}
},
},
Group {

View File

@@ -20,7 +20,6 @@ class QmlJsEditingSettings final : public Utils::AspectContainer
public:
QmlJsEditingSettings();
QString defaultFormatCommand() const;
Utils::FilePath defaultQdsCommand() const;
Utils::BoolAspect enableContextPane{this};
@@ -28,11 +27,8 @@ public:
Utils::BoolAspect autoFormatOnSave{this};
Utils::BoolAspect autoFormatOnlyCurrentProject{this};
Utils::BoolAspect foldAuxData{this};
Utils::BoolAspect useCustomFormatCommand{this};
Utils::BoolAspect useCustomAnalyzer{this};
Utils::SelectionAspect uiQmlOpenMode{this};
Utils::StringAspect formatCommand{this};
Utils::StringAspect formatCommandOptions{this};
Utils::IntegersAspect disabledMessages{this};
Utils::IntegersAspect disabledMessagesForNonQuickUi{this};
Utils::FilePathAspect qdsCommand{this};

View File

@@ -7,8 +7,10 @@ add_qtc_plugin(QmlJSTools
qmljsbundleprovider.cpp qmljsbundleprovider.h
qmljscodestylepreferenceswidget.cpp qmljscodestylepreferenceswidget.h
qmljscodestylesettings.cpp qmljscodestylesettings.h
qmljscodestylesettingswidget.cpp qmljscodestylesettingswidget.h
qmljscodestylesettingspage.cpp qmljscodestylesettingspage.h
qmljscustomformatterwidget.cpp qmljscustomformatterwidget.h
qmlformatsettingswidget.cpp qmlformatsettingswidget.h
qmljsformatterselectionwidget.cpp qmljsformatterselectionwidget.h
qmljsfunctionfilter.cpp qmljsfunctionfilter.h
qmljsindenter.cpp qmljsindenter.h
qmljsmodelmanager.cpp qmljsmodelmanager.h

View File

@@ -0,0 +1,98 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qmlformatsettingswidget.h"
#include "qmljsformatterselectionwidget.h"
#include "qmlformatsettings.h"
#include "qmljstoolstr.h"
#include <texteditor/snippets/snippeteditor.h>
#include <utils/layoutbuilder.h>
#include <QVBoxLayout>
namespace QmlJSTools {
QmlFormatSettingsWidget::QmlFormatSettingsWidget(
QWidget *parent, FormatterSelectionWidget *selection)
: QmlCodeStyleWidgetBase(parent)
, m_qmlformatConfigTextEdit(std::make_unique<TextEditor::SnippetEditorWidget>())
, m_formatterSelectionWidget(selection)
{
QSizePolicy sp(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
sp.setHorizontalStretch(1);
m_qmlformatConfigTextEdit->setSizePolicy(sp);
using namespace Layouting;
// clang-format off
Column {
Group {
title(Tr::tr("Global qmlformat Configuration")),
Column {
m_qmlformatConfigTextEdit.get(),
},
},
noMargin
}.attachTo(this);
// clang-format on
connect(
m_qmlformatConfigTextEdit.get(),
&TextEditor::SnippetEditorWidget::textChanged,
this,
&QmlFormatSettingsWidget::slotSettingsChanged);
}
void QmlFormatSettingsWidget::setCodeStyleSettings(const QmlJSCodeStyleSettings &s)
{
QSignalBlocker blocker(this);
if (s.qmlformatIniContent != m_qmlformatConfigTextEdit->toPlainText())
m_qmlformatConfigTextEdit->setPlainText(s.qmlformatIniContent);
}
void QmlFormatSettingsWidget::setPreferences(QmlJSCodeStylePreferences *preferences)
{
if (m_preferences == preferences)
return; // nothing changes
slotCurrentPreferencesChanged(preferences);
// cleanup old
if (m_preferences) {
disconnect(m_preferences, &QmlJSCodeStylePreferences::currentValueChanged, this, nullptr);
disconnect(m_preferences, &QmlJSCodeStylePreferences::currentPreferencesChanged,
this, &QmlFormatSettingsWidget::slotCurrentPreferencesChanged);
}
m_preferences = preferences;
// fillup new
if (m_preferences) {
setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
connect(m_preferences, &QmlJSCodeStylePreferences::currentValueChanged, this, [this] {
this->setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
});
connect(m_preferences, &QmlJSCodeStylePreferences::currentPreferencesChanged,
this, &QmlFormatSettingsWidget::slotCurrentPreferencesChanged);
}
}
void QmlFormatSettingsWidget::slotCurrentPreferencesChanged(
TextEditor::ICodeStylePreferences *preferences)
{
auto *current = dynamic_cast<QmlJSCodeStylePreferences *>(
preferences ? preferences->currentPreferences() : nullptr);
const bool enableWidgets = current && !current->isReadOnly() && m_formatterSelectionWidget
&& m_formatterSelectionWidget->selection().value()
== QmlCodeStyleWidgetBase::QmlFormat;
setEnabled(enableWidgets);
}
void QmlFormatSettingsWidget::slotSettingsChanged()
{
QmlJSCodeStyleSettings settings = m_preferences ? m_preferences->currentCodeStyleSettings()
: QmlJSCodeStyleSettings::currentGlobalCodeStyle();
settings.qmlformatIniContent = m_qmlformatConfigTextEdit->toPlainText();
QmlFormatSettings::instance().globalQmlFormatIniFile().writeFileContents(settings.qmlformatIniContent.toUtf8());
emit settingsChanged(settings);
}
} // namespace QmlJSTools

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "qmljsformatterselectionwidget.h"
#include "qmljscodestylesettings.h"
#include <texteditor/snippets/snippeteditor.h>
#include <memory>
#include <QWidget>
namespace QmlJSTools {
class FormatterSelectionWidget;
class QmlFormatSettingsWidget : public QmlCodeStyleWidgetBase
{
public:
explicit QmlFormatSettingsWidget(
QWidget *parent = nullptr,
FormatterSelectionWidget *selection = nullptr);
void setCodeStyleSettings(const QmlJSCodeStyleSettings &s) override;
void setPreferences(QmlJSCodeStylePreferences *preferences) override;
void slotCurrentPreferencesChanged(TextEditor::ICodeStylePreferences* preferences) override;
private:
void slotSettingsChanged();
std::unique_ptr<TextEditor::SnippetEditorWidget> m_qmlformatConfigTextEdit;
FormatterSelectionWidget *m_formatterSelectionWidget = nullptr;
QmlJSCodeStylePreferences *m_preferences = nullptr;
};
} // namespace QmlJSTools

View File

@@ -4,23 +4,58 @@
#include "qmljscodestylepreferenceswidget.h"
#include "qmljscodestylesettings.h"
#include "qmljscodestylesettingswidget.h"
#include "qmljsformatterselectionwidget.h"
#include "qmljstoolstr.h"
#include <texteditor/simplecodestylepreferenceswidget.h>
#include <texteditor/texteditor.h>
#include <utils/layoutbuilder.h>
#include <utils/aspects.h>
#include <QLabel>
#include <QVBoxLayout>
using namespace TextEditor;
namespace QmlJSTools {
QmlJSCodeStylePreferencesWidget::QmlJSCodeStylePreferencesWidget(QWidget *parent) :
QWidget(parent)
BuiltinFormatterSettingsWidget::BuiltinFormatterSettingsWidget(QWidget *parent, FormatterSelectionWidget *selection)
: QmlCodeStyleWidgetBase(parent)
, m_tabSettingsWidget(new TextEditor::TabSettingsWidget)
, m_formatterSelectionWidget(selection)
{
m_codeStyleSettingsWidget = new QmlJSCodeStyleSettingsWidget(this);
auto layout = new QVBoxLayout(this);
layout->addWidget(m_codeStyleSettingsWidget);
layout->setContentsMargins(QMargins());
m_lineLength.setRange(0, 999);
m_tabSettingsWidget->setParent(this);
using namespace Layouting;
Column {
Group {
title(Tr::tr("Builtin Formatter Settings")),
Column {
m_tabSettingsWidget,
Group {
title(Tr::tr("Other Settings")),
Form {
Tr::tr("Line Length:"), m_lineLength, br
}
}
}
}
}.attachTo(this);
connect(
&m_lineLength,
&Utils::IntegerAspect::changed,
this,
&BuiltinFormatterSettingsWidget::slotSettingsChanged);
}
void QmlJSCodeStylePreferencesWidget::setPreferences(QmlJSCodeStylePreferences *preferences)
void BuiltinFormatterSettingsWidget::setCodeStyleSettings(const QmlJSCodeStyleSettings &settings)
{
QSignalBlocker blocker(this);
m_lineLength.setValue(settings.lineLength);
}
void BuiltinFormatterSettingsWidget::setPreferences(QmlJSCodeStylePreferences *preferences)
{
if (m_preferences == preferences)
return; // nothing changes
@@ -31,41 +66,58 @@ void QmlJSCodeStylePreferencesWidget::setPreferences(QmlJSCodeStylePreferences *
if (m_preferences) {
disconnect(m_preferences, &QmlJSCodeStylePreferences::currentValueChanged, this, nullptr);
disconnect(m_preferences, &QmlJSCodeStylePreferences::currentPreferencesChanged,
this, &QmlJSCodeStylePreferencesWidget::slotCurrentPreferencesChanged);
disconnect(m_codeStyleSettingsWidget, &QmlJSCodeStyleSettingsWidget::settingsChanged,
this, &QmlJSCodeStylePreferencesWidget::slotSettingsChanged);
this, &BuiltinFormatterSettingsWidget::slotCurrentPreferencesChanged);
disconnect(m_preferences, &ICodeStylePreferences::currentTabSettingsChanged,
m_tabSettingsWidget, &TabSettingsWidget::setTabSettings);
disconnect(m_tabSettingsWidget, &TabSettingsWidget::settingsChanged,
this, &BuiltinFormatterSettingsWidget::slotTabSettingsChanged);
}
m_preferences = preferences;
// fillup new
if (m_preferences) {
m_codeStyleSettingsWidget->setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
connect(m_preferences, &QmlJSCodeStylePreferences::currentValueChanged, this, [this] {
m_codeStyleSettingsWidget->setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
});
connect(m_preferences, &QmlJSCodeStylePreferences::currentPreferencesChanged,
this, &QmlJSCodeStylePreferencesWidget::slotCurrentPreferencesChanged);
connect(m_codeStyleSettingsWidget, &QmlJSCodeStyleSettingsWidget::settingsChanged,
this, &QmlJSCodeStylePreferencesWidget::slotSettingsChanged);
this, &BuiltinFormatterSettingsWidget::slotCurrentPreferencesChanged);
m_tabSettingsWidget->setTabSettings(m_preferences->currentTabSettings());
connect(m_preferences, &ICodeStylePreferences::currentTabSettingsChanged,
m_tabSettingsWidget, &TabSettingsWidget::setTabSettings);
connect(m_tabSettingsWidget, &TabSettingsWidget::settingsChanged,
this, &BuiltinFormatterSettingsWidget::slotTabSettingsChanged);
}
}
void QmlJSCodeStylePreferencesWidget::slotCurrentPreferencesChanged(TextEditor::ICodeStylePreferences *preferences)
void BuiltinFormatterSettingsWidget::slotCurrentPreferencesChanged(
TextEditor::ICodeStylePreferences *preferences)
{
m_codeStyleSettingsWidget->setEnabled(preferences && preferences->currentPreferences() &&
!preferences->currentPreferences()->isReadOnly());
QmlJSCodeStylePreferences *current = dynamic_cast<QmlJSCodeStylePreferences *>(
preferences ? preferences->currentPreferences() : nullptr);
const bool enableWidgets = current && !current->isReadOnly() && m_formatterSelectionWidget
&& m_formatterSelectionWidget->selection().value()
== QmlCodeStyleWidgetBase::Builtin;
setEnabled(enableWidgets);
}
void QmlJSCodeStylePreferencesWidget::slotSettingsChanged(const QmlJSCodeStyleSettings &settings)
void BuiltinFormatterSettingsWidget::slotSettingsChanged()
{
QmlJSCodeStyleSettings settings = m_preferences ? m_preferences->currentCodeStyleSettings()
: QmlJSCodeStyleSettings::currentGlobalCodeStyle();
settings.lineLength = m_lineLength.value();
emit settingsChanged(settings);
}
void BuiltinFormatterSettingsWidget::slotTabSettingsChanged(const TextEditor::TabSettings &settings)
{
if (!m_preferences)
return;
QmlJSCodeStylePreferences *current = dynamic_cast<QmlJSCodeStylePreferences*>(m_preferences->currentPreferences());
ICodeStylePreferences *current = m_preferences->currentPreferences();
if (!current)
return;
current->setCodeStyleSettings(settings);
current->setTabSettings(settings);
}
} // namespace QmlJSTools

View File

@@ -3,30 +3,35 @@
#pragma once
#include "qmljstools_global.h"
#include "qmljscodestylesettings.h"
#include "qmljsformatterselectionwidget.h"
#include <texteditor/tabsettingswidget.h>
#include <utils/aspects.h>
#include <QWidget>
QT_BEGIN_NAMESPACE
class QSpinBox;
QT_END_NAMESPACE
namespace QmlJSTools {
class QmlJSCodeStyleSettingsWidget;
class QMLJSTOOLS_EXPORT QmlJSCodeStylePreferencesWidget : public QWidget
class BuiltinFormatterSettingsWidget : public QmlCodeStyleWidgetBase
{
Q_OBJECT
public:
explicit QmlJSCodeStylePreferencesWidget(QWidget *parent = nullptr);
void setPreferences(QmlJSCodeStylePreferences *tabPreferences);
explicit BuiltinFormatterSettingsWidget(QWidget *parent, FormatterSelectionWidget *selection);
void setCodeStyleSettings(const QmlJSCodeStyleSettings &settings) override;
void setPreferences(QmlJSCodeStylePreferences *tabPreferences) override;
void slotCurrentPreferencesChanged(TextEditor::ICodeStylePreferences* preferences) override;
private:
void slotCurrentPreferencesChanged(TextEditor::ICodeStylePreferences* preferences);
void slotSettingsChanged(const QmlJSCodeStyleSettings &settings);
void slotSettingsChanged();
void slotTabSettingsChanged(const TextEditor::TabSettings &settings);
QmlJSCodeStyleSettingsWidget *m_codeStyleSettingsWidget;
Utils::IntegerAspect m_lineLength;
TextEditor::TabSettingsWidget *m_tabSettingsWidget;
QmlJSCodeStylePreferences *m_preferences = nullptr;
FormatterSelectionWidget *m_formatterSelectionWidget;
};
} // namespace QmlJSTools

View File

@@ -18,9 +18,12 @@
#include <utils/qtcassert.h>
static const char lineLengthKey[] = "LineLength";
static const char qmlformatIniContentKey[] = "QmlFormatIniContent";
static const char formatterKey[] = "Formatter";
static const char customFormatterPathKey[] = "CustomFormatterPath";
static const char customFormatterArgumentsKey[] = "CustomFormatterArguments";
using namespace Utils;
namespace QmlJSTools {
// QmlJSCodeStyleSettings
@@ -30,18 +33,28 @@ QmlJSCodeStyleSettings::QmlJSCodeStyleSettings() = default;
Store QmlJSCodeStyleSettings::toMap() const
{
return {
{lineLengthKey, lineLength}
{formatterKey, formatter},
{lineLengthKey, lineLength},
{qmlformatIniContentKey, qmlformatIniContent},
{customFormatterPathKey, customFormatterPath.toUrlishString()},
{customFormatterArgumentsKey, customFormatterArguments}
};
}
void QmlJSCodeStyleSettings::fromMap(const Store &map)
{
lineLength = map.value(lineLengthKey, lineLength).toInt();
qmlformatIniContent = map.value(qmlformatIniContentKey, qmlformatIniContent).toString();
formatter = static_cast<Formatter>(map.value(formatterKey, formatter).toInt());
customFormatterPath = Utils::FilePath::fromString(map.value(customFormatterPathKey).toString());
customFormatterArguments = map.value(customFormatterArgumentsKey).toString();
}
bool QmlJSCodeStyleSettings::equals(const QmlJSCodeStyleSettings &rhs) const
{
return lineLength == rhs.lineLength;
return lineLength == rhs.lineLength && qmlformatIniContent == rhs.qmlformatIniContent
&& formatter == rhs.formatter && customFormatterPath == rhs.customFormatterPath
&& customFormatterArguments == rhs.customFormatterArguments;
}
QmlJSCodeStyleSettings QmlJSCodeStyleSettings::currentGlobalCodeStyle()

View File

@@ -7,6 +7,7 @@
#include <texteditor/icodestylepreferences.h>
#include <utils/filepath.h>
#include <utils/store.h>
namespace TextEditor { class TabSettings; }
@@ -19,7 +20,17 @@ class QMLJSTOOLS_EXPORT QmlJSCodeStyleSettings
public:
QmlJSCodeStyleSettings();
enum Formatter {
Builtin,
QmlFormat,
Custom
};
int lineLength = 80;
QString qmlformatIniContent;
Formatter formatter = Builtin;
Utils::FilePath customFormatterPath;
QString customFormatterArguments;
Utils::Store toMap() const;
void fromMap(const Utils::Store &map);

View File

@@ -3,64 +3,112 @@
#include "qmljscodestylesettingspage.h"
#include "qmlformatsettings.h"
#include "qmlformatsettingswidget.h"
#include "qmljscodestylepreferenceswidget.h"
#include "qmljscodestylesettings.h"
#include "qmljscustomformatterwidget.h"
#include "qmljsformatterselectionwidget.h"
#include "qmljsqtstylecodeformatter.h"
#include "qmljstoolsconstants.h"
#include "qmljstoolssettings.h"
#include "qmljstoolstr.h"
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <extensionsystem/pluginmanager.h>
#include <qmljseditor/qmljseditorconstants.h>
#include <texteditor/codestyleeditor.h>
#include <texteditor/command.h>
#include <texteditor/displaysettings.h>
#include <texteditor/fontsettings.h>
#include <texteditor/formattexteditor.h>
#include <texteditor/icodestylepreferencesfactory.h>
#include <texteditor/simplecodestylepreferenceswidget.h>
#include <texteditor/snippets/snippeteditor.h>
#include <texteditor/snippets/snippetprovider.h>
#include <texteditor/tabsettings.h>
#include <texteditor/texteditorsettings.h>
#include <utils/commandline.h>
#include <utils/filepath.h>
#include <utils/layoutbuilder.h>
#include <qmljseditor/qmljseditorconstants.h>
#include <QTextStream>
#include <QVBoxLayout>
#include <QStandardPaths>
using namespace TextEditor;
using namespace QmlJSTools;
namespace QmlJSTools::Internal {
constexpr int BuiltinFormatterIndex = QmlCodeStyleWidgetBase::Builtin;
constexpr int QmlFormatIndex = QmlCodeStyleWidgetBase::QmlFormat;
constexpr int CustomFormatterIndex = QmlCodeStyleWidgetBase::Custom;
// QmlJSCodeStylePreferencesWidget
QmlJSCodeStylePreferencesWidget::QmlJSCodeStylePreferencesWidget(
const QString &previewText, QWidget *parent)
: TextEditor::CodeStyleEditorWidget(parent)
, m_formatterSelectionWidget(new FormatterSelectionWidget(this))
, m_formatterSettingsStack(new QStackedWidget(this))
{
m_tabPreferencesWidget = new SimpleCodeStylePreferencesWidget;
m_codeStylePreferencesWidget = new QmlJSTools::QmlJSCodeStylePreferencesWidget;
m_previewTextEdit = new SnippetEditorWidget;
m_formatterSettingsStack->insertWidget(BuiltinFormatterIndex, new BuiltinFormatterSettingsWidget(this, m_formatterSelectionWidget));
m_formatterSettingsStack->insertWidget(QmlFormatIndex, new QmlFormatSettingsWidget(this, m_formatterSelectionWidget));
m_formatterSettingsStack->insertWidget(CustomFormatterIndex, new CustomFormatterWidget(this, m_formatterSelectionWidget));
for (const auto &formatterWidget :
m_formatterSettingsStack->findChildren<QmlCodeStyleWidgetBase *>()) {
connect(
formatterWidget,
&QmlCodeStyleWidgetBase::settingsChanged,
this,
&QmlJSCodeStylePreferencesWidget::slotSettingsChanged);
}
const int index = m_formatterSelectionWidget->selection().value();
m_formatterSettingsStack->setCurrentIndex(index);
m_previewTextEdit = new SnippetEditorWidget(this);
m_previewTextEdit->setPlainText(previewText);
QSizePolicy sp(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
sp.setHorizontalStretch(1);
m_previewTextEdit->setSizePolicy(sp);
decorateEditor(TextEditorSettings::fontSettings());
connect(
TextEditorSettings::instance(),
&TextEditorSettings::fontSettingsChanged,
this,
&QmlJSCodeStylePreferencesWidget::decorateEditor);
connect(
m_formatterSelectionWidget,
&FormatterSelectionWidget::settingsChanged,
[this](const QmlJSCodeStyleSettings &settings) {
int index = m_formatterSelectionWidget->selection().volatileValue();
if (index < 0 || index >= static_cast<int>(m_formatterSettingsStack->count()))
return;
m_formatterSettingsStack->setCurrentIndex(index);
if (auto *current = dynamic_cast<QmlCodeStyleWidgetBase *>(
m_formatterSettingsStack->widget(index))) {
current->slotCurrentPreferencesChanged(m_preferences);
}
slotSettingsChanged(settings);
});
using namespace Layouting;
Row {
Column {
m_tabPreferencesWidget,
m_codeStylePreferencesWidget,
st,
m_formatterSelectionWidget, br,
m_formatterSettingsStack,
st
},
m_previewTextEdit,
noMargin
}.attachTo(this);
connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged,
this, &QmlJSCodeStylePreferencesWidget::decorateEditor);
setVisualizeWhitespace(true);
updatePreview();
@@ -69,14 +117,20 @@ QmlJSCodeStylePreferencesWidget::QmlJSCodeStylePreferencesWidget(
void QmlJSCodeStylePreferencesWidget::setPreferences(QmlJSCodeStylePreferences *preferences)
{
m_preferences = preferences;
m_tabPreferencesWidget->setPreferences(preferences);
m_codeStylePreferencesWidget->setPreferences(preferences);
m_formatterSelectionWidget->setPreferences(preferences);
for (const auto &formatterWidget :
m_formatterSettingsStack->findChildren<QmlCodeStyleWidgetBase *>()) {
formatterWidget->setPreferences(preferences);
}
if (m_preferences)
{
connect(m_preferences, &ICodeStylePreferences::currentTabSettingsChanged,
this, &QmlJSCodeStylePreferencesWidget::slotSettingsChanged);
this, &QmlJSCodeStylePreferencesWidget::updatePreview);
connect(m_preferences, &QmlJSCodeStylePreferences::currentValueChanged,
this, &QmlJSCodeStylePreferencesWidget::slotSettingsChanged);
[this]{
m_formatterSettingsStack->setCurrentIndex(m_formatterSelectionWidget->selection().value());
updatePreview();
});
}
updatePreview();
}
@@ -95,12 +149,36 @@ void QmlJSCodeStylePreferencesWidget::setVisualizeWhitespace(bool on)
m_previewTextEdit->setDisplaySettings(displaySettings);
}
void QmlJSCodeStylePreferencesWidget::slotSettingsChanged()
void QmlJSCodeStylePreferencesWidget::slotSettingsChanged(const QmlJSCodeStyleSettings &settings)
{
if (!m_preferences)
return;
QmlJSCodeStylePreferences *current = dynamic_cast<QmlJSCodeStylePreferences*>(m_preferences->currentPreferences());
if (!current)
return;
current->setCodeStyleSettings(settings);
updatePreview();
}
void QmlJSCodeStylePreferencesWidget::updatePreview()
{
switch (m_formatterSelectionWidget->selection().value()) {
case QmlCodeStyleWidgetBase::Builtin:
builtInFormatterPreview();
break;
case QmlCodeStyleWidgetBase::QmlFormat:
qmlformatPreview();
break;
case QmlCodeStyleWidgetBase::Custom:
customFormatterPreview();
break;
}
}
void QmlJSCodeStylePreferencesWidget::builtInFormatterPreview()
{
QTextDocument *doc = m_previewTextEdit->document();
@@ -121,6 +199,63 @@ void QmlJSCodeStylePreferencesWidget::updatePreview()
tc.endEditBlock();
}
void QmlJSCodeStylePreferencesWidget::qmlformatPreview()
{
using namespace Core;
const Utils::FilePath &qmlFormatPath = QmlFormatSettings::instance().latestQmlFormatPath();
if (qmlFormatPath.isEmpty()) {
MessageManager::writeSilently("QmlFormat not found.");
return;
}
const Utils::CommandLine commandLine(qmlFormatPath);
TextEditor::Command command;
command.setExecutable(commandLine.executable());
command.setProcessing(TextEditor::Command::FileProcessing);
command.addOptions(commandLine.splitArguments());
command.addOption("--inplace");
command.addOption("%file");
if (!command.isValid())
return;
TextEditor::TabSettings tabSettings;
QSettings settings(
QmlJSTools::QmlFormatSettings::globalQmlFormatIniFile().toUrlishString(),
QSettings::IniFormat);
if (settings.contains("IndentWidth"))
tabSettings.m_indentSize = settings.value("IndentWidth").toInt();
if (settings.contains("UseTabs"))
tabSettings.m_tabPolicy = settings.value("UseTabs").toBool()
? TextEditor::TabSettings::TabPolicy::TabsOnlyTabPolicy
: TextEditor::TabSettings::TabPolicy::SpacesOnlyTabPolicy;
QString dummyFilePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/dummy.qml";
m_previewTextEdit->textDocument()->setFilePath(Utils::FilePath::fromString(dummyFilePath));
m_previewTextEdit->textDocument()->setTabSettings(tabSettings);
TextEditor::formatEditor(m_previewTextEdit, command);
}
void QmlJSCodeStylePreferencesWidget::customFormatterPreview()
{
Utils::FilePath path = m_preferences->currentCodeStyleSettings().customFormatterPath;
QStringList args = m_preferences->currentCodeStyleSettings()
.customFormatterArguments.split(" ", Qt::SkipEmptyParts);
if (path.isEmpty()) {
Core::MessageManager::writeSilently("Custom formatter not found.");
return;
}
const Utils::CommandLine commandLine(path, args);
TextEditor::Command command;
command.setExecutable(commandLine.executable());
command.setProcessing(TextEditor::Command::FileProcessing);
command.addOptions(commandLine.splitArguments());
command.addOption("--inplace");
command.addOption("%file");
if (!command.isValid())
return;
QString dummyFilePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/dummy.qml";
m_previewTextEdit->textDocument()->setFilePath(Utils::FilePath::fromString(dummyFilePath));
TextEditor::formatEditor(m_previewTextEdit, command);
}
// QmlJSCodeStyleSettingsPageWidget
class QmlJSCodeStyleSettingsPageWidget : public Core::IOptionsPageWidget

View File

@@ -4,10 +4,13 @@
#pragma once
#include "qmljscodestylesettings.h"
#include "qmljsformatterselectionwidget.h"
#include <coreplugin/dialogs/ioptionspage.h>
#include <texteditor/codestyleeditor.h>
#include <QStackedWidget>
QT_BEGIN_NAMESPACE
class QString;
class QWidget;
@@ -15,14 +18,10 @@ QT_END_NAMESPACE
namespace TextEditor {
class FontSettings;
class SimpleCodeStylePreferencesWidget;
class SnippetEditorWidget;
}
namespace QmlJSTools {
class QmlJSCodeStylePreferencesWidget;
class QmlJSCodeStyleSettings;
namespace Internal {
class QmlJSCodeStylePreferencesWidget : public TextEditor::CodeStyleEditorWidget
@@ -37,16 +36,19 @@ public:
private:
void decorateEditor(const TextEditor::FontSettings &fontSettings);
void setVisualizeWhitespace(bool on);
void slotSettingsChanged();
void slotSettingsChanged(const QmlJSCodeStyleSettings &);
void slotCurrentPreferencesChanged(TextEditor::ICodeStylePreferences *preferences);
void updatePreview();
void builtInFormatterPreview();
void qmlformatPreview();
void customFormatterPreview();
QmlJSCodeStylePreferences *m_preferences = nullptr;
TextEditor::SimpleCodeStylePreferencesWidget *m_tabPreferencesWidget;
QmlJSTools::QmlJSCodeStylePreferencesWidget *m_codeStylePreferencesWidget;
FormatterSelectionWidget *m_formatterSelectionWidget;
QStackedWidget *m_formatterSettingsStack;
TextEditor::SnippetEditorWidget *m_previewTextEdit;
QmlJSCodeStylePreferences *m_preferences = nullptr;
};
class QmlJSCodeStyleSettingsPage : public Core::IOptionsPage
{
public:

View File

@@ -1,59 +0,0 @@
// 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 "qmljscodestylesettingswidget.h"
#include "qmljscodestylesettings.h"
#include "qmljstoolstr.h"
#include <utils/layoutbuilder.h>
#include <QSpinBox>
#include <QTextStream>
namespace QmlJSTools {
QmlJSCodeStyleSettingsWidget::QmlJSCodeStyleSettingsWidget(QWidget *parent)
: QWidget(parent)
{
m_lineLengthSpinBox = new QSpinBox;
m_lineLengthSpinBox->setMinimum(0);
m_lineLengthSpinBox->setMaximum(999);
using namespace Layouting;
// clang-format off
Column {
Group {
title(Tr::tr("Other")),
Form {
Tr::tr("&Line length:"), m_lineLengthSpinBox, br,
}
},
noMargin
}.attachTo(this);
// clang-format on
connect(m_lineLengthSpinBox, &QSpinBox::valueChanged,
this, &QmlJSCodeStyleSettingsWidget::slotSettingsChanged);
}
void QmlJSCodeStyleSettingsWidget::setCodeStyleSettings(const QmlJSCodeStyleSettings &settings)
{
QSignalBlocker blocker(this);
m_lineLengthSpinBox->setValue(settings.lineLength);
}
QmlJSCodeStyleSettings QmlJSCodeStyleSettingsWidget::codeStyleSettings() const
{
QmlJSCodeStyleSettings set;
set.lineLength = m_lineLengthSpinBox->value();
return set;
}
void QmlJSCodeStyleSettingsWidget::slotSettingsChanged()
{
emit settingsChanged(codeStyleSettings());
}
} // namespace TextEditor

View File

@@ -1,44 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "qmljstools_global.h"
#include <QGroupBox>
QT_BEGIN_NAMESPACE
class QSpinBox;
QT_END_NAMESPACE
namespace QmlJSTools {
class QmlJSCodeStyleSettings;
class QMLJSTOOLS_EXPORT QmlJSCodeStyleSettingsWidget : public QWidget
{
Q_OBJECT
public:
enum CodingStyleLink {
CppLink,
QtQuickLink
};
explicit QmlJSCodeStyleSettingsWidget(QWidget *parent = nullptr);
QmlJSCodeStyleSettings codeStyleSettings() const;
void setCodingStyleWarningVisible(bool visible);
void setCodeStyleSettings(const QmlJSCodeStyleSettings &settings);
signals:
void settingsChanged(const QmlJSCodeStyleSettings &);
private:
void slotSettingsChanged();
void codingStyleLinkActivated(const QString &linkString);
QSpinBox *m_lineLengthSpinBox;
};
} // namespace QmlJSTools

View File

@@ -0,0 +1,117 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qmljscustomformatterwidget.h"
#include "qmlformatsettings.h"
#include "qmljsformatterselectionwidget.h"
#include "qmljstoolstr.h"
#include <utils/layoutbuilder.h>
#include <QWidget>
namespace QmlJSTools {
CustomFormatterWidget::CustomFormatterWidget(QWidget *parent, FormatterSelectionWidget *selection)
: QmlCodeStyleWidgetBase(parent)
, m_formatterSelectionWidget(selection)
{
m_customFormatterPath.setParent(this);
m_customFormatterArguments.setParent(this);
m_customFormatterPath.setPlaceHolderText(
QmlFormatSettings::instance().latestQmlFormatPath().toUrlishString());
m_customFormatterPath.setLabelText(Tr::tr("Command:"));
m_customFormatterArguments.setLabelText(Tr::tr("Arguments:"));
m_customFormatterArguments.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
using namespace Layouting;
// clang-format off
Column {
Group {
title(Tr::tr("Custom Formatter Configuration")),
Column {
m_customFormatterPath, br,
m_customFormatterArguments, br,
st
},
},
noMargin,
}.attachTo(this);
// clang-format on
connect(
&m_customFormatterPath,
&Utils::BaseAspect::changed,
this,
&CustomFormatterWidget::slotSettingsChanged);
connect(
&m_customFormatterArguments,
&Utils::BaseAspect::changed,
this,
&CustomFormatterWidget::slotSettingsChanged);
}
void CustomFormatterWidget::setCodeStyleSettings(const QmlJSCodeStyleSettings& s)
{
QSignalBlocker blocker(this);
if (s.customFormatterPath != m_customFormatterPath.expandedValue()) {
m_customFormatterPath.setValue(s.customFormatterPath);
}
if (s.customFormatterArguments != m_customFormatterArguments.value()) {
m_customFormatterArguments.setValue(s.customFormatterArguments);
}
}
void CustomFormatterWidget::setPreferences(QmlJSCodeStylePreferences *preferences)
{
if (m_preferences == preferences)
return; // nothing changes
slotCurrentPreferencesChanged(preferences);
// cleanup old
if (m_preferences) {
disconnect(m_preferences, &QmlJSCodeStylePreferences::currentValueChanged, this, nullptr);
disconnect(m_preferences, &QmlJSCodeStylePreferences::currentPreferencesChanged,
this, &CustomFormatterWidget::slotCurrentPreferencesChanged);
}
m_preferences = preferences;
// fillup new
if (m_preferences) {
setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
connect(m_preferences, &QmlJSCodeStylePreferences::currentValueChanged, this, [this] {
this->setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
});
connect(m_preferences, &QmlJSCodeStylePreferences::currentPreferencesChanged,
this, &CustomFormatterWidget::slotCurrentPreferencesChanged);
}
}
void CustomFormatterWidget::slotCurrentPreferencesChanged(
TextEditor::ICodeStylePreferences *preferences)
{
QmlJSCodeStylePreferences *current = dynamic_cast<QmlJSCodeStylePreferences *>(
preferences ? preferences->currentPreferences() : nullptr);
const bool enableWidgets = current && !current->isReadOnly() && m_formatterSelectionWidget
&& m_formatterSelectionWidget->selection().value()
== QmlCodeStyleWidgetBase::Custom;
setEnabled(enableWidgets);
}
void CustomFormatterWidget::slotSettingsChanged()
{
QmlJSCodeStyleSettings settings = m_preferences ? m_preferences->currentCodeStyleSettings()
: QmlJSCodeStyleSettings::currentGlobalCodeStyle();
if (m_customFormatterPath.value().isEmpty()) {
m_customFormatterPath.setValue(
QmlFormatSettings::instance().latestQmlFormatPath().toUrlishString());
}
settings.customFormatterPath = m_customFormatterPath.expandedValue();
settings.customFormatterArguments = m_customFormatterArguments.value();
emit settingsChanged(settings);
}
} // namespace QmlJSTools

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "qmljscodestylesettings.h"
#include "qmljsformatterselectionwidget.h"
#include <utils/aspects.h>
namespace QmlJSTools {
class CustomFormatterWidget : public QmlCodeStyleWidgetBase
{
public:
explicit CustomFormatterWidget(QWidget *parent, FormatterSelectionWidget *selection = nullptr);
void setCodeStyleSettings(const QmlJSCodeStyleSettings &s) override;
void setPreferences(QmlJSCodeStylePreferences *preferences) override;
void slotCurrentPreferencesChanged(TextEditor::ICodeStylePreferences* preferences) override;
private:
void slotSettingsChanged();
Utils::FilePathAspect m_customFormatterPath;
Utils::StringAspect m_customFormatterArguments;
FormatterSelectionWidget *m_formatterSelectionWidget = nullptr;
QmlJSCodeStylePreferences *m_preferences = nullptr;
};
} // namespace QmlJSTools

View File

@@ -0,0 +1,89 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qmljsformatterselectionwidget.h"
#include "qmljstoolstr.h"
#include <utils/layoutbuilder.h>
namespace QmlJSTools {
QmlCodeStyleWidgetBase::QmlCodeStyleWidgetBase(QWidget *parent)
: QWidget(parent)
{
}
FormatterSelectionWidget::FormatterSelectionWidget(QWidget *parent)
: QmlCodeStyleWidgetBase(parent)
{
using namespace Utils;
m_formatterSelection.setDefaultValue(Builtin);
m_formatterSelection.setDisplayStyle(SelectionAspect::DisplayStyle::RadioButtons);
m_formatterSelection.addOption(Tr::tr("Built-In Formatter [Deprecated]") );
m_formatterSelection.addOption(Tr::tr("QmlFormat [LSP]"));
m_formatterSelection.addOption(Tr::tr("Custom Formatter [Must be qmlformat compatible]"));
m_formatterSelection.setLabelText(Tr::tr("Formatter"));
connect(&m_formatterSelection, &SelectionAspect::changed,
this, &FormatterSelectionWidget::slotSettingsChanged);
using namespace Layouting;
Column {
Group {
title(Tr::tr("Formatter Selection")),
Column {
m_formatterSelection, br
}
}
}.attachTo(this);
}
void FormatterSelectionWidget::setCodeStyleSettings(const QmlJSCodeStyleSettings &s)
{
if (s.formatter != m_formatterSelection.value())
m_formatterSelection.setValue(s.formatter);
}
void FormatterSelectionWidget::setPreferences(QmlJSCodeStylePreferences *preferences)
{
if (m_preferences == preferences)
return; // nothing changes
slotCurrentPreferencesChanged(preferences);
// cleanup old
if (m_preferences) {
disconnect(m_preferences, &QmlJSCodeStylePreferences::currentValueChanged, this, nullptr);
disconnect(m_preferences, &QmlJSCodeStylePreferences::currentPreferencesChanged,
this, &FormatterSelectionWidget::slotCurrentPreferencesChanged);
}
m_preferences = preferences;
// fillup new
if (m_preferences) {
setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
connect(m_preferences, &QmlJSCodeStylePreferences::currentValueChanged, this, [this] {
setCodeStyleSettings(m_preferences->currentCodeStyleSettings());
});
connect(m_preferences, &QmlJSCodeStylePreferences::currentPreferencesChanged,
this, &FormatterSelectionWidget::slotCurrentPreferencesChanged);
}
}
void FormatterSelectionWidget::slotCurrentPreferencesChanged(
TextEditor::ICodeStylePreferences *preferences)
{
QmlJSCodeStylePreferences *current = dynamic_cast<QmlJSCodeStylePreferences *>(
preferences ? preferences->currentPreferences() : nullptr);
const bool enableWidgets = current && !current->isReadOnly();
setEnabled(enableWidgets);
}
void FormatterSelectionWidget::slotSettingsChanged()
{
QmlJSCodeStyleSettings settings = m_preferences
? m_preferences->currentCodeStyleSettings()
: QmlJSCodeStyleSettings::currentGlobalCodeStyle();
settings.formatter = static_cast<QmlJSCodeStyleSettings::Formatter>(m_formatterSelection.value());
emit settingsChanged(settings);
}
} // namespace QmlJSTools

View File

@@ -0,0 +1,51 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "qmljscodestylesettings.h"
#include <utils/aspects.h>
namespace QmlJSTools {
class QmlJSCodeStyleSettings;
class QmlCodeStyleWidgetBase : public QWidget
{
Q_OBJECT
public:
QmlCodeStyleWidgetBase(QWidget *parent = nullptr);
virtual ~QmlCodeStyleWidgetBase() = default;
enum Formatter {
Builtin,
QmlFormat,
Custom,
Count
};
virtual void setCodeStyleSettings(const QmlJSCodeStyleSettings &s) = 0;
virtual void setPreferences(QmlJSCodeStylePreferences *preferences) = 0;
virtual void slotCurrentPreferencesChanged(TextEditor::ICodeStylePreferences *preferences) = 0;
signals:
void settingsChanged(const QmlJSCodeStyleSettings &);
};
class FormatterSelectionWidget : public QmlCodeStyleWidgetBase
{
Q_OBJECT
public:
explicit FormatterSelectionWidget(QWidget *parent);
const Utils::SelectionAspect &selection() const { return m_formatterSelection; }
Utils::SelectionAspect &selection() { return m_formatterSelection; }
void setCodeStyleSettings(const QmlJSCodeStyleSettings &s) override;
void setPreferences(QmlJSCodeStylePreferences *preferences) override;
void slotCurrentPreferencesChanged(TextEditor::ICodeStylePreferences *preferences) override;
private:
void slotSettingsChanged();
Utils::SelectionAspect m_formatterSelection;
QmlJSCodeStylePreferences *m_preferences = nullptr;
};
} // namespace QmlJSTools

View File

@@ -25,8 +25,6 @@ QtcPlugin {
"qmljscodestylesettings.h",
"qmljscodestylesettingspage.cpp",
"qmljscodestylesettingspage.h",
"qmljscodestylesettingswidget.cpp",
"qmljscodestylesettingswidget.h",
"qmljsfunctionfilter.cpp",
"qmljsfunctionfilter.h",
"qmljsindenter.cpp",

View File

@@ -8,6 +8,7 @@
#include "qmljsindenter.h"
#include "qmljstoolsconstants.h"
#include "qmljstoolssettings.h"
#include "qmlformatsettings.h"
#include "qmljstoolstr.h"
#include <projectexplorer/project.h>
@@ -20,6 +21,7 @@
#include <texteditor/texteditorsettings.h>
#include <utils/id.h>
#include <utils/filepath.h>
#include <utils/mimeconstants.h>
#include <utils/mimeutils.h>
#include <utils/qtcassert.h>
@@ -166,9 +168,22 @@ QmlJSToolsSettings::QmlJSToolsSettings()
qtTabSettings.m_indentSize = 4;
qtTabSettings.m_continuationAlignBehavior = TabSettings::ContinuationAlignWithIndent;
qtCodeStyle->setTabSettings(qtTabSettings);
QmlJSCodeStyleSettings qtQmlJSSetings;
qtQmlJSSetings.lineLength = 80;
qtCodeStyle->setCodeStyleSettings(qtQmlJSSetings);
connect(&QmlFormatSettings::instance(), &QmlFormatSettings::qmlformatIniCreated, [](Utils::FilePath qmlformatIniPath) {
QmlJSCodeStyleSettings s;
s.lineLength = 80;
Utils::expected_str<QByteArray> fileContents = qmlformatIniPath.fileContents();
if (fileContents)
s.qmlformatIniContent = QString::fromUtf8(*qmlformatIniPath.fileContents());
auto builtInCodeStyles = TextEditorSettings::codeStylePool(
QmlJSTools::Constants::QML_JS_SETTINGS_ID)
->builtInCodeStyles();
for (auto codeStyle : builtInCodeStyles) {
if (auto qtCodeStyle = dynamic_cast<QmlJSCodeStylePreferences *>(codeStyle))
qtCodeStyle->setCodeStyleSettings(s);
}
});
pool->addCodeStyle(qtCodeStyle);
// default delegate for global preferences

View File

@@ -138,6 +138,7 @@ const char UNFOLD_RECURSIVELY[] = "TextEditor.UnfoldRecursively";
const char UNFOLD_ALL[] = "TextEditor.UnFoldAll";
const char AUTO_INDENT_SELECTION[] = "TextEditor.AutoIndentSelection";
const char AUTO_FORMAT_SELECTION[] = "TextEditor.AutoFormatSelection";
const char REFORMAT_FILE[] = "TextEditor.ReformatFile";
const char INCREASE_FONT_SIZE[] = "TextEditor.IncreaseFontSize";
const char DECREASE_FONT_SIZE[] = "TextEditor.DecreaseFontSize";
const char RESET_FONT_SIZE[] = "TextEditor.ResetFontSize";