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 <thomas.hartmann@qt.io>
This commit is contained in:
Marco Bubke
2025-04-28 15:53:36 +02:00
parent bd9213f231
commit 3025fedd03
14 changed files with 149 additions and 55 deletions

View File

@@ -239,7 +239,6 @@ extend_qtc_library(QmlDesignerCore
rewritingexception.h
signalhandlerproperty.h
sourcepathids.h
stringutils.h
synchronousimagecache.h
variantproperty.h
widgetregistration.h

View File

@@ -8,6 +8,8 @@
#include "model.h"
#include "model_p.h"
#include <qmldesignerutils/stringutils.h>
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());

View File

@@ -9,19 +9,21 @@
#include <QVector3D>
#include <QVector4D>
#include <qmldesignerutils/stringutils.h>
#include "bindingproperty.h"
#include "model.h"
#include "nodelistproperty.h"
#include "nodeproperty.h"
#include "signalhandlerproperty.h"
#include "stringutils.h"
#include "variantproperty.h"
#include <nodemetainfo.h>
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<QVector2D>();
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

View File

@@ -23,6 +23,8 @@
#include <signalhandlerproperty.h>
#include <variantproperty.h>
#include <qmldesignerutils/stringutils.h>
#include <qmljs/parser/qmljsengine_p.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljs/qmljssimplereader.h>
@@ -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;
}

View File

@@ -25,6 +25,7 @@
#include <import.h>
#include <modelutils.h>
#include <projectstorage/modulescanner.h>
#include <qmldesignerutils/stringutils.h>
#include <rewritingexception.h>
#include <enumeration.h>
@@ -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<QString,QStringList> map = ModelManagerInterface::instance()->filesInQrcPath(path);
const QStringList qrcFilePaths = map.value(fileName, {});

View File

@@ -17,6 +17,7 @@ add_qtc_library(QmlDesignerUtils STATIC
qmldesignerutils_global.h
version.cpp version.h
maputils.h
stringutils.h
)
extend_qtc_library(QmlDesignerUtils

View File

@@ -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 <QString>
#include <QStringView>
using namespace Qt::StringLiterals;
#include <algorithm>
#include <ranges>
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<typename T>
concept is_object = std::is_object_v<T>;
std::pair<QStringView, QStringView> split_last(is_object auto &&, QChar c) = delete; // remove rvalue overload
inline std::pair<QStringView, QStringView> 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

View File

@@ -14,11 +14,11 @@
#include "qmlstate.h"
#include "qmltimelinekeyframegroup.h"
#include "qmlvisualnode.h"
#include "stringutils.h"
#include "variantproperty.h"
#include <auxiliarydataproperties.h>
#include <designersettings.h>
#include <qmldesignerutils/stringutils.h>
#include <qmltimeline.h>
@@ -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;
}

View File

@@ -209,7 +209,6 @@ extend_qtc_library(TestDesignerCore
rewritingexception.h
signalhandlerproperty.h
sourcepathids.h
stringutils.h
synchronousimagecache.h
variantproperty.h
)

View File

@@ -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)

View File

@@ -1,9 +1,6 @@
# qmldesigner/designercore/model
extend_qtc_test(unittest
DEPENDS
QmlDesignerUtils
SOURCES
modelutils-test.cpp
uniquename-test.cpp
version-test.cpp
)

View File

@@ -0,0 +1,7 @@
extend_qtc_test(unittest
DEPENDS
QmlDesignerUtils
SOURCES
version-test.cpp
stringutils-test.cpp
)

View File

@@ -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 <googletest.h>
#include <qmldesignerutils/stringutils.h>
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