aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/quickcontrols/stylekit/styles/Haze.qml10
-rw-r--r--src/labs/stylekit/Button.qml2
-rw-r--r--src/labs/stylekit/CMakeLists.txt2
-rw-r--r--src/labs/stylekit/CheckBox.qml2
-rw-r--r--src/labs/stylekit/ComboBox.qml3
-rw-r--r--src/labs/stylekit/ItemDelegate.qml2
-rw-r--r--src/labs/stylekit/RadioButton.qml2
-rw-r--r--src/labs/stylekit/SpinBox.qml2
-rw-r--r--src/labs/stylekit/Switch.qml2
-rw-r--r--src/labs/stylekit/TextField.qml8
-rw-r--r--src/labs/stylekit/qqstylekitfont.cpp379
-rw-r--r--src/labs/stylekit/qqstylekitfont_p.h49
-rw-r--r--src/labs/stylekit/qqstylekitreader.cpp26
-rw-r--r--src/labs/stylekit/qqstylekitreader_p.h9
-rw-r--r--src/labs/stylekit/qqstylekitstyle.cpp46
-rw-r--r--src/labs/stylekit/qqstylekitstyle_p.h5
-rw-r--r--src/labs/stylekit/qqstylekitstyleandthemebase.cpp (renamed from src/labs/stylekit/qqstylekitthemeproperties.cpp)8
-rw-r--r--src/labs/stylekit/qqstylekitstyleandthemebase_p.h (renamed from src/labs/stylekit/qqstylekitthemeproperties_p.h)12
-rw-r--r--src/labs/stylekit/qqstylekittheme.cpp2
-rw-r--r--src/labs/stylekit/qqstylekittheme_p.h5
-rw-r--r--src/qmlls/qqmlcodemodel_p.h3
-rw-r--r--src/qmlls/qqmlhighlightsupport.cpp126
-rw-r--r--src/qmlls/qqmlsemantictokens.cpp13
-rw-r--r--src/qmlls/qqmlsemantictokens_p.h5
-rw-r--r--tests/auto/qmlls/modules/tst_qmlls_modules.cpp16
-rw-r--r--tests/auto/qmlls/utils/data/highlights/highlightsShift.qml8
-rw-r--r--tests/auto/qmlls/utils/data/highlights/highlightsShift_consoleLog.qml8
-rw-r--r--tests/auto/qmlls/utils/data/highlights/highlightsShift_withComments.qml11
-rw-r--r--tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_consoleLog_del.qml8
-rw-r--r--tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del1.qml8
-rw-r--r--tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del2.qml8
-rw-r--r--tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del3.qml8
-rw-r--r--tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del4.qml6
-rw-r--r--tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del5.qml7
-rw-r--r--tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del6.qml6
-rw-r--r--tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_withComments_del.qml5
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp285
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_highlighting.h2
38 files changed, 761 insertions, 348 deletions
diff --git a/examples/quickcontrols/stylekit/styles/Haze.qml b/examples/quickcontrols/stylekit/styles/Haze.qml
index b3920f7c59..db58af43ad 100644
--- a/examples/quickcontrols/stylekit/styles/Haze.qml
+++ b/examples/quickcontrols/stylekit/styles/Haze.qml
@@ -7,6 +7,16 @@ import Qt.labs.StyleKit
Style {
id: style
+ fonts {
+ systemFont {
+ family: "Courier New"
+ pointSize: 12
+ }
+ textFieldFont {
+ bold: true
+ }
+ }
+
control {
// 'control' is the fallback for all the controls. Any properties that are not
// overridden by a specific control underneath will be read from here instead.
diff --git a/src/labs/stylekit/Button.qml b/src/labs/stylekit/Button.qml
index 734ff24d27..c4d705e09d 100644
--- a/src/labs/stylekit/Button.qml
+++ b/src/labs/stylekit/Button.qml
@@ -45,7 +45,7 @@ T.Button {
icon: control.icon
text: control.text
- font: control.font
+ font: styleReader.font // FIXME: should be inherited from control
color: styleReader.text.color
alignment: styleReader.text.alignment
}
diff --git a/src/labs/stylekit/CMakeLists.txt b/src/labs/stylekit/CMakeLists.txt
index b981bcac62..ed23c75934 100644
--- a/src/labs/stylekit/CMakeLists.txt
+++ b/src/labs/stylekit/CMakeLists.txt
@@ -26,7 +26,7 @@ qt_internal_add_qml_module(QtQuickStyleKit
qqstylekitstorage_p.h qqstylekitstorage.cpp
qqstylekitstyle_p.h qqstylekitstyle.cpp
qqstylekittheme_p.h qqstylekittheme.cpp
- qqstylekitthemeproperties_p.h qqstylekitthemeproperties.cpp
+ qqstylekitstyleandthemebase_p.h qqstylekitstyleandthemebase.cpp
qqstylekitcustomtheme_p.h qqstylekitcustomtheme.cpp
qqstylekitvariation_p.h qqstylekitvariation.cpp
DEFINES
diff --git a/src/labs/stylekit/CheckBox.qml b/src/labs/stylekit/CheckBox.qml
index fa5a43f990..af55697bd1 100644
--- a/src/labs/stylekit/CheckBox.qml
+++ b/src/labs/stylekit/CheckBox.qml
@@ -75,7 +75,7 @@ T.CheckBox {
contentItem: CheckLabel {
text: control.text
- font: control.font
+ font: styleReader.font
color: styleReader.text.color
horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
diff --git a/src/labs/stylekit/ComboBox.qml b/src/labs/stylekit/ComboBox.qml
index 2592355a5d..bcb6b89f56 100644
--- a/src/labs/stylekit/ComboBox.qml
+++ b/src/labs/stylekit/ComboBox.qml
@@ -88,7 +88,8 @@ T.ComboBox {
height: indicatorItem.height
}
- contentItem: T.TextField {
+ contentItem: TextInput {
+ font: styleReader.font
text: control.editable ? control.editText : control.displayText
enabled: control.editable
autoScroll: control.editable
diff --git a/src/labs/stylekit/ItemDelegate.qml b/src/labs/stylekit/ItemDelegate.qml
index feca0eafe3..207d22c3b4 100644
--- a/src/labs/stylekit/ItemDelegate.qml
+++ b/src/labs/stylekit/ItemDelegate.qml
@@ -46,7 +46,7 @@ T.ItemDelegate {
display: control.display
icon: control.icon
text: control.text
- font: control.font
+ font: styleReader.font
color: styleReader.text.color
alignment: styleReader.text.alignment
}
diff --git a/src/labs/stylekit/RadioButton.qml b/src/labs/stylekit/RadioButton.qml
index 2ef5b62385..ad5a6d0bed 100644
--- a/src/labs/stylekit/RadioButton.qml
+++ b/src/labs/stylekit/RadioButton.qml
@@ -75,7 +75,7 @@ T.RadioButton {
contentItem: CheckLabel {
text: control.text
- font: control.font
+ font: styleReader.font
color: styleReader.text.color
horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
diff --git a/src/labs/stylekit/SpinBox.qml b/src/labs/stylekit/SpinBox.qml
index b5476df13a..88eed43261 100644
--- a/src/labs/stylekit/SpinBox.qml
+++ b/src/labs/stylekit/SpinBox.qml
@@ -103,7 +103,7 @@ T.SpinBox {
z: 2
text: control.displayText
- font: control.font
+ font: styleReader.font
selectionColor: control.palette.highlight
selectedTextColor: control.palette.highlightedText
color: styleReader.text.color
diff --git a/src/labs/stylekit/Switch.qml b/src/labs/stylekit/Switch.qml
index 7202f3b923..8ce306bbe4 100644
--- a/src/labs/stylekit/Switch.qml
+++ b/src/labs/stylekit/Switch.qml
@@ -85,7 +85,7 @@ T.Switch {
contentItem: CheckLabel {
text: control.text
- font: control.font
+ font: styleReader.font
color: styleReader.text.color
horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
diff --git a/src/labs/stylekit/TextField.qml b/src/labs/stylekit/TextField.qml
index 0cdf31852b..f148b52784 100644
--- a/src/labs/stylekit/TextField.qml
+++ b/src/labs/stylekit/TextField.qml
@@ -22,6 +22,14 @@ T.TextField {
rightPadding: styleReader.rightPadding
bottomPadding: styleReader.bottomPadding
+ // FIXME: Should work when assigned to control.font directly
+ font.family: styleReader.font.family
+ font.pointSize: styleReader.font.pointSize
+ font.weight: styleReader.font.weight
+ font.italic: styleReader.font.italic
+ font.underline: styleReader.font.underline
+ font.bold: styleReader.font.bold
+
color: styleReader.text.color
verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
diff --git a/src/labs/stylekit/qqstylekitfont.cpp b/src/labs/stylekit/qqstylekitfont.cpp
index 2db4eae309..ff1b3cec8d 100644
--- a/src/labs/stylekit/qqstylekitfont.cpp
+++ b/src/labs/stylekit/qqstylekitfont.cpp
@@ -10,222 +10,185 @@ QQStyleKitFont::QQStyleKitFont(QObject *parent)
{
}
-#define STYLEKIT_FONT_GETTER(propertyName) \
- QFont QQStyleKitFont::propertyName() const \
- { \
- if (!m_##propertyName) { \
- m_##propertyName.reset(new QFont()); \
- } \
- return *m_##propertyName; \
- }
-
-STYLEKIT_FONT_GETTER(systemFont)
-STYLEKIT_FONT_GETTER(buttonFont)
-STYLEKIT_FONT_GETTER(checkboxFont)
-STYLEKIT_FONT_GETTER(comboBoxFont)
-STYLEKIT_FONT_GETTER(groupBoxFont)
-STYLEKIT_FONT_GETTER(itemViewFont)
-STYLEKIT_FONT_GETTER(labelFont)
-STYLEKIT_FONT_GETTER(listViewFont)
-STYLEKIT_FONT_GETTER(menuFont)
-STYLEKIT_FONT_GETTER(menuBarFont)
-STYLEKIT_FONT_GETTER(radioButtonFont)
-STYLEKIT_FONT_GETTER(spinBoxFont)
-STYLEKIT_FONT_GETTER(switchControlFont)
-STYLEKIT_FONT_GETTER(tabBarFont)
-STYLEKIT_FONT_GETTER(textAreaFont)
-STYLEKIT_FONT_GETTER(textFieldFont)
-STYLEKIT_FONT_GETTER(toolBarFont)
-STYLEKIT_FONT_GETTER(toolTipFont)
-STYLEKIT_FONT_GETTER(tumblerFont)
-
-void QQStyleKitFont::setSystemFont(const QFont &font)
-{
- if (!m_systemFont)
- m_systemFont.reset(new QFont(font));
- else
- *m_systemFont = font;
- emit systemFontChanged();
-}
-
-void QQStyleKitFont::setButtonFont(const QFont &font)
-{
- if (!m_buttonFont) {
- m_buttonFont.reset(new QFont(font));
- } else {
- *m_buttonFont = font;
- }
- emit buttonFontChanged();
-}
-
-void QQStyleKitFont::setCheckboxFont(const QFont &font)
-{
- if (!m_checkboxFont) {
- m_checkboxFont.reset(new QFont(font));
- } else {
- *m_checkboxFont = font;
- }
- emit checkboxFontChanged();
-}
-
-void QQStyleKitFont::setComboBoxFont(const QFont &font)
-{
- if (!m_comboBoxFont) {
- m_comboBoxFont.reset(new QFont(font));
- } else {
- *m_comboBoxFont = font;
- }
- emit comboBoxFontChanged();
-}
-
-void QQStyleKitFont::setGroupBoxFont(const QFont &font)
-{
- if (!m_groupBoxFont) {
- m_groupBoxFont.reset(new QFont(font));
- } else {
- *m_groupBoxFont = font;
- }
- emit groupBoxFontChanged();
-}
+#define DEFINE_FONT_GETTER(scopeName, scopeEnum) \
+ QFont QQStyleKitFont::scopeName##Font() const \
+ { \
+ return fontForScope(QQuickTheme::scopeEnum); \
+ }
+
+DEFINE_FONT_GETTER(system, System)
+DEFINE_FONT_GETTER(button, Button)
+DEFINE_FONT_GETTER(checkBox, CheckBox)
+DEFINE_FONT_GETTER(comboBox, ComboBox)
+DEFINE_FONT_GETTER(groupBox, GroupBox)
+DEFINE_FONT_GETTER(itemView, ItemView)
+DEFINE_FONT_GETTER(label, Label)
+DEFINE_FONT_GETTER(listView, ListView)
+DEFINE_FONT_GETTER(menu, Menu)
+DEFINE_FONT_GETTER(menuBar, MenuBar)
+DEFINE_FONT_GETTER(radioButton, RadioButton)
+DEFINE_FONT_GETTER(spinBox, SpinBox)
+DEFINE_FONT_GETTER(switchControl, Switch)
+DEFINE_FONT_GETTER(tabBar, TabBar)
+DEFINE_FONT_GETTER(textArea, TextArea)
+DEFINE_FONT_GETTER(textField, TextField)
+DEFINE_FONT_GETTER(toolBar, ToolBar)
+DEFINE_FONT_GETTER(toolTip, ToolTip)
+DEFINE_FONT_GETTER(tumbler, Tumbler)
+
+#define DEFINE_FONT_SETTER(scopeName, scopeEnum, signal) \
+ void QQStyleKitFont::set##scopeName##Font(const QFont &font) \
+ { \
+ setFontForScope(QQuickTheme::scopeEnum, font, &QQStyleKitFont::signal); \
+ }
+
+DEFINE_FONT_SETTER(System, System, systemFontChanged)
+DEFINE_FONT_SETTER(Button, Button, buttonFontChanged)
+DEFINE_FONT_SETTER(CheckBox, CheckBox, checkBoxFontChanged)
+DEFINE_FONT_SETTER(ComboBox, ComboBox, comboBoxFontChanged)
+DEFINE_FONT_SETTER(GroupBox, GroupBox, groupBoxFontChanged)
+DEFINE_FONT_SETTER(ItemView, ItemView, itemViewFontChanged)
+DEFINE_FONT_SETTER(Label, Label, labelFontChanged)
+DEFINE_FONT_SETTER(ListView, ListView, listViewFontChanged)
+DEFINE_FONT_SETTER(Menu, Menu, menuFontChanged)
+DEFINE_FONT_SETTER(MenuBar, MenuBar, menuBarFontChanged)
+DEFINE_FONT_SETTER(RadioButton, RadioButton, radioButtonFontChanged)
+DEFINE_FONT_SETTER(SpinBox, SpinBox, spinBoxFontChanged)
+DEFINE_FONT_SETTER(SwitchControl, Switch, switchControlFontChanged)
+DEFINE_FONT_SETTER(TabBar, TabBar, tabBarFontChanged)
+DEFINE_FONT_SETTER(TextArea, TextArea, textAreaFontChanged)
+DEFINE_FONT_SETTER(TextField, TextField, textFieldFontChanged)
+DEFINE_FONT_SETTER(ToolBar, ToolBar, toolBarFontChanged)
+DEFINE_FONT_SETTER(ToolTip, ToolTip, toolTipFontChanged)
+DEFINE_FONT_SETTER(Tumbler, Tumbler, tumblerFontChanged)
+
+void QQStyleKitFont::setFontForScope(QQuickTheme::Scope scope, const QFont &font, void (QQStyleKitFont::*signal)())
+{
+ const int index = int(scope);
+ if (isSet(scope) && m_local[index] == font)
+ return;
+
+ QFont local = font;
+ // TODO: Figure out resolve mask to set here
+
+ m_local[index] = font;
+ markSet(scope);
+
+ m_effectiveDirty = true;
+
+ emit (this->*signal)();
+}
+
+// The fallback font is used to resolve unset fonts
+// The theme fonts fallback to the style fonts and
+// style fonts fallback to the fallback style fonts
+QQStyleKitFont *QQStyleKitFont::fallbackFont() const
+{
+ return m_fallback;
+}
+
+void QQStyleKitFont::setFallbackFont(QQStyleKitFont *fallback)
+{
+ if (m_fallback == fallback)
+ return;
+
+ if (m_fallback)
+ disconnect(m_fallback, nullptr, this, nullptr);
+
+ m_fallback = fallback;
+
+ markEffectiveDirty();
+ ensureEffectiveUpToDate();
+
+ if (m_fallback) {
+ auto makeHandler = [this](void (QQStyleKitFont::*signal)()) {
+ return [this, signal] {
+ markEffectiveDirty();
+ emit (this->*signal)();
+ };
+ };
+ connect(m_fallback, &QQStyleKitFont::systemFontChanged, this,
+ makeHandler(&QQStyleKitFont::systemFontChanged));
+ connect(m_fallback, &QQStyleKitFont::buttonFontChanged, this,
+ makeHandler(&QQStyleKitFont::buttonFontChanged));
+ connect(m_fallback, &QQStyleKitFont::checkBoxFontChanged, this,
+ makeHandler(&QQStyleKitFont::checkBoxFontChanged));
+ connect(m_fallback, &QQStyleKitFont::comboBoxFontChanged, this,
+ makeHandler(&QQStyleKitFont::comboBoxFontChanged));
+ connect(m_fallback, &QQStyleKitFont::groupBoxFontChanged, this,
+ makeHandler(&QQStyleKitFont::groupBoxFontChanged));
+ connect(m_fallback, &QQStyleKitFont::itemViewFontChanged, this,
+ makeHandler(&QQStyleKitFont::itemViewFontChanged));
+ connect(m_fallback, &QQStyleKitFont::labelFontChanged, this,
+ makeHandler(&QQStyleKitFont::labelFontChanged));
+ connect(m_fallback, &QQStyleKitFont::listViewFontChanged, this,
+ makeHandler(&QQStyleKitFont::listViewFontChanged));
+ connect(m_fallback, &QQStyleKitFont::menuFontChanged, this,
+ makeHandler(&QQStyleKitFont::menuFontChanged));
+ connect(m_fallback, &QQStyleKitFont::menuBarFontChanged, this,
+ makeHandler(&QQStyleKitFont::menuBarFontChanged));
+ connect(m_fallback, &QQStyleKitFont::radioButtonFontChanged, this,
+ makeHandler(&QQStyleKitFont::radioButtonFontChanged));
+ connect(m_fallback, &QQStyleKitFont::spinBoxFontChanged, this,
+ makeHandler(&QQStyleKitFont::spinBoxFontChanged));
+ connect(m_fallback, &QQStyleKitFont::switchControlFontChanged, this,
+ makeHandler(&QQStyleKitFont::switchControlFontChanged));
+ connect(m_fallback, &QQStyleKitFont::tabBarFontChanged, this,
+ makeHandler(&QQStyleKitFont::tabBarFontChanged));
+ connect(m_fallback, &QQStyleKitFont::textAreaFontChanged, this,
+ makeHandler(&QQStyleKitFont::textAreaFontChanged));
+ connect(m_fallback, &QQStyleKitFont::textFieldFontChanged, this,
+ makeHandler(&QQStyleKitFont::textFieldFontChanged));
+ connect(m_fallback, &QQStyleKitFont::toolBarFontChanged, this,
+ makeHandler(&QQStyleKitFont::toolBarFontChanged));
+ connect(m_fallback, &QQStyleKitFont::toolTipFontChanged, this,
+ makeHandler(&QQStyleKitFont::toolTipFontChanged));
+ connect(m_fallback, &QQStyleKitFont::tumblerFontChanged, this,
+ makeHandler(&QQStyleKitFont::tumblerFontChanged));
+ }
+ emit fallbackFontChanged();
+}
+
+QFont QQStyleKitFont::fontForScope(QQuickTheme::Scope scope) const
+{
+ ensureEffectiveUpToDate();
+ return m_effective[int(scope)];
+}
-void QQStyleKitFont::setItemViewFont(const QFont &font)
+void QQStyleKitFont::ensureEffectiveUpToDate() const
{
- if (!m_itemViewFont) {
- m_itemViewFont.reset(new QFont(font));
- } else {
- *m_itemViewFont = font;
- }
- emit itemViewFontChanged();
-}
+ if (!m_effectiveDirty)
+ return;
-void QQStyleKitFont::setLabelFont(const QFont &font)
-{
- if (!m_labelFont) {
- m_labelFont.reset(new QFont(font));
- } else {
- *m_labelFont = font;
- }
- emit labelFontChanged();
-}
+ const int sysIdx = int(QQuickTheme::System);
-void QQStyleKitFont::setListViewFont(const QFont &font)
-{
- if (!m_listViewFont) {
- m_listViewFont.reset(new QFont(font));
- } else {
- *m_listViewFont = font;
- }
- emit listViewFontChanged();
-}
+ {
+ const QFont localSys = isSet(QQuickTheme::System) ? m_local[sysIdx] : QFont();
+ const QFont fbSys = m_fallback ? m_fallback->fontForScope(QQuickTheme::System) : QFont();
+ // TODO: Resolve mask?
+ m_effective[sysIdx] = localSys.resolve(fbSys);
-void QQStyleKitFont::setMenuFont(const QFont &font)
-{
- if (!m_menuFont) {
- m_menuFont.reset(new QFont(font));
- } else {
- *m_menuFont = font;
}
- emit menuFontChanged();
-}
-void QQStyleKitFont::setMenuBarFont(const QFont &font)
-{
- if (!m_menuBarFont) {
- m_menuBarFont.reset(new QFont(font));
- } else {
- *m_menuBarFont = font;
- }
- emit menuBarFontChanged();
-}
+ const QFont systemEff = m_effective[sysIdx];
+ const QFont fallbackSystem = m_fallback ? m_fallback->fontForScope(QQuickTheme::System) : QFont();
-void QQStyleKitFont::setRadioButtonFont(const QFont &font)
-{
- if (!m_radioButtonFont) {
- m_radioButtonFont.reset(new QFont(font));
- } else {
- *m_radioButtonFont = font;
- }
- emit radioButtonFontChanged();
-}
-
-void QQStyleKitFont::setSpinBoxFont(const QFont &font)
-{
- if (!m_spinBoxFont) {
- m_spinBoxFont.reset(new QFont(font));
- } else {
- *m_spinBoxFont = font;
- }
- emit spinBoxFontChanged();
-}
-
-void QQStyleKitFont::setSwitchControlFont(const QFont &font)
-{
- if (!m_switchControlFont) {
- m_switchControlFont.reset(new QFont(font));
- } else {
- *m_switchControlFont = font;
- }
- emit switchControlFontChanged();
-}
-
-void QQStyleKitFont::setTabBarFont(const QFont &font)
-{
- if (!m_tabBarFont) {
- m_tabBarFont.reset(new QFont(font));
- } else {
- *m_tabBarFont = font;
- }
- emit tabBarFontChanged();
-}
-
-void QQStyleKitFont::setTextAreaFont(const QFont &font)
-{
- if (!m_textAreaFont) {
- m_textAreaFont.reset(new QFont(font));
- } else {
- *m_textAreaFont = font;
- }
- emit textAreaFontChanged();
-}
-
-void QQStyleKitFont::setTextFieldFont(const QFont &font)
-{
- if (!m_textFieldFont) {
- m_textFieldFont.reset(new QFont(font));
- } else {
- *m_textFieldFont = font;
- }
- emit textFieldFontChanged();
-}
-
-void QQStyleKitFont::setToolBarFont(const QFont &font)
-{
- if (!m_toolBarFont) {
- m_toolBarFont.reset(new QFont(font));
- } else {
- *m_toolBarFont = font;
- }
- emit toolBarFontChanged();
-}
-
-void QQStyleKitFont::setToolTipFont(const QFont &font)
-{
- if (!m_toolTipFont) {
- m_toolTipFont.reset(new QFont(font));
- } else {
- *m_toolTipFont = font;
- }
- emit toolTipFontChanged();
-}
-
-void QQStyleKitFont::setTumblerFont(const QFont &font)
-{
- if (!m_tumblerFont) {
- m_tumblerFont.reset(new QFont(font));
- } else {
- *m_tumblerFont = font;
- }
- emit tumblerFontChanged();
+ // Scopes: localScope > localSystem > fallbackScope > fallbackSystem
+ for (int i = 0; i < NScopes; ++i) {
+ if (i == sysIdx)
+ continue;
+
+ const QQuickTheme::Scope scope = static_cast<QQuickTheme::Scope>(i);
+ const QFont localRole = isSet(scope) ? m_local[i] : QFont();
+ const QFont fallbackRole = m_fallback ? m_fallback->fontForScope(scope) : QFont();
+
+ QFont fallbackLayer = fallbackRole.resolve(fallbackSystem);
+ QFont base = systemEff.resolve(fallbackLayer);
+ // TODO: Resolve mask?
+ m_effective[i] = localRole.resolve(base);
+ }
+
+ m_effectiveDirty = false;
}
QT_END_NAMESPACE
diff --git a/src/labs/stylekit/qqstylekitfont_p.h b/src/labs/stylekit/qqstylekitfont_p.h
index eeadcdc0f4..41854222d8 100644
--- a/src/labs/stylekit/qqstylekitfont_p.h
+++ b/src/labs/stylekit/qqstylekitfont_p.h
@@ -17,6 +17,7 @@
//
#include <QtQml/QtQml>
+#include <QtQuickTemplates2/private/qquicktheme_p.h>
#include <QtGui/qfont.h>
QT_BEGIN_NAMESPACE
@@ -26,7 +27,7 @@ class QQStyleKitFont : public QObject
Q_OBJECT
Q_PROPERTY(QFont systemFont READ systemFont WRITE setSystemFont NOTIFY systemFontChanged FINAL)
Q_PROPERTY(QFont buttonFont READ buttonFont WRITE setButtonFont NOTIFY buttonFontChanged FINAL)
- Q_PROPERTY(QFont checkboxFont READ checkboxFont WRITE setCheckboxFont NOTIFY checkboxFontChanged FINAL)
+ Q_PROPERTY(QFont checkBoxFont READ checkBoxFont WRITE setCheckBoxFont NOTIFY checkBoxFontChanged FINAL)
Q_PROPERTY(QFont comboBoxFont READ comboBoxFont WRITE setComboBoxFont NOTIFY comboBoxFontChanged FINAL)
Q_PROPERTY(QFont groupBoxFont READ groupBoxFont WRITE setGroupBoxFont NOTIFY groupBoxFontChanged FINAL)
Q_PROPERTY(QFont itemViewFont READ itemViewFont WRITE setItemViewFont NOTIFY itemViewFontChanged FINAL)
@@ -54,8 +55,8 @@ public:
QFont buttonFont() const;
void setButtonFont(const QFont &font);
- QFont checkboxFont() const;
- void setCheckboxFont(const QFont &font);
+ QFont checkBoxFont() const;
+ void setCheckBoxFont(const QFont &font);
QFont comboBoxFont() const;
void setComboBoxFont(const QFont &font);
@@ -105,10 +106,15 @@ public:
QFont tumblerFont() const;
void setTumblerFont(const QFont &font);
+ QQStyleKitFont *fallbackFont() const;
+ void setFallbackFont(QQStyleKitFont *fallback);
+
+ QFont fontForScope(QQuickTheme::Scope scope) const;
+
signals:
void systemFontChanged();
void buttonFontChanged();
- void checkboxFontChanged();
+ void checkBoxFontChanged();
void comboBoxFontChanged();
void groupBoxFontChanged();
void itemViewFontChanged();
@@ -125,29 +131,26 @@ signals:
void toolBarFontChanged();
void toolTipFontChanged();
void tumblerFontChanged();
+ void fallbackFontChanged();
private:
Q_DISABLE_COPY(QQStyleKitFont)
- mutable std::unique_ptr<QFont> m_systemFont;
- mutable std::unique_ptr<QFont> m_buttonFont;
- mutable std::unique_ptr<QFont> m_checkboxFont;
- mutable std::unique_ptr<QFont> m_comboBoxFont;
- mutable std::unique_ptr<QFont> m_groupBoxFont;
- mutable std::unique_ptr<QFont> m_itemViewFont;
- mutable std::unique_ptr<QFont> m_labelFont;
- mutable std::unique_ptr<QFont> m_listViewFont;
- mutable std::unique_ptr<QFont> m_menuFont;
- mutable std::unique_ptr<QFont> m_menuBarFont;
- mutable std::unique_ptr<QFont> m_radioButtonFont;
- mutable std::unique_ptr<QFont> m_spinBoxFont;
- mutable std::unique_ptr<QFont> m_switchControlFont;
- mutable std::unique_ptr<QFont> m_tabBarFont;
- mutable std::unique_ptr<QFont> m_textAreaFont;
- mutable std::unique_ptr<QFont> m_textFieldFont;
- mutable std::unique_ptr<QFont> m_toolBarFont;
- mutable std::unique_ptr<QFont> m_toolTipFont;
- mutable std::unique_ptr<QFont> m_tumblerFont;
+ void ensureEffectiveUpToDate() const;
+ void markEffectiveDirty() { m_effectiveDirty = true; }
+
+ void setFontForScope(QQuickTheme::Scope scope, const QFont &font, void (QQStyleKitFont::*signal)());
+
+ bool isSet(QQuickTheme::Scope scope) const { return (m_setMask & (1u << int(scope))) != 0; }
+ void markSet(QQuickTheme::Scope scope) { m_setMask |= (1u << int(scope)); }
+
+ static const int NScopes = QQuickTheme::Tumbler + 1;
+ QFont m_local[NScopes];
+ mutable QFont m_effective[NScopes];
+ quint32 m_setMask = 0;
+ mutable bool m_effectiveDirty = true;
+
+ QQStyleKitFont *m_fallback = nullptr;
};
QT_END_NAMESPACE
diff --git a/src/labs/stylekit/qqstylekitreader.cpp b/src/labs/stylekit/qqstylekitreader.cpp
index f6a2b974fd..71ea689377 100644
--- a/src/labs/stylekit/qqstylekitreader.cpp
+++ b/src/labs/stylekit/qqstylekitreader.cpp
@@ -243,6 +243,8 @@ void QQStyleKitReader::updateControl()
default:
Q_UNREACHABLE();
}
+
+ setFont(style->fontForReader(this));
}
void QQStyleKitReader::resetAll()
@@ -251,9 +253,19 @@ void QQStyleKitReader::resetAll()
reader->m_effectiveVariationsDirty = true;
reader->clearLocalStorage();
reader->emitChangedForAllStyleProperties();
+ reader->updateFontFromTheme();
}
}
+void QQStyleKitReader::updateFontFromTheme()
+{
+ const QQStyleKitStyle *style = QQStyleKitStyle::current();
+ if (!style || !style->loaded())
+ return;
+
+ setFont(style->fontForReader(this));
+}
+
void QQStyleKitReader::populateLocalStorage()
{
if (!m_storage.isEmpty())
@@ -485,6 +497,20 @@ void QQStyleKitReader::setPalette(QQuickPalette *palette)
emitChangedForAllStyleProperties();
}
+QFont QQStyleKitReader::font() const
+{
+ return m_font;
+}
+
+void QQStyleKitReader::setFont(const QFont &font)
+{
+ if (m_font == font)
+ return;
+
+ m_font = font;
+ emit fontChanged();
+}
+
QQStyleKitControlProperties *QQStyleKitReader::global() const
{
return &const_cast<QQStyleKitReader *>(this)->m_global;
diff --git a/src/labs/stylekit/qqstylekitreader_p.h b/src/labs/stylekit/qqstylekitreader_p.h
index 860c12855f..33257141a9 100644
--- a/src/labs/stylekit/qqstylekitreader_p.h
+++ b/src/labs/stylekit/qqstylekitreader_p.h
@@ -21,6 +21,7 @@
#include "qqstylekitglobal_p.h"
#include "qqstylekitcontrolproperties_p.h"
+#include "qqstylekitfont_p.h"
#include "qqstylekitstorage_p.h"
QT_BEGIN_NAMESPACE
@@ -40,6 +41,7 @@ class QQStyleKitReader : public QQStyleKitControlProperties
Q_PROPERTY(bool pressed READ pressed WRITE setPressed NOTIFY pressedChanged FINAL)
Q_PROPERTY(bool vertical READ vertical WRITE setVertical NOTIFY verticalChanged FINAL)
Q_PROPERTY(bool highlighted READ highlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL)
+ Q_PROPERTY(QFont font READ font NOTIFY fontChanged FINAL)
Q_PROPERTY(QQuickPalette *palette READ palette WRITE setPalette NOTIFY paletteChanged FINAL)
Q_PROPERTY(QQStyleKitControlProperties *global READ global CONSTANT FINAL)
@@ -111,6 +113,9 @@ public:
bool highlighted() const;
void setHighlighted(bool highlighted);
+ QFont font() const;
+ void setFont(const QFont &font);
+
QQStyleKitControlProperties *global() const;
QVariant readStyleProperty(PropertyStorageId key) const;
@@ -123,6 +128,8 @@ public:
static bool transitionEnabled();
static void resetAll();
+ void updateFontFromTheme();
+
static QList<QQStyleKitReader *> s_allReaders;
signals:
@@ -137,6 +144,7 @@ signals:
void paletteChanged();
void verticalChanged();
void highlightedChanged();
+ void fontChanged();
private:
void updateControl();
@@ -163,6 +171,7 @@ private:
bool m_effectiveVariationsDirty: 1;
QQuickPalette m_palette;
+ QFont m_font;
mutable QQStyleKitPropertyStorage m_storage;
AlternateState m_alternateState = AlternateState::Alternate1;
QQSK::State m_state = QQSK::StateFlag::NoState;
diff --git a/src/labs/stylekit/qqstylekitstyle.cpp b/src/labs/stylekit/qqstylekitstyle.cpp
index 624294fc03..e15fa8674d 100644
--- a/src/labs/stylekit/qqstylekitstyle.cpp
+++ b/src/labs/stylekit/qqstylekitstyle.cpp
@@ -21,7 +21,7 @@ static const QString kLight = "Light"_L1;
static const QString kDark = "Dark"_L1;
QQStyleKitStyle::QQStyleKitStyle(QObject *parent)
- : QQStyleKitControls(parent)
+ : QQStyleKitStyleAndThemeBase(parent)
, m_themeName(kSystem)
{
}
@@ -62,6 +62,12 @@ void QQStyleKitStyle::setFallbackStyle(QQStyleKitStyle *fallbackStyle)
m_fallbackStyle = fallbackStyle;
emit fallbackStyleChanged();
+
+ if (fonts())
+ fonts()->setFallbackFont(m_fallbackStyle ? m_fallbackStyle->fonts() : nullptr);
+
+ if (m_theme && m_theme->fonts())
+ m_theme->fonts()->setFallbackFont(fonts());
}
void QQStyleKitStyle::setLight(QQmlComponent *lightTheme)
@@ -234,8 +240,14 @@ void QQStyleKitStyle::recreateTheme()
m_theme->setParent(this);
}
+ if (m_theme && m_theme->fonts())
+ m_theme->fonts()->setFallbackFont(fonts());
if (this == current()) {
m_theme->updateQuickTheme();
+ if (m_theme->fonts())
+ m_theme->fonts()->setFallbackFont(fonts());
+ if (fonts())
+ fonts()->setFallbackFont(m_fallbackStyle ? m_fallbackStyle->fonts() : nullptr);
QQStyleKitReader::resetAll();
}
@@ -247,6 +259,38 @@ QQStyleKitStyle* QQStyleKitStyle::current()
return QQStyleKit::qmlAttachedProperties()->style();
}
+QFont QQStyleKitStyle::fontForReader(QQStyleKitReader *reader) const
+{
+ switch (reader->type()) {
+ case QQStyleKitReader::ControlType::Control:
+ return m_theme->fonts()->systemFont();
+ case QQStyleKitReader::ControlType::AbstractButton:
+ case QQStyleKitReader::ControlType::Button:
+ case QQStyleKitReader::ControlType::FlatButton: {
+ return m_theme->fonts()->buttonFont();
+ }
+ case QQStyleKitReader::ControlType::CheckBox:
+ return m_theme->fonts()->checkBoxFont();
+ case QQStyleKitReader::ControlType::ComboBox:
+ return m_theme->fonts()->comboBoxFont();
+ case QQStyleKitReader::ControlType::RadioButton:
+ return m_theme->fonts()->radioButtonFont();
+ case QQStyleKitReader::ControlType::SpinBox:
+ return m_theme->fonts()->spinBoxFont();
+ case QQStyleKitReader::ControlType::SwitchControl:
+ return m_theme->fonts()->switchControlFont();
+ case QQStyleKitReader::ControlType::TextInput:
+ case QQStyleKitReader::ControlType::TextField:
+ return m_theme->fonts()->textFieldFont();
+ case QQStyleKitReader::ControlType::TextArea:
+ return m_theme->fonts()->textAreaFont();
+ case QQStyleKitReader::ControlType::ItemDelegate:
+ return m_theme->fonts()->itemViewFont();
+ default:
+ return m_theme->fonts()->systemFont();
+ }
+}
+
bool QQStyleKitStyle::loaded() const
{
/* Before both the style and theme has completed loading
diff --git a/src/labs/stylekit/qqstylekitstyle_p.h b/src/labs/stylekit/qqstylekitstyle_p.h
index ac560f7b88..2aba7ddbf5 100644
--- a/src/labs/stylekit/qqstylekitstyle_p.h
+++ b/src/labs/stylekit/qqstylekitstyle_p.h
@@ -20,6 +20,7 @@
#include "qqstylekitcontrols_p.h"
#include "qqstylekitcustomtheme_p.h"
#include "qqstylekitdebug_p.h"
+#include "qqstylekitstyleandthemebase_p.h"
#include <QtQml/QtQml>
#include <QtQuickTemplates2/private/qquickdeferredpointer_p_p.h>
@@ -29,7 +30,7 @@ QT_BEGIN_NAMESPACE
class QQStyleKitTheme;
class QQStyleKitPropertyResolver;
-class QQStyleKitStyle : public QQStyleKitControls
+class QQStyleKitStyle : public QQStyleKitStyleAndThemeBase
{
Q_OBJECT
Q_PROPERTY(QQuickPalette *palette READ palette NOTIFY paletteChanged FINAL)
@@ -76,6 +77,8 @@ public:
static QQStyleKitStyle *current();
+ QFont fontForReader(QQStyleKitReader *reader) const;
+
// For now, used by qqcontrolstowidgetstyle
Q_INVOKABLE QList<QObject *> customThemesAsList();
diff --git a/src/labs/stylekit/qqstylekitthemeproperties.cpp b/src/labs/stylekit/qqstylekitstyleandthemebase.cpp
index 7d342c563b..6ff18b26cc 100644
--- a/src/labs/stylekit/qqstylekitthemeproperties.cpp
+++ b/src/labs/stylekit/qqstylekitstyleandthemebase.cpp
@@ -1,22 +1,22 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include "qqstylekitthemeproperties_p.h"
+#include "qqstylekitstyleandthemebase_p.h"
QT_BEGIN_NAMESPACE
-QQStyleKitThemeProperties::QQStyleKitThemeProperties(QObject *parent)
+QQStyleKitStyleAndThemeBase::QQStyleKitStyleAndThemeBase(QObject *parent)
: QQStyleKitControls(parent)
{
}
-QQStyleKitFont *QQStyleKitThemeProperties::fonts()
+QQStyleKitFont *QQStyleKitStyleAndThemeBase::fonts()
{
return &m_fonts;
}
QT_END_NAMESPACE
-#include "moc_qqstylekitthemeproperties_p.cpp"
+#include "moc_qqstylekitstyleandthemebase_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitthemeproperties_p.h b/src/labs/stylekit/qqstylekitstyleandthemebase_p.h
index 5d8985441b..c59fde77c8 100644
--- a/src/labs/stylekit/qqstylekitthemeproperties_p.h
+++ b/src/labs/stylekit/qqstylekitstyleandthemebase_p.h
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
-#ifndef QQSTYLEKITTHEMEPROPERTIES_P_H
-#define QQSTYLEKITTHEMEPROPERTIES_P_H
+#ifndef QQSTYLEKITTSTYLEANDTHEMEBASE_P_H
+#define QQSTYLEKITTSTYLEANDTHEMEBASE_P_H
//
// W A R N I N G
@@ -22,7 +22,7 @@
QT_BEGIN_NAMESPACE
-class QQStyleKitThemeProperties : public QQStyleKitControls
+class QQStyleKitStyleAndThemeBase : public QQStyleKitControls
{
Q_OBJECT
Q_PROPERTY(QQStyleKitFont *fonts READ fonts NOTIFY fontsChanged FINAL)
@@ -31,7 +31,7 @@ class QQStyleKitThemeProperties : public QQStyleKitControls
QML_NAMED_ELEMENT(ThemeProperties)
public:
- QQStyleKitThemeProperties(QObject *parent = nullptr);
+ QQStyleKitStyleAndThemeBase(QObject *parent = nullptr);
QQStyleKitFont *fonts();
@@ -39,11 +39,11 @@ signals:
void fontsChanged();
private:
- Q_DISABLE_COPY(QQStyleKitThemeProperties)
+ Q_DISABLE_COPY(QQStyleKitStyleAndThemeBase)
QQStyleKitFont m_fonts;
};
QT_END_NAMESPACE
-#endif // QQSTYLEKITTHEMEPROPERTIES_P_H
+#endif // QQSTYLEKITTSTYLEANDTHEMEBASE_P_H
diff --git a/src/labs/stylekit/qqstylekittheme.cpp b/src/labs/stylekit/qqstylekittheme.cpp
index 08cec449c9..6996f299cf 100644
--- a/src/labs/stylekit/qqstylekittheme.cpp
+++ b/src/labs/stylekit/qqstylekittheme.cpp
@@ -12,7 +12,7 @@
QT_BEGIN_NAMESPACE
QQStyleKitTheme::QQStyleKitTheme(QObject *parent)
- : QQStyleKitControls(parent)
+ : QQStyleKitStyleAndThemeBase(parent)
{
}
diff --git a/src/labs/stylekit/qqstylekittheme_p.h b/src/labs/stylekit/qqstylekittheme_p.h
index 8096e9935b..6b09eea3f3 100644
--- a/src/labs/stylekit/qqstylekittheme_p.h
+++ b/src/labs/stylekit/qqstylekittheme_p.h
@@ -18,7 +18,7 @@
#include <QtQml/QtQml>
-#include "qqstylekitcontrols_p.h"
+#include "qqstylekitstyleandthemebase_p.h"
#include "qqstylekitpalette_p.h"
QT_BEGIN_NAMESPACE
@@ -26,10 +26,11 @@ QT_BEGIN_NAMESPACE
class QQStyleKitControls;
class QQStyleKitPropertyResolver;
-class QQStyleKitTheme : public QQStyleKitControls
+class QQStyleKitTheme : public QQStyleKitStyleAndThemeBase
{
Q_OBJECT
+ // TODO: Move to QQStyleKitStyleAndThemeBase
Q_PROPERTY(QQStyleKitPalette *palettes READ palettes NOTIFY palettesChanged FINAL)
QML_NAMED_ELEMENT(Theme)
diff --git a/src/qmlls/qqmlcodemodel_p.h b/src/qmlls/qqmlcodemodel_p.h
index dba6060dff..d2851a885f 100644
--- a/src/qmlls/qqmlcodemodel_p.h
+++ b/src/qmlls/qqmlcodemodel_p.h
@@ -20,6 +20,7 @@
#include "qtextdocument_p.h"
#include "qprocessscheduler_p.h"
#include "qqmllshelputils_p.h"
+#include "qqmlsemantictokens_p.h"
#include <QObject>
#include <QHash>
@@ -70,7 +71,7 @@ public:
struct RegisteredSemanticTokens
{
QByteArray resultId = "0";
- QList<int> lastTokens;
+ QmlHighlighting::HighlightsContainer highlights;
};
struct ModuleSetting
diff --git a/src/qmlls/qqmlhighlightsupport.cpp b/src/qmlls/qqmlhighlightsupport.cpp
index 70f484d245..50617cceb9 100644
--- a/src/qmlls/qqmlhighlightsupport.cpp
+++ b/src/qmlls/qqmlhighlightsupport.cpp
@@ -3,12 +3,14 @@
// Qt-Security score:significant reason:default
#include <qqmlhighlightsupport_p.h>
+#include <qqmldiffer_p.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
using namespace QLspSpecification;
using namespace QQmlJS::Dom;
+using namespace QmlHighlighting;
/*!
\internal
@@ -37,7 +39,36 @@ QList<QByteArray> defaultTokenModifiersList()
QList<QByteArray> extendedTokenTypesList()
{
- return enumToByteArray<QmlHighlighting::SemanticTokenProtocolTypes>();
+ return enumToByteArray<SemanticTokenProtocolTypes>();
+}
+
+static QList<int> generateHighlights(QmlLsp::RegisteredSemanticTokens &cached,
+ const QmlLsp::OpenDocument &doc,
+ const std::optional<HighlightsRange> &range,
+ HighlightingMode mode)
+{
+ DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
+ const auto fileObject = file.ownerAs<QmlFile>();
+ QmlHighlighting::Utils::updateResultID(cached.resultId);
+ if (!fileObject || !(fileObject && fileObject->isValid())) {
+ if (const auto lastValidItem = doc.snapshot.validDoc.ownerAs<QmlFile>()) {
+ const auto shiftedHighlights = QmlHighlighting::Utils::shiftHighlights(
+ cached.highlights, lastValidItem->code(), doc.textDocument->toPlainText());
+ return QmlHighlighting::Utils::encodeSemanticTokens(shiftedHighlights, mode);
+ } else {
+ // TODO: Implement regexp based fallback highlighting
+ return {};
+ }
+ } else {
+ HighlightsContainer highlights = QmlHighlighting::Utils::visitTokens(file, range);
+ if (highlights.isEmpty())
+ return {};
+ // Record the highlights for future diffs, only record full highlights
+ if (!range.has_value() )
+ cached.highlights = highlights;
+
+ return QmlHighlighting::Utils::encodeSemanticTokens(highlights, mode);
+ }
}
/*!
@@ -47,7 +78,7 @@ https://microsoft.github.io/language-server-protocol/specifications/specificatio
Sends a QLspSpecification::SemanticTokens data as response that is generated for the entire file.
*/
SemanticTokenFullHandler::SemanticTokenFullHandler(QmlLsp::QQmlCodeModelManager *codeModelManager)
- : QQmlBaseModule(codeModelManager), m_mode(QmlHighlighting::HighlightingMode::Default)
+ : QQmlBaseModule(codeModelManager), m_mode(HighlightingMode::Default)
{
}
@@ -63,23 +94,14 @@ void SemanticTokenFullHandler::process(
ResponseScopeGuard guard(result, request->m_response);
const QByteArray uri = QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri);
const auto doc = m_codeModelManager->openDocumentByUrl(uri);
- DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
- const auto fileObject = file.ownerAs<QmlFile>();
- if (!fileObject || !(fileObject && fileObject->isValid())) {
- guard.setError({
- int(QLspSpecification::ErrorCodes::RequestCancelled),
- "Cannot proceed: current QML document is invalid!"_L1,
- });
+ auto &cached = m_codeModelManager->registeredTokens(uri);
+ const auto encoded = generateHighlights(cached, doc, std::nullopt, m_mode);
+
+ if (encoded.isEmpty()) {
+ result = nullptr;
return;
- }
- auto &&encoded = QmlHighlighting::Utils::collectTokens(file, std::nullopt, m_mode);
- auto &registeredTokens = m_codeModelManager->registeredTokens(uri);
- if (!encoded.isEmpty()) {
- QmlHighlighting::Utils::updateResultID(registeredTokens.resultId);
- result = SemanticTokens{ registeredTokens.resultId, encoded };
- registeredTokens.lastTokens = std::move(encoded);
} else {
- result = nullptr;
+ result = SemanticTokens{cached.resultId, std::move(encoded)};
}
}
@@ -96,7 +118,7 @@ Sends either SemanticTokens or SemanticTokensDelta data as response.
This is generally requested when the text document is edited after receiving full highlighting data.
*/
SemanticTokenDeltaHandler::SemanticTokenDeltaHandler(QmlLsp::QQmlCodeModelManager *codeModelManager)
- : QQmlBaseModule(codeModelManager), m_mode(QmlHighlighting::HighlightingMode::Default)
+ : QQmlBaseModule(codeModelManager), m_mode(HighlightingMode::Default)
{
}
@@ -112,33 +134,20 @@ void SemanticTokenDeltaHandler::process(
ResponseScopeGuard guard(result, request->m_response);
const QByteArray uri = QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri);
const auto doc = m_codeModelManager->openDocumentByUrl(uri);
- DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
- const auto fileObject = file.ownerAs<QmlFile>();
- if (!fileObject || !(fileObject && fileObject->isValid())) {
- guard.setError({
- int(QLspSpecification::ErrorCodes::RequestCancelled),
- "Cannot proceed: current QML document is invalid!"_L1,
- });
- return;
- }
- auto newEncoded = QmlHighlighting::Utils::collectTokens(file, std::nullopt, m_mode);
- auto &registeredTokens = m_codeModelManager->registeredTokens(uri);
- const auto lastResultId = registeredTokens.resultId;
- QmlHighlighting::Utils::updateResultID(registeredTokens.resultId);
-
- // Return full token list if result ids not align
- // otherwise compute the delta.
- if (lastResultId == request->m_parameters.previousResultId) {
- result = QLspSpecification::SemanticTokensDelta{
- registeredTokens.resultId,
- QmlHighlighting::Utils::computeDiff(registeredTokens.lastTokens, newEncoded)
- };
- } else if (!newEncoded.isEmpty()) {
- result = QLspSpecification::SemanticTokens{ registeredTokens.resultId, newEncoded };
+ auto &cached = m_codeModelManager->registeredTokens(uri);
+ if (cached.resultId != request->m_parameters.previousResultId) {
+ // The client is out of sync, send full tokens
+ cached.resultId = request->m_parameters.previousResultId;
+ const auto encoded = generateHighlights(cached, doc, std::nullopt, m_mode);
+ result = QLspSpecification::SemanticTokens{ cached.resultId, encoded };
} else {
- result = nullptr;
+ const auto cachedHighlights = QmlHighlighting::Utils::encodeSemanticTokens(cached.highlights);
+ const auto encoded = generateHighlights(cached, doc, std::nullopt, m_mode);
+ result = QLspSpecification::SemanticTokensDelta{
+ cached.resultId,
+ QmlHighlighting::Utils::computeDiff(cachedHighlights, encoded)
+ };
}
- registeredTokens.lastTokens = std::move(newEncoded);
}
void SemanticTokenDeltaHandler::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
@@ -153,7 +162,7 @@ https://microsoft.github.io/language-server-protocol/specifications/specificatio
Sends a QLspSpecification::SemanticTokens data as response that is generated for a range of file.
*/
SemanticTokenRangeHandler::SemanticTokenRangeHandler(QmlLsp::QQmlCodeModelManager *codeModelManager)
- : QQmlBaseModule(codeModelManager), m_mode(QmlHighlighting::HighlightingMode::Default)
+ : QQmlBaseModule(codeModelManager), m_mode(HighlightingMode::Default)
{
}
@@ -169,28 +178,21 @@ void SemanticTokenRangeHandler::process(
ResponseScopeGuard guard(result, request->m_response);
const QByteArray uri = QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri);
const auto doc = m_codeModelManager->openDocumentByUrl(uri);
- DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
- const auto qmlFile = file.as<QmlFile>();
- if (!qmlFile || !(qmlFile && qmlFile->isValid())) {
- guard.setError({
- int(QLspSpecification::ErrorCodes::RequestCancelled),
- "Cannot proceed: current QML document is invalid!"_L1,
- });
- return;
- }
- const QString &code = qmlFile->code();
+ const QString code = doc.textDocument->toPlainText();
const auto range = request->m_parameters.range;
int startOffset =
int(QQmlLSUtils::textOffsetFrom(code, range.start.line, range.end.character));
int endOffset = int(QQmlLSUtils::textOffsetFrom(code, range.end.line, range.end.character));
- auto &&encoded = QmlHighlighting::Utils::collectTokens(
- file, QmlHighlighting::HighlightsRange{ startOffset, endOffset }, m_mode);
- auto &registeredTokens = m_codeModelManager->registeredTokens(uri);
- if (!encoded.isEmpty()) {
- QmlHighlighting::Utils::updateResultID(registeredTokens.resultId);
- result = SemanticTokens{ registeredTokens.resultId, std::move(encoded) };
- } else {
+ auto &cached = m_codeModelManager->registeredTokens(uri);
+ const auto encodedTokens = generateHighlights(
+ cached,
+ doc,
+ QmlHighlighting::HighlightsRange{ startOffset, endOffset },
+ m_mode);
+ if (encodedTokens.isEmpty()) {
result = nullptr;
+ } else {
+ result = SemanticTokens{ cached.resultId, std::move(encodedTokens) };
}
}
@@ -226,7 +228,7 @@ void QQmlHighlightSupport::setupCapabilities(
if (auto clientInitOptions = clientCapabilities.initializationOptions) {
if ((*clientInitOptions)[u"qtCreatorHighlighting"_s].toBool(false)) {
- const auto mode = QmlHighlighting::HighlightingMode::QtCHighlighting;
+ const auto mode = HighlightingMode::QtCHighlighting;
m_delta.setHighlightingMode(mode);
m_full.setHighlightingMode(mode);
m_range.setHighlightingMode(mode);
diff --git a/src/qmlls/qqmlsemantictokens.cpp b/src/qmlls/qqmlsemantictokens.cpp
index b5b38f827f..3a0157f447 100644
--- a/src/qmlls/qqmlsemantictokens.cpp
+++ b/src/qmlls/qqmlsemantictokens.cpp
@@ -1057,11 +1057,15 @@ HighlightsContainer Utils::visitTokens(const QQmlJS::Dom::DomItem &item,
return highlightDomElements.highlights();
}
-QList<int> Utils::collectTokens(const QQmlJS::Dom::DomItem &item,
- const std::optional<HighlightsRange> &range,
- HighlightingMode mode)
+HighlightsContainer Utils::shiftHighlights(const HighlightsContainer &cachedHighlights,
+ const QString &lastValidCode, const QString &currentCode)
{
- return Utils::encodeSemanticTokens(visitTokens(item, range), mode);
+ using namespace QQmlLSUtils;
+ Differ differ;
+ const QList<Diff> diffs = differ.diff(lastValidCode, currentCode);
+ HighlightsContainer shifts = cachedHighlights;
+ applyDiffs(shifts, diffs);
+ return shifts;
}
static std::pair<quint32, quint32> newlineCountAndLastLineLength(const QString &text)
@@ -1069,6 +1073,7 @@ static std::pair<quint32, quint32> newlineCountAndLastLineLength(const QString &
auto [row, col] = QQmlJS::SourceLocation::rowAndColumnFrom(text, text.size());
return { row - 1, col - 1 }; // rows are 1-based, so subtract 1 to get the number of newlines
}
+
static void updateCursorPositionByDiff(const QString &text, QQmlJS::SourceLocation &cursor)
{
auto [newLines, lastLineLength] = newlineCountAndLastLineLength(text);
diff --git a/src/qmlls/qqmlsemantictokens_p.h b/src/qmlls/qqmlsemantictokens_p.h
index 1641c9f303..0674a0a3cc 100644
--- a/src/qmlls/qqmlsemantictokens_p.h
+++ b/src/qmlls/qqmlsemantictokens_p.h
@@ -160,14 +160,13 @@ void addModifier(QLspSpecification::SemanticTokenModifiers modifier, int *baseMo
bool rangeOverlapsWithSourceLocation(const QQmlJS::SourceLocation &loc, const HighlightsRange &r);
QList<QLspSpecification::SemanticTokensEdit> computeDiff(const QList<int> &, const QList<int> &);
void updateResultID(QByteArray &resultID);
-QList<int> collectTokens(const QQmlJS::Dom::DomItem &item,
- const std::optional<HighlightsRange> &range,
- HighlightingMode mode = HighlightingMode::Default);
HighlightsContainer visitTokens(const QQmlJS::Dom::DomItem &item,
const std::optional<HighlightsRange> &range);
void addHighlight(HighlightsContainer &out, const QQmlJS::SourceLocation &loc, QmlHighlightKind,
QmlHighlightModifiers = QmlHighlightModifier::None);
void applyDiffs(HighlightsContainer &highlights, const QList<QQmlLSUtils::Diff> &diffs);
+HighlightsContainer shiftHighlights(const HighlightsContainer &cachedHighlights,
+ const QString &lastValidCode, const QString &currentCode);
} // namespace Utils
class HighlightingVisitor
diff --git a/tests/auto/qmlls/modules/tst_qmlls_modules.cpp b/tests/auto/qmlls/modules/tst_qmlls_modules.cpp
index 7ad4b4447e..533fccfe62 100644
--- a/tests/auto/qmlls/modules/tst_qmlls_modules.cpp
+++ b/tests/auto/qmlls/modules/tst_qmlls_modules.cpp
@@ -1945,8 +1945,8 @@ void tst_qmlls_modules::semanticHighlightingFull()
{
QFETCH(QString, filePath);
const auto item = fileObject(testFile(filePath));
- QmlHighlighting::HighlightsContainer highlights;
- const auto expectedData = QmlHighlighting::Utils::collectTokens(item, std::nullopt);
+ QmlHighlighting::HighlightsContainer highlights = QmlHighlighting::Utils::visitTokens(item, std::nullopt);
+ const auto expectedData = QmlHighlighting::Utils::encodeSemanticTokens(highlights);
const auto uri = openFile(filePath);
QVERIFY(uri);
@@ -1988,13 +1988,13 @@ void tst_qmlls_modules::semanticHighlightingRange()
QFETCH(QLspSpecification::Range, range);
const auto item = fileObject(testFile(filePath));
- QmlHighlighting::HighlightsContainer highlights;
const auto qmlFile = item.as<QQmlJS::Dom::QmlFile>();
const auto code = qmlFile->code();
const int startOffset = int(QQmlLSUtils::textOffsetFrom(code, range.start.line, range.end.character));
const int endOffset = int(QQmlLSUtils::textOffsetFrom(code, range.end.line, range.end.character));
- const auto expectedData = QmlHighlighting::Utils::collectTokens(
+ QmlHighlighting::HighlightsContainer highlights = QmlHighlighting::Utils::visitTokens(
item, QmlHighlighting::HighlightsRange{ startOffset, endOffset });
+ const auto expectedData = QmlHighlighting::Utils::encodeSemanticTokens(highlights);
const auto uri = openFile(filePath);
QVERIFY(uri);
@@ -2039,8 +2039,12 @@ void tst_qmlls_modules::semanticHighlightingDelta()
const auto fileItem = fileObject(testFile(filePath));
const auto deltaFileItem = fileObject(testFile(deltaFilePath));
- auto fullDocumentSemanticTokensData = QmlHighlighting::Utils::collectTokens(fileItem, std::nullopt);
- auto editedDocumentSemanticTokensData = QmlHighlighting::Utils::collectTokens(deltaFileItem, std::nullopt);
+ const auto fullDocumentSemanticTokens = QmlHighlighting::Utils::visitTokens(fileItem, std::nullopt);
+ const auto editedDocumentSemanticTokens = QmlHighlighting::Utils::visitTokens(deltaFileItem, std::nullopt);
+ auto fullDocumentSemanticTokensData =
+ QmlHighlighting::Utils::encodeSemanticTokens(fullDocumentSemanticTokens);
+ auto editedDocumentSemanticTokensData =
+ QmlHighlighting::Utils::encodeSemanticTokens(editedDocumentSemanticTokens);
const auto expectedEdits = QmlHighlighting::Utils::computeDiff(
fullDocumentSemanticTokensData, editedDocumentSemanticTokensData);
diff --git a/tests/auto/qmlls/utils/data/highlights/highlightsShift.qml b/tests/auto/qmlls/utils/data/highlights/highlightsShift.qml
new file mode 100644
index 0000000000..df4ffc69da
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/highlightsShift.qml
@@ -0,0 +1,8 @@
+import QtQuick
+Item {
+ width: 100
+ Rectangle {
+ width: 100
+ height: 100
+ }
+}
diff --git a/tests/auto/qmlls/utils/data/highlights/highlightsShift_consoleLog.qml b/tests/auto/qmlls/utils/data/highlights/highlightsShift_consoleLog.qml
new file mode 100644
index 0000000000..b717634689
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/highlightsShift_consoleLog.qml
@@ -0,0 +1,8 @@
+import QtQuick
+
+HasEnumAndAttachedType {
+ property var enumValue: 1
+ Component.onCompleted: {
+ console.log(enumValue)
+ }
+}
diff --git a/tests/auto/qmlls/utils/data/highlights/highlightsShift_withComments.qml b/tests/auto/qmlls/utils/data/highlights/highlightsShift_withComments.qml
new file mode 100644
index 0000000000..fddac941f6
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/highlightsShift_withComments.qml
@@ -0,0 +1,11 @@
+import QtQuick
+
+HasEnumAndAttachedType {
+ property var enumValue: 1
+ Component.onCompleted: {
+ console.log(enumValue)
+ }
+}
+
+// This is a comment line
+// Another comment line \ No newline at end of file
diff --git a/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_consoleLog_del.qml b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_consoleLog_del.qml
new file mode 100644
index 0000000000..63ace4b663
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_consoleLog_del.qml
@@ -0,0 +1,8 @@
+import QtQuick
+
+HasEnumAndAttachedType {
+ property var enumValue: 1
+ Component.onCompleted:
+ console.log(enumValue)
+ }
+}
diff --git a/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del1.qml b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del1.qml
new file mode 100644
index 0000000000..ee722b9ce7
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del1.qml
@@ -0,0 +1,8 @@
+imQuick
+Item {
+ width: 100
+ Rectangle {
+ width: 100
+ height: 100
+ }
+}
diff --git a/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del2.qml b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del2.qml
new file mode 100644
index 0000000000..c81451e306
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del2.qml
@@ -0,0 +1,8 @@
+import QtQuick
+Ite
+ width: 100
+ Rectangle {
+ width: 100
+ height: 100
+ }
+}
diff --git a/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del3.qml b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del3.qml
new file mode 100644
index 0000000000..7e93d458d3
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del3.qml
@@ -0,0 +1,8 @@
+ QtQuick
+Item {
+ width: 100
+ Rectangle {
+ width: 100
+ height: 100
+ }
+}
diff --git a/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del4.qml b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del4.qml
new file mode 100644
index 0000000000..8e8e7f0475
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del4.qml
@@ -0,0 +1,6 @@
+import QtQuick
+Item {
+ width: 100
+ Rectangle {
+
+}
diff --git a/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del5.qml b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del5.qml
new file mode 100644
index 0000000000..9cf4c3804a
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del5.qml
@@ -0,0 +1,7 @@
+import QtQuick
+Item {
+ width: 100
+ Rectangle
+height: 100
+ }
+}
diff --git a/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del6.qml b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del6.qml
new file mode 100644
index 0000000000..5437541542
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_del6.qml
@@ -0,0 +1,6 @@
+import QtQuick
+Item {
+ width: 100th: 100
+ height: 100
+ }
+}
diff --git a/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_withComments_del.qml b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_withComments_del.qml
new file mode 100644
index 0000000000..4bccec2ebe
--- /dev/null
+++ b/tests/auto/qmlls/utils/data/highlights/invalid/highlightsShift_withComments_del.qml
@@ -0,0 +1,5 @@
+import QtQuick
+
+
+// This is a comment line
+// Another comment line \ No newline at end of file
diff --git a/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp b/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp
index 6dae204bb0..49fe4e8c87 100644
--- a/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp
+++ b/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp
@@ -991,28 +991,28 @@ void tst_qmlls_highlighting::computeDiff()
}
}
-void tst_qmlls_highlighting::enumCrash()
+static QQmlJS::Dom::DomItem fileObject(const QString &filePath)
{
using namespace QQmlJS::Dom;
- const auto fileObject = [](const QString &filePath) {
- QFile f(filePath);
- DomItem file;
- if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
- return file;
- QString code = f.readAll();
-
- QStringList dirs = { QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) };
- auto envPtr = DomEnvironment::create(
- dirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, Extended);
- envPtr->loadBuiltins();
- envPtr->loadFile(FileToLoad::fromMemory(envPtr, filePath, code),
- [&file](Path, const DomItem &, const DomItem &newIt) {
- file = newIt.fileObject();
- });
- envPtr->loadPendingDependencies();
+ QFile f(filePath);
+ DomItem file;
+ if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return file;
- };
+ QString code = f.readAll();
+
+ QStringList dirs = { QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) };
+ auto envPtr = DomEnvironment::create(dirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded,
+ Extended);
+ envPtr->loadBuiltins();
+ envPtr->loadFile(
+ FileToLoad::fromMemory(envPtr, filePath, code),
+ [&file](Path, const DomItem &, const DomItem &newIt) { file = newIt.fileObject(); });
+ envPtr->loadPendingDependencies();
+ return file;
+};
+void tst_qmlls_highlighting::enumCrash()
+{
const auto filePath = m_highlightingDataDir + "/enums_qtbug.qml";
const auto fileItem = fileObject(filePath);
@@ -1021,4 +1021,253 @@ void tst_qmlls_highlighting::enumCrash()
QVERIFY(!highlights.isEmpty());
}
+void tst_qmlls_highlighting::shiftHighlights_data()
+{
+ QTest::addColumn<QmlHighlighting::HighlightsContainer>("lastValidHighlights");
+ QTest::addColumn<QString>("lastValidCode");
+ QTest::addColumn<QString>("currentCode");
+ QTest::addColumn<QmlHighlighting::HighlightsContainer>("expectedHighlights");
+
+ const auto filePath = m_highlightingDataDir + "/highlightsShift.qml";
+ const auto fileItem = fileObject(filePath);
+ const auto originalHighlights = QmlHighlighting::Utils::visitTokens(fileItem, std::nullopt);
+ const QString lastValidCode = fileItem.ownerAs<QQmlJS::Dom::QmlFile>()->code();
+
+ const auto editedCode = [&](const QString &file) -> QString {
+ const auto editedPath = m_highlightingDataDir + file;
+ QFile editedFile(editedPath);
+ if (!editedFile.open(QIODevice::ReadOnly | QIODevice::Text))
+ return QString();
+ return editedFile.readAll();
+ };
+
+ // delete port Qt from import QtQuick
+ // Expect highlights are the same as the originals except Item token.
+ {
+ const QString currentCode = editedCode("/invalid/highlightsShift_del1.qml");
+
+ QmlHighlighting::HighlightsContainer expectedHighlights = originalHighlights;
+ expectedHighlights.remove(0);
+ expectedHighlights.remove(7);
+ const auto im = QQmlJS::SourceLocation(0, 2, 1, 1); // 'im' location
+ expectedHighlights[im.offset] =
+ HighlightToken(im, QmlHighlightKind::QmlKeyword, QmlHighlightModifier::None);
+ const auto quick = QQmlJS::SourceLocation(3, 5, 1, 3); // 'Quick' location
+ expectedHighlights[quick.offset] =
+ HighlightToken(quick, QmlHighlightKind::QmlImportId, QmlHighlightModifier::None);
+
+ QTest::addRow("modify-line-without-lineshift-from-lhs-rhs")
+ << originalHighlights << lastValidCode << currentCode << expectedHighlights;
+ }
+ // modify Item { into Ite
+ // Expect highlights are the same as the originals except Item token.
+ {
+ const QString currentCode = editedCode("/invalid/highlightsShift_del2.qml");
+
+ QmlHighlighting::HighlightsContainer expectedHighlights = originalHighlights;
+ const auto itemNewLoc = QQmlJS::SourceLocation(15, 3, 2, 1); // 'Ite' location
+ expectedHighlights[itemNewLoc.offset] =
+ HighlightToken(itemNewLoc, QmlHighlightKind::QmlType, QmlHighlightModifier::None);
+
+ QTest::addRow("modify-line-without-lineshift-from-rhs")
+ << originalHighlights << lastValidCode << currentCode << expectedHighlights;
+ }
+ // delete "import" from import QtQuick
+ // Expect highlights are the same as the originals QtQuick token should shift.
+ // and import should be gone.
+ {
+ const QString currentCode = editedCode("/invalid/highlightsShift_del3.qml");
+
+ QmlHighlighting::HighlightsContainer expectedHighlights = originalHighlights;
+ // remove import QtQuick
+ expectedHighlights.remove(0);
+ expectedHighlights.remove(7);
+ // insert QtQuick
+ const auto qtquickNewLoc = QQmlJS::SourceLocation(1, 7, 1, 2);
+ expectedHighlights[qtquickNewLoc.offset] = HighlightToken(
+ qtquickNewLoc, QmlHighlightKind::QmlImportId, QmlHighlightModifier::None);
+
+ QTest::addRow("modify-line-without-lineshift-from-lhs")
+ << originalHighlights << lastValidCode << currentCode << expectedHighlights;
+ }
+
+ // delete inner content of Rectangle, leave it in invalid state
+ {
+ const QString currentCode = editedCode("/invalid/highlightsShift_del4.qml");
+
+ QmlHighlighting::HighlightsContainer expectedHighlights = originalHighlights;
+ // remove Rectangle content tokens
+ expectedHighlights.remove(61); // width
+ expectedHighlights.remove(68); // 100
+ expectedHighlights.remove(80); // height
+ expectedHighlights.remove(88); // 100
+
+ QTest::addRow("delete-multiline-no-shift")
+ << originalHighlights << lastValidCode << currentCode << expectedHighlights;
+ }
+ // delete width content of Rectangle, height should line shift up
+ {
+ const QString currentCode = editedCode("/invalid/highlightsShift_del5.qml");
+
+ QmlHighlighting::HighlightsContainer expectedHighlights = originalHighlights;
+ // remove Rectangle content tokens
+ expectedHighlights.remove(61); // width
+ expectedHighlights.remove(68); // 100
+
+ // height should line shift up, and shifts column
+ auto heightToken = expectedHighlights.take(80);
+ expectedHighlights.remove(80);
+ heightToken.loc.startLine = 5;
+ heightToken.loc.startColumn = 1;
+ heightToken.loc.offset = 51;
+ expectedHighlights[heightToken.loc.offset] = heightToken;
+
+ auto numberToken = expectedHighlights.take(88);
+ expectedHighlights.remove(88);
+ numberToken.loc.startLine = 5;
+ numberToken.loc.startColumn = 9;
+ numberToken.loc.offset = 59;
+ expectedHighlights[numberToken.loc.offset] = numberToken;
+ QTest::addRow("delete-multiline-line-column-shift")
+ << originalHighlights << lastValidCode << currentCode << expectedHighlights;
+ }
+ // delete width content of Rectangle, height should line shift up, Rectangle should be erased
+ // th: 100 part of width should line up and start at the end of upper line
+ {
+ const QString currentCode = editedCode("/invalid/highlightsShift_del6.qml");
+
+ QmlHighlighting::HighlightsContainer expectedHighlights = originalHighlights;
+ // remove Rectangle content tokens
+ expectedHighlights.remove(41); // Rectangle
+
+ // width
+ auto widthToken = expectedHighlights.take(61);
+ expectedHighlights.remove(61);
+ widthToken.loc.startLine = 3;
+ widthToken.loc.startColumn = 15;
+ widthToken.loc.offset = 36;
+ widthToken.loc.length = 2; // th part remained
+ expectedHighlights[widthToken.loc.offset] = widthToken;
+ // 100
+ auto numberToken = expectedHighlights.take(68);
+ expectedHighlights.remove(68);
+ numberToken.loc.startLine = 3;
+ numberToken.loc.startColumn = 19;
+ numberToken.loc.offset = 40;
+ expectedHighlights[numberToken.loc.offset] = numberToken;
+ // height should line shift up
+ auto heightToken = expectedHighlights.take(80);
+ expectedHighlights.remove(80);
+ heightToken.loc.startLine = 4;
+ heightToken.loc.startColumn = 9;
+ heightToken.loc.offset = 52;
+ expectedHighlights[heightToken.loc.offset] = heightToken;
+
+ auto heightNumber = expectedHighlights.take(88);
+ expectedHighlights.remove(88);
+ heightNumber.loc.startLine = 4;
+ heightNumber.loc.startColumn = 17;
+ heightNumber.loc.offset = 60;
+ expectedHighlights[heightNumber.loc.offset] = heightNumber;
+
+ QTest::addRow("delete-multiline-line-column-shift-2")
+ << originalHighlights << lastValidCode << currentCode << expectedHighlights;
+ }
+ { // manual testing find: console.log(element)
+ const auto filePath = m_highlightingDataDir + "/highlightsShift_consoleLog.qml";
+ const auto fileItem = fileObject(filePath);
+ const auto highlights = QmlHighlighting::Utils::visitTokens(fileItem, std::nullopt);
+ const QString validCode = fileItem.ownerAs<QQmlJS::Dom::QmlFile>()->code();
+ const QString currentCode = editedCode("/invalid/highlightsShift_consoleLog_del.qml");
+
+ QmlHighlighting::HighlightsContainer expectedHighlights = highlights;
+ // console
+ auto consoleToken = expectedHighlights.take(108);
+ expectedHighlights.remove(108);
+ consoleToken.loc.offset = 107;
+ expectedHighlights[consoleToken.loc.offset] = consoleToken;
+ // log
+ auto logToken = expectedHighlights.take(116);
+ expectedHighlights.remove(116);
+ logToken.loc.offset = 115;
+ expectedHighlights[logToken.loc.offset] = logToken;
+ // enumValue
+ auto enumValueToken = expectedHighlights.take(120);
+ expectedHighlights.remove(120);
+ enumValueToken.loc.offset = 119;
+ expectedHighlights[enumValueToken.loc.offset] = enumValueToken;
+
+ QTest::addRow("delete-shift-console-log")
+ << highlights << validCode << currentCode << expectedHighlights;
+ }
+ { // manual testing find: comments after deletion
+ const auto filePath = m_highlightingDataDir + "/highlightsShift_withComments.qml";
+ const auto fileItem = fileObject(filePath);
+ const auto highlights = QmlHighlighting::Utils::visitTokens(fileItem, std::nullopt);
+ const QString validCode = fileItem.ownerAs<QQmlJS::Dom::QmlFile>()->code();
+ const QString currentCode = editedCode("/invalid/highlightsShift_withComments_del.qml");
+
+ QmlHighlighting::HighlightsContainer expectedHighlights;
+ // import
+ expectedHighlights[0] = highlights[0];
+ // QtQuick
+ expectedHighlights[7] = highlights[7];
+ // commentline 1
+ auto commentLine1 = QmlHighlighting::HighlightToken(
+ QQmlJS::SourceLocation(17, 25, 4, 1),
+ QmlHighlightKind::Comment, QmlHighlightModifier::None);
+ expectedHighlights[commentLine1.loc.offset] = commentLine1;
+ // commentline 2
+ auto commentLine2 = QmlHighlighting::HighlightToken(
+ QQmlJS::SourceLocation(43, 23, 5, 1),
+ QmlHighlightKind::Comment, QmlHighlightModifier::None);
+ expectedHighlights[commentLine2.loc.offset] = commentLine2;
+
+ QTest::addRow("delete-shift-comments-after-deletion")
+ << highlights << validCode << currentCode << expectedHighlights;
+ }
+}
+
+void tst_qmlls_highlighting::shiftHighlights()
+{
+ QFETCH(QmlHighlighting::HighlightsContainer, lastValidHighlights);
+ QFETCH(QString, lastValidCode);
+ QFETCH(QString, currentCode);
+ QFETCH(QmlHighlighting::HighlightsContainer, expectedHighlights);
+
+ HighlightsContainer actualHighlights =
+ QmlHighlighting::Utils::shiftHighlights(lastValidHighlights, lastValidCode, currentCode);
+ [&] {
+ const auto actualEncoded = QmlHighlighting::Utils::encodeSemanticTokens(
+ actualHighlights, QmlHighlighting::HighlightingMode::Default);
+ const auto expectedEncoded = QmlHighlighting::Utils::encodeSemanticTokens(
+ expectedHighlights, QmlHighlighting::HighlightingMode::Default);
+ QCOMPARE(actualEncoded, expectedEncoded);
+ }();
+
+ if (QTest::currentTestFailed()) {
+ auto [actual, expected] =
+ std::mismatch(actualHighlights.begin(), actualHighlights.end(),
+ expectedHighlights.begin(), expectedHighlights.end());
+
+ if (actual != actualHighlights.end() && expected != expectedHighlights.end()) {
+
+ const auto msg = [](const QString &title, int actualValue, int expectedValue) {
+ return QString("%1 : [Actual %2, Expected %3]")
+ .arg(title)
+ .arg(actualValue)
+ .arg(expectedValue);
+ };
+ qDebug() << msg("Offset", actual->loc.offset, expected->loc.offset);
+ qDebug() << msg("Length", actual->loc.length, expected->loc.length);
+ qDebug() << msg("StartLine", actual->loc.startLine, expected->loc.startLine);
+ qDebug() << msg("StartColumn", actual->loc.startColumn, expected->loc.startColumn);
+ qDebug() << msg("Kind", static_cast<int>(actual->kind),
+ static_cast<int>(expected->kind));
+ qDebug() << msg("Modifiers", static_cast<int>(actual->modifiers),
+ static_cast<int>(expected->modifiers));
+ }
+ }
+}
+
QTEST_MAIN(tst_qmlls_highlighting)
diff --git a/tests/auto/qmlls/utils/tst_qmlls_highlighting.h b/tests/auto/qmlls/utils/tst_qmlls_highlighting.h
index 42a0344a65..f7dc03ac00 100644
--- a/tests/auto/qmlls/utils/tst_qmlls_highlighting.h
+++ b/tests/auto/qmlls/utils/tst_qmlls_highlighting.h
@@ -32,6 +32,8 @@ private slots:
void computeDiff();
void enumCrash();
+ void shiftHighlights_data();
+ void shiftHighlights();
private:
QString m_highlightingDataDir;