diff --git a/autotests/syntaxrepository_test.cpp b/autotests/syntaxrepository_test.cpp --- a/autotests/syntaxrepository_test.cpp +++ b/autotests/syntaxrepository_test.cpp @@ -253,7 +253,7 @@ QVERIFY(customTheme.isValid()); } - void testEmptyDefinition() + void testInvalidDefinition() { Definition def; QVERIFY(!def.isValid()); @@ -277,8 +277,25 @@ QVERIFY(def.formats().isEmpty()); QVERIFY(def.includedDefinitions().isEmpty()); - for (QChar c : QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~")) + for (QChar c : QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~")) { QVERIFY(def.isWordDelimiter(c)); + QVERIFY(def.isWordWrapDelimiter(c)); + } + } + + void testDelimiters() + { + auto def = m_repo.definitionForName(QLatin1String("LaTeX")); + QVERIFY(def.isValid()); + + // check that backslash '\' is removed + for (QChar c : QStringLiteral("\t !%&()*+,-./:;<=>?[]^{|}~")) + QVERIFY(def.isWordDelimiter(c)); + QVERIFY(!def.isWordDelimiter(QLatin1Char('\\'))); + + // check where breaking a line is valid + for (QChar c : QStringLiteral(",{}[]")) + QVERIFY(def.isWordWrapDelimiter(c)); } }; } diff --git a/src/lib/definition.h b/src/lib/definition.h --- a/src/lib/definition.h +++ b/src/lib/definition.h @@ -171,9 +171,40 @@ * characters: \\t !%&()*+,-./:;<=>?[\\]^{|}~ * * @since 5.50 + * @see isWordWrapDelimiter() */ bool isWordDelimiter(QChar c) const; + /** + * Returns whether it is safe to break a line at before the character @c. + * This is useful when wrapping a line e.g. by applying static word wrap. + * + * As example, consider the LaTeX code + * @code + * \command1\command2 + * @endcode + * Applying static word wrap could lead to the following code: + * @code + * \command1\ + * command2 + * @endcode + * command2 without a leading backslash is invalid in LaTeX. If '\\' is set + * as word wrap delimiter, isWordWrapDelimiter('\\') then returns true, + * meaning that it is safe to break the line before @c. The resulting code + * then would be + * @code + * \command1 + * \command2 + * @endcode + * + * @note By default, the word wrap delimiters are equal to the word + * delimiters in isWordDelimiter(). + * + * @since 5.50 + * @see isWordDelimiter() + */ + bool isWordWrapDelimiter(QChar c) const; + /** * Returns whether indentation-based folding is enabled. * An example for indentation-based folding is Python. diff --git a/src/lib/definition.cpp b/src/lib/definition.cpp --- a/src/lib/definition.cpp +++ b/src/lib/definition.cpp @@ -44,7 +44,8 @@ using namespace KSyntaxHighlighting; DefinitionData::DefinitionData() - : delimiters(QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~")) // must be sorted! + : wordDelimiters(QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~")) // must be sorted! + , wordWrapDelimiters(wordDelimiters) { } @@ -175,6 +176,12 @@ return d->isWordDelimiter(c); } +bool Definition::isWordWrapDelimiter(QChar c) const +{ + d->load(); + return std::binary_search(d->wordWrapDelimiters.constBegin(), d->wordWrapDelimiters.constEnd(), c); +} + bool Definition::indentationBasedFoldingEnabled() const { d->load(); @@ -265,7 +272,7 @@ bool DefinitionData::isWordDelimiter(QChar c) const { - return std::binary_search(delimiters.constBegin(), delimiters.constEnd(), c); + return std::binary_search(wordDelimiters.constBegin(), wordDelimiters.constEnd(), c); } Format DefinitionData::formatByName(const QString& name) const @@ -315,7 +322,7 @@ context->resolveIncludes(); } - Q_ASSERT(std::is_sorted(delimiters.constBegin(), delimiters.constEnd())); + Q_ASSERT(std::is_sorted(wordDelimiters.constBegin(), wordDelimiters.constEnd())); return true; } @@ -335,7 +342,8 @@ license.clear(); mimetypes.clear(); extensions.clear(); - delimiters = QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~"); // must be sorted! + wordDelimiters = QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~"); // must be sorted! + wordWrapDelimiters = wordDelimiters; caseSensitive = Qt::CaseSensitive; version = 0.0f; priority = 0; @@ -515,12 +523,20 @@ if (reader.name() == QLatin1String("keywords")) { if (reader.attributes().hasAttribute(QStringLiteral("casesensitive"))) caseSensitive = Xml::attrToBool(reader.attributes().value(QStringLiteral("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive; - delimiters += reader.attributes().value(QStringLiteral("additionalDeliminator")); - std::sort(delimiters.begin(), delimiters.end()); - auto it = std::unique(delimiters.begin(), delimiters.end()); - delimiters.truncate(std::distance(delimiters.begin(), it)); + + // adapt sorted wordDelimiters + wordDelimiters += reader.attributes().value(QStringLiteral("additionalDeliminator")); + std::sort(wordDelimiters.begin(), wordDelimiters.end()); + auto it = std::unique(wordDelimiters.begin(), wordDelimiters.end()); + wordDelimiters.truncate(std::distance(wordDelimiters.begin(), it)); foreach (const auto c, reader.attributes().value(QLatin1String("weakDeliminator"))) - delimiters.remove(c); + wordDelimiters.remove(c); + + // adaptWordWrapDelimiters, and sort + wordWrapDelimiters = reader.attributes().value(QStringLiteral("wordWrapDeliminator")).toString(); + std::sort(wordWrapDelimiters.begin(), wordWrapDelimiters.end()); + if (wordWrapDelimiters.isEmpty()) + wordWrapDelimiters = wordDelimiters; } else if (reader.name() == QLatin1String("folding")) { if (reader.attributes().hasAttribute(QStringLiteral("indentationsensitive"))) indentationBasedFolding = Xml::attrToBool(reader.attributes().value(QStringLiteral("indentationsensitive"))); diff --git a/src/lib/definition_p.h b/src/lib/definition_p.h --- a/src/lib/definition_p.h +++ b/src/lib/definition_p.h @@ -73,7 +73,8 @@ QHash keywordLists; QVector contexts; QHash formats; - QString delimiters; + QString wordDelimiters; + QString wordWrapDelimiters; bool indentationBasedFolding = false; QStringList foldingIgnoreList;