From 3025fedd035a4cee466654dc787ecaccd3022e91 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 28 Apr 2025 15:53:36 +0200 Subject: [PATCH] QmlDesigner: Add StringUtils::split_last function - Utilized QStringView for type and url to improve performance. - Implemented split_last functionality using std::ranges and std::views to efficiently split the fullType string into type and url. - Move all string utils to qml designer utils Change-Id: I627c5b4f03462df8a4f9ab174c34a249b6b8fe87 Reviewed-by: Thomas Hartmann --- .../libs/designercore/CMakeLists.txt | 1 - .../designercore/model/bindingproperty.cpp | 10 +-- .../rewriter/qmltextgenerator.cpp | 12 ++- .../designercore/rewriter/rewriterview.cpp | 15 ++-- .../rewriter/texttomodelmerger.cpp | 30 ++----- .../libs/qmldesignerutils/CMakeLists.txt | 1 + .../stringutils.h | 33 ++++++-- .../qmldesigner/qmltools/qmlobjectnode.cpp | 8 +- .../tests/testdesignercore/CMakeLists.txt | 1 - tests/unit/tests/unittests/CMakeLists.txt | 1 + .../designercoreutils/CMakeLists.txt | 3 - .../unittests/qmldesignerutils/CMakeLists.txt | 7 ++ .../qmldesignerutils/stringutils-test.cpp | 82 +++++++++++++++++++ .../version-test.cpp | 0 14 files changed, 149 insertions(+), 55 deletions(-) rename src/plugins/qmldesigner/libs/{designercore/include => qmldesignerutils}/stringutils.h (55%) create mode 100644 tests/unit/tests/unittests/qmldesignerutils/CMakeLists.txt create mode 100644 tests/unit/tests/unittests/qmldesignerutils/stringutils-test.cpp rename tests/unit/tests/unittests/{designercoreutils => qmldesignerutils}/version-test.cpp (100%) diff --git a/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt b/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt index b5f529c8e3a..0447d428daf 100644 --- a/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt +++ b/src/plugins/qmldesigner/libs/designercore/CMakeLists.txt @@ -239,7 +239,6 @@ extend_qtc_library(QmlDesignerCore rewritingexception.h signalhandlerproperty.h sourcepathids.h - stringutils.h synchronousimagecache.h variantproperty.h widgetregistration.h diff --git a/src/plugins/qmldesigner/libs/designercore/model/bindingproperty.cpp b/src/plugins/qmldesigner/libs/designercore/model/bindingproperty.cpp index 4d4f79c2504..7c9d5ab3e47 100644 --- a/src/plugins/qmldesigner/libs/designercore/model/bindingproperty.cpp +++ b/src/plugins/qmldesigner/libs/designercore/model/bindingproperty.cpp @@ -8,6 +8,8 @@ #include "model.h" #include "model_p.h" +#include + using namespace Qt::StringLiterals; namespace QmlDesigner { @@ -133,12 +135,10 @@ AbstractProperty BindingProperty::resolveToProperty() const return {}; ModelNode node = parentModelNode(); - auto lastElementBegin = std::ranges::find(binding | std::views::reverse, u'.').base(); - QStringView lastElement{lastElementBegin, binding.end()}; - if (binding.begin() != lastElementBegin) { - QStringView nodeBinding{binding.begin(), std::prev(lastElementBegin)}; + + auto [nodeBinding, lastElement] = StringUtils::split_last(binding, u'.'); + if (nodeBinding.size()) node = resolveBinding(nodeBinding, node); - } if (node.isValid() && !lastElement.contains(' ')) return node.property(lastElement.toUtf8()); diff --git a/src/plugins/qmldesigner/libs/designercore/rewriter/qmltextgenerator.cpp b/src/plugins/qmldesigner/libs/designercore/rewriter/qmltextgenerator.cpp index a5a987b0fc5..c255e7f0843 100644 --- a/src/plugins/qmldesigner/libs/designercore/rewriter/qmltextgenerator.cpp +++ b/src/plugins/qmldesigner/libs/designercore/rewriter/qmltextgenerator.cpp @@ -9,19 +9,21 @@ #include #include +#include + #include "bindingproperty.h" #include "model.h" #include "nodelistproperty.h" #include "nodeproperty.h" #include "signalhandlerproperty.h" -#include "stringutils.h" #include "variantproperty.h" #include using namespace QmlDesigner; -using namespace QmlDesigner::Internal; using namespace Qt::StringLiterals; +namespace QmlDesigner::Internal { + static QString properColorName(const QColor &color) { if (color.alpha() == 255) @@ -145,7 +147,7 @@ QString QmlTextGenerator::toQml(const AbstractProperty &property, int indentDept return stringValue; case QMetaType::QString: case QMetaType::QChar: - return QStringView(u"\"%1\"").arg(escape(unicodeEscape(stringValue))); + return QStringView(u"\"%1\"").arg(StringUtils::escape(unicodeEscape(stringValue))); case QMetaType::QVector2D: { auto vec = value.value(); return QStringLiteral("Qt.vector2d(%1, %2)").arg(vec.x(), vec.y()); @@ -160,7 +162,7 @@ QString QmlTextGenerator::toQml(const AbstractProperty &property, int indentDept .arg(vec.x(), vec.y(), vec.z(), vec.w()); } default: - return QStringView(u"\"%1\"").arg(escape(stringValue)); + return QStringView(u"\"%1\"").arg(StringUtils::escape(stringValue)); } } } else { @@ -281,3 +283,5 @@ QString QmlTextGenerator::propertyToQml(const AbstractProperty &property, int in return result; } + +} // namespace QmlDesigner::Internal diff --git a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriterview.cpp b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriterview.cpp index 12d547833e4..257684fc804 100644 --- a/src/plugins/qmldesigner/libs/designercore/rewriter/rewriterview.cpp +++ b/src/plugins/qmldesigner/libs/designercore/rewriter/rewriterview.cpp @@ -23,6 +23,8 @@ #include #include +#include + #include #include #include @@ -964,12 +966,11 @@ QmlJS::Document::Ptr RewriterView::document() const QString RewriterView::convertTypeToImportAlias(QStringView type) const { - auto simplifiedTypeBegin = std::ranges::find(type | std::views::reverse, u'.').base(); - const auto simplifiedType = QStringView{simplifiedTypeBegin, type.end()}.toString(); - if (type.begin() == simplifiedTypeBegin) - return simplifiedType; + auto [url, simplifiedType] = StringUtils::split_last(type, u'.'); + + if (url.isEmpty()) + return simplifiedType.toString(); - QStringView url{type.begin(), std::prev(simplifiedTypeBegin)}; auto &&imports = model()->imports(); auto projection = [](auto &&import) { return import.isFileImport() ? import.file() : import.url(); @@ -977,12 +978,12 @@ QString RewriterView::convertTypeToImportAlias(QStringView type) const auto found = std::ranges::find(imports, url, projection); if (found == imports.end()) - return simplifiedType; + return simplifiedType.toString(); auto &&alias = found->alias(); if (alias.isEmpty()) - return simplifiedType; + return simplifiedType.toString(); return alias + '.'_L1 + simplifiedType; } diff --git a/src/plugins/qmldesigner/libs/designercore/rewriter/texttomodelmerger.cpp b/src/plugins/qmldesigner/libs/designercore/rewriter/texttomodelmerger.cpp index 0d0a3d6bc52..aac7992e24a 100644 --- a/src/plugins/qmldesigner/libs/designercore/rewriter/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/libs/designercore/rewriter/texttomodelmerger.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -215,9 +216,8 @@ bool isSignalPropertyName(QStringView signalName) if (signalName.isEmpty()) return false; // see QmlCompiler::isSignalPropertyName - auto begin = std::ranges::find(signalName | std::views::reverse, u'.').base(); + auto [dummy, pureSignalName] = QmlDesigner::StringUtils::split_last(signalName, u'.'); - QStringView pureSignalName = {begin, signalName.end()}; return pureSignalName.length() >= 3 && pureSignalName.startsWith(u"on") && pureSignalName.at(2).isLetter(); } @@ -1626,26 +1626,6 @@ void TextToModelMerger::syncArrayProperty(AbstractProperty &modelProperty, } } -static QString fileForFullQrcPath(QStringView string) -{ - auto found = std::ranges::find(string | std::views::reverse, u'/'); - - if (found == string.rend()) - return {}; - - return QStringView{found.base(), string.end()}.toString(); -} - -static QString removeFileFromQrcPath(const QStringView string) -{ - auto found = std::ranges::find(string | std::views::reverse, u'/'); - - if (found == string.rend()) - return {}; - - return QStringView{string.begin(), std::prev(found.base())}.toString(); -} - void TextToModelMerger::syncVariantProperty(AbstractProperty &modelProperty, const QVariant &astValue, const TypeName &astType, @@ -2303,8 +2283,10 @@ void TextToModelMerger::populateQrcMapping(const QString &filePath) if (!filePath.startsWith(QLatin1String("qrc:"))) return; - QString path = removeFileFromQrcPath(filePath); - const QString fileName = fileForFullQrcPath(filePath); + auto [pathView, fileNameView] = StringUtils::split_last(filePath, u'/'); + + QString path = pathView.toString(); + const QString fileName = fileNameView.toString(); path.remove(QLatin1String("qrc:")); QMap map = ModelManagerInterface::instance()->filesInQrcPath(path); const QStringList qrcFilePaths = map.value(fileName, {}); diff --git a/src/plugins/qmldesigner/libs/qmldesignerutils/CMakeLists.txt b/src/plugins/qmldesigner/libs/qmldesignerutils/CMakeLists.txt index cca42246688..5820b8825a1 100644 --- a/src/plugins/qmldesigner/libs/qmldesignerutils/CMakeLists.txt +++ b/src/plugins/qmldesigner/libs/qmldesignerutils/CMakeLists.txt @@ -17,6 +17,7 @@ add_qtc_library(QmlDesignerUtils STATIC qmldesignerutils_global.h version.cpp version.h maputils.h + stringutils.h ) extend_qtc_library(QmlDesignerUtils diff --git a/src/plugins/qmldesigner/libs/designercore/include/stringutils.h b/src/plugins/qmldesigner/libs/qmldesignerutils/stringutils.h similarity index 55% rename from src/plugins/qmldesigner/libs/designercore/include/stringutils.h rename to src/plugins/qmldesigner/libs/qmldesignerutils/stringutils.h index b93c439719f..d6e7e72b0fc 100644 --- a/src/plugins/qmldesigner/libs/designercore/include/stringutils.h +++ b/src/plugins/qmldesigner/libs/qmldesignerutils/stringutils.h @@ -1,21 +1,25 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// 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 +#include -using namespace Qt::StringLiterals; +#include +#include -namespace QmlDesigner { +namespace QmlDesigner::StringUtils { inline QString escape(const QString &value) { - QString result = value; + using namespace Qt::StringLiterals; if (value.length() == 6 && value.startsWith("\\u")) //Do not double escape unicode chars return value; + QString result = value; + result.replace("\\"_L1, "\\\\"_L1); result.replace("\""_L1, "\\\""_L1); result.replace("\t"_L1, "\\t"_L1); @@ -27,11 +31,13 @@ inline QString escape(const QString &value) inline QString deescape(const QString &value) { - QString result = value; + using namespace Qt::StringLiterals; if (value.length() == 6 && value.startsWith("\\u")) //Ignore unicode chars return value; + QString result = value; + result.replace("\\\\"_L1, "\\"_L1); result.replace("\\\""_L1, "\""_L1); result.replace("\\t"_L1, "\t"_L1); @@ -41,4 +47,19 @@ inline QString deescape(const QString &value) return result; } -} // namespace QmlDesigner +template +concept is_object = std::is_object_v; + +std::pair split_last(is_object auto &&, QChar c) = delete; // remove rvalue overload + +inline std::pair split_last(QStringView text, QChar c) +{ + auto splitPoint = std::ranges::find(text | std::views::reverse, c).base(); + + if (splitPoint == text.begin()) + return {{}, text}; + + return {{text.begin(), std::prev(splitPoint)}, {splitPoint, text.end()}}; +} + +} // namespace QmlDesigner::StringUtils diff --git a/src/plugins/qmldesigner/qmltools/qmlobjectnode.cpp b/src/plugins/qmldesigner/qmltools/qmlobjectnode.cpp index 902d12e5c69..7e93e09931a 100644 --- a/src/plugins/qmldesigner/qmltools/qmlobjectnode.cpp +++ b/src/plugins/qmldesigner/qmltools/qmlobjectnode.cpp @@ -14,11 +14,11 @@ #include "qmlstate.h" #include "qmltimelinekeyframegroup.h" #include "qmlvisualnode.h" -#include "stringutils.h" #include "variantproperty.h" #include #include +#include #include @@ -255,7 +255,7 @@ QString QmlObjectNode::stripedTranslatableText(PropertyNameView name) const const QRegularExpressionMatch match = regularExpressionPattern.match( modelNode().bindingProperty(name).expression()); if (match.hasMatch()) - return deescape(match.captured(2)); + return StringUtils::deescape(match.captured(2)); return instanceValue(name).toString(); } return instanceValue(name).toString(); @@ -535,7 +535,7 @@ QVariant QmlObjectNode::instanceValue(const ModelNode &modelNode, PropertyNameVi QString QmlObjectNode::generateTranslatableText([[maybe_unused]] const QString &text, const DesignerSettings &settings) { - const QString escapedText = escape(text); + const QString escapedText = StringUtils::escape(text); if (settings.value(DesignerSettingsKey::TYPE_OF_QSTR_FUNCTION).toInt()) switch (settings.value(DesignerSettingsKey::TYPE_OF_QSTR_FUNCTION).toInt()) { @@ -557,7 +557,7 @@ QString QmlObjectNode::stripedTranslatableTextFunction(const QString &text) QLatin1String("^qsTr(|Id|anslate)\\(\"(.*)\"\\)$")); const QRegularExpressionMatch match = regularExpressionPattern.match(text); if (match.hasMatch()) - return deescape(match.captured(2)); + return StringUtils::deescape(match.captured(2)); return text; } diff --git a/tests/unit/tests/testdesignercore/CMakeLists.txt b/tests/unit/tests/testdesignercore/CMakeLists.txt index dd9dad3bd30..6b9798929b9 100644 --- a/tests/unit/tests/testdesignercore/CMakeLists.txt +++ b/tests/unit/tests/testdesignercore/CMakeLists.txt @@ -209,7 +209,6 @@ extend_qtc_library(TestDesignerCore rewritingexception.h signalhandlerproperty.h sourcepathids.h - stringutils.h synchronousimagecache.h variantproperty.h ) diff --git a/tests/unit/tests/unittests/CMakeLists.txt b/tests/unit/tests/unittests/CMakeLists.txt index 78cd94c2d9a..1a6ade38f3f 100644 --- a/tests/unit/tests/unittests/CMakeLists.txt +++ b/tests/unit/tests/unittests/CMakeLists.txt @@ -46,6 +46,7 @@ endfunction(unittest_copy_data_folder) add_subdirectory(componentcore) add_subdirectory(designercoreutils) +add_subdirectory(qmldesignerutils) add_subdirectory(designsystem) add_subdirectory(listmodeleditor) add_subdirectory(imagecache) diff --git a/tests/unit/tests/unittests/designercoreutils/CMakeLists.txt b/tests/unit/tests/unittests/designercoreutils/CMakeLists.txt index 51bd3a88d11..74e0f274e99 100644 --- a/tests/unit/tests/unittests/designercoreutils/CMakeLists.txt +++ b/tests/unit/tests/unittests/designercoreutils/CMakeLists.txt @@ -1,9 +1,6 @@ # qmldesigner/designercore/model extend_qtc_test(unittest - DEPENDS - QmlDesignerUtils SOURCES modelutils-test.cpp uniquename-test.cpp - version-test.cpp ) diff --git a/tests/unit/tests/unittests/qmldesignerutils/CMakeLists.txt b/tests/unit/tests/unittests/qmldesignerutils/CMakeLists.txt new file mode 100644 index 00000000000..c15c8eed464 --- /dev/null +++ b/tests/unit/tests/unittests/qmldesignerutils/CMakeLists.txt @@ -0,0 +1,7 @@ +extend_qtc_test(unittest + DEPENDS + QmlDesignerUtils + SOURCES + version-test.cpp + stringutils-test.cpp +) diff --git a/tests/unit/tests/unittests/qmldesignerutils/stringutils-test.cpp b/tests/unit/tests/unittests/qmldesignerutils/stringutils-test.cpp new file mode 100644 index 00000000000..bf53f4ed59e --- /dev/null +++ b/tests/unit/tests/unittests/qmldesignerutils/stringutils-test.cpp @@ -0,0 +1,82 @@ +// 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 +#include + +namespace { + +using QmlDesigner::StringUtils::split_last; + +TEST(StringUtils_split_last, leaf_is_empty_for_empty_input) +{ + QString text; + + auto [steam, leaf] = split_last(text, u'.'); + + ASSERT_THAT(leaf, IsEmpty()); +} + +TEST(StringUtils_split_last, leaf_is_empty_with_ending_dot) +{ + QString text; + + auto [steam, leaf] = split_last(text, u'.'); + + ASSERT_THAT(leaf, IsEmpty()); +} + +TEST(StringUtils_split_last, steam_is_empty_for_last_beginning_dot) +{ + QString text = ".bar"; + + auto [steam, leaf] = split_last(text, u'.'); + + ASSERT_THAT(steam, IsEmpty()); +} + +TEST(StringUtils_split_last, steam_is_empty) +{ + QString text = "."; + + auto [steam, leaf] = split_last(text, u'.'); + + ASSERT_THAT(steam, IsEmpty()); +} + +TEST(StringUtils_split_last, leaf) +{ + QString text = "foo.foo.bar"; + + auto [steam, leaf] = split_last(text, u'.'); + + ASSERT_THAT(leaf, u"bar"); +} + +TEST(StringUtils_split_last, leaf_for_not_dot) +{ + QString text = "bar"; + + auto [steam, leaf] = split_last(text, u'.'); + + ASSERT_THAT(leaf, u"bar"); +} + +TEST(StringUtils_split_last, steam) +{ + QString text = "foo.foo.bar"; + + auto [steam, leaf] = split_last(text, u'.'); + + ASSERT_THAT(steam, u"foo.foo"); +} + +TEST(StringUtils_split_last, no_steam_for_not_dot) +{ + QString text = "bar"; + + auto [steam, leaf] = split_last(text, u'.'); + + ASSERT_THAT(steam, IsEmpty()); +} +} // namespace diff --git a/tests/unit/tests/unittests/designercoreutils/version-test.cpp b/tests/unit/tests/unittests/qmldesignerutils/version-test.cpp similarity index 100% rename from tests/unit/tests/unittests/designercoreutils/version-test.cpp rename to tests/unit/tests/unittests/qmldesignerutils/version-test.cpp