Skip to content

Commit 76721df

Browse files
authored
Refactoring of TextUtil.quote() to improve readability (#899)
* textutil: readability: comments * textutil: readability: rename vars * textutil: readability: bool atLineStart * textutil: readability: comments on a line * textutil: readability: const regexp * textutil: use QStringLiteral * TextUtil.quote(): make regexps static and use QStringLiteral * TextUtil.quote(): make followLinePattern regexps static * TextUtil.quote(): cleanup and comments
1 parent 17b6ef7 commit 76721df

File tree

1 file changed

+87
-37
lines changed

1 file changed

+87
-37
lines changed

src/textutil.cpp

Lines changed: 87 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,21 @@
88

99
#include <QTextDocument> // for escape()
1010

11-
// With Qt4 this func was more complex. Now we don't need it
1211
QString TextUtil::escape(const QString &plain) { return plain.toHtmlEscaped(); }
1312

13+
/**
14+
* Converts a given QString containing escaped XML/HTML entities into their unescaped equivalents.
15+
*
16+
* This function replaces specific XML/HTML escape sequences within the input string
17+
* with their corresponding special characters. The replacements include:
18+
* - "&lt;" with "<"
19+
* - "&gt;" with ">"
20+
* - "&quot;" with "\""
21+
* - "&amp;" with "&"
22+
*
23+
* @param escaped The QString containing escaped XML/HTML entities to be unescaped.
24+
* @return A QString where the escape sequences are replaced with their corresponding characters.
25+
*/
1426
QString TextUtil::unescape(const QString &escaped)
1527
{
1628
QString plain = escaped;
@@ -21,65 +33,103 @@ QString TextUtil::unescape(const QString &escaped)
2133
return plain;
2234
}
2335

36+
/**
37+
* Quote text for a response with "> " prefix.
38+
* Nested quotes and word wrapping are supported.
39+
* assert(TextUtil::quote("Hello world", 80, true) == "> Hello world\n\n")
40+
* // Line wrapping
41+
* assert(TextUtil::quote("This is a very long line that should be wrapped", 20, true) == "> This is a very\n> long line that should be\n> wrapped\n\n")
42+
* // Empty lines
43+
* assert(TextUtil::quote("Line1\n\nLine2", 80, false) == "> Line1\n\n> Line2\n\n")
44+
* // Empty lines: quoteEmpty
45+
* assert(TextUtil::quote("Line1\n\nLine2", 80, true) == "> Line1\n>\n> Line2\n\n")
46+
* @param toquote text to quote
47+
* @param width width where to wrap text
48+
* @param quoteEmpty add > to empty lines
49+
* @return
50+
*/
2451
QString TextUtil::quote(const QString &toquote, int width, bool quoteEmpty)
2552
{
26-
int ql = 0, col = 0, atstart = 1, ls = 0;
27-
28-
QString quoted = "> " + toquote; // quote first line
29-
QString rxs = quoteEmpty ? "\n" : "\n(?!\\s*\n)";
30-
QRegularExpression rx(rxs); // quote following lines
31-
quoted.replace(rx, "\n> ");
32-
rx.setPattern("> +>"); // compress > > > > quotes to >>>>
33-
quoted.replace(rx, ">>");
34-
quoted.replace(rx, ">>");
35-
quoted.replace(QRegularExpression(" +\n"), "\n"); // remove trailing spaces
53+
int quoteLevel = 0; // amount of leading '>' in the current line
54+
int column = 0; // current column
55+
bool atLineStart = true; // at beginning of line
56+
int lastSpaceIndex = 0; // index of last whitespace to break line
57+
58+
static const QRegularExpression rxTrimTrailingSpaces(QStringLiteral(" +\n"));
59+
static const QRegularExpression rxUnquote1(QStringLiteral("^>+\n"));
60+
static const QRegularExpression rxUnquote2(QStringLiteral("\n>+\n"));
61+
static const QRegularExpression rxFollowLineEmpty(QStringLiteral("\n"));
62+
static const QRegularExpression rxFollowLinePattern(QStringLiteral("\n(?!\\s*\n)"));
63+
static const QRegularExpression rxCompress(QStringLiteral("> +>"));
64+
65+
// quote first line
66+
QString quoted = QStringLiteral("> ") + toquote;
67+
QRegularExpression rx = quoteEmpty ? rxFollowLineEmpty : rxFollowLinePattern;
68+
// quote the following lines
69+
quoted.replace(rx, QStringLiteral("\n> "));
70+
// compress > > > > quotes to >>>>
71+
quoted.replace(rxCompress, QStringLiteral(">>"));
72+
quoted.replace(rxCompress, QStringLiteral(">>"));
73+
// remove trailing spaces before a newline
74+
quoted.replace(rxTrimTrailingSpaces, QStringLiteral("\n"));
3675

3776
if (!quoteEmpty) {
38-
quoted.replace(QRegularExpression("^>+\n"), "\n\n"); // unquote empty lines
39-
quoted.replace(QRegularExpression("\n>+\n"), "\n\n");
77+
// unquote empty lines
78+
quoted.replace(rxUnquote1, QStringLiteral("\n\n"));
79+
quoted.replace(rxUnquote2, QStringLiteral("\n\n"));
4080
}
4181

4282
for (int i = 0; i < int(quoted.length()); i++) {
43-
col++;
44-
if (atstart && quoted[i] == '>')
45-
ql++;
46-
else
47-
atstart = 0;
83+
column++;
84+
if (atLineStart && quoted[i] == '>') {
85+
quoteLevel++;
86+
} else {
87+
atLineStart = false;
88+
}
4889

4990
switch (quoted[i].toLatin1()) {
5091
case '\n':
51-
ql = col = 0;
52-
atstart = 1;
92+
// Reset state at a newline
93+
quoteLevel = 0;
94+
column = 0;
95+
atLineStart = true;
5396
break;
5497
case ' ':
5598
case '\t':
56-
ls = i;
99+
lastSpaceIndex = i;
57100
break;
58101
}
59102
if (quoted[i] == '\n') {
60-
ql = 0;
61-
atstart = 1;
103+
quoteLevel = 0;
104+
atLineStart = true;
62105
}
63106

64-
if (col > width) {
65-
if ((ls + width) < i) {
66-
ls = i;
107+
if (column > width) {
108+
// If we have no breakable space in range, advance to the next whitespace
109+
if ((lastSpaceIndex + width) < i) {
110+
// advance to the next whitespace from the position
111+
lastSpaceIndex = i;
67112
i = quoted.length();
68-
while ((ls < i) && !quoted[ls].isSpace())
69-
ls++;
70-
i = ls;
113+
while ((lastSpaceIndex < i) && !quoted[lastSpaceIndex].isSpace()) {
114+
lastSpaceIndex++;
115+
}
116+
i = lastSpaceIndex;
71117
}
72-
if ((i < int(quoted.length())) && (quoted[ls] != '\n')) {
73-
quoted.insert(ls, '\n');
74-
++ls;
75-
quoted.insert(ls, QString().fill('>', ql));
76-
i += ql + 1;
77-
col = 0;
118+
if ((i < int(quoted.length())) && (quoted[lastSpaceIndex] != '\n')) {
119+
// Insert newline at lastSpaceIndex
120+
quoted.insert(lastSpaceIndex, QLatin1Char('\n'));
121+
++lastSpaceIndex;
122+
// Re-insert the current quote prefix to the next line
123+
quoted.insert(lastSpaceIndex, QString().fill(QLatin1Char('>'), quoteLevel));
124+
// count of inserted '>' chars
125+
i += (quoteLevel + 1);
126+
// Reset wrapping state for the new line
127+
column = 0;
78128
}
79129
}
80130
}
81-
quoted += "\n\n"; // add two empty lines to quoted text - the cursor
82-
// will be positioned at the end of those.
131+
// add two empty lines to the quoted text - the cursor will be positioned at the end of those.
132+
quoted += QStringLiteral("\n\n");
83133
return quoted;
84134
}
85135

0 commit comments

Comments
 (0)