Skip to content

Commit

Permalink
QTextMarkdownWriter: Handle lists in blockquotes correctly
Browse files Browse the repository at this point in the history
But we do not yet handle a blockquote in a list item. Presumably
that's less common anyway.

We now also continue block-quote prefixes onto blank lines
within a block quote, which looks more normal.

Task-number: QTBUG-104997
Change-Id: I2b5642cf3a0c81a94444a33f026a02ad53e7e6bb
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
(cherry picked from commit f3e528b)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
  • Loading branch information
ec1oud authored and Qt Cherry-pick Bot committed Feb 14, 2024
1 parent a1413a6 commit 3b4901d
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 20 deletions.
68 changes: 51 additions & 17 deletions src/gui/text/qtextmarkdownwriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,22 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
// suppress needless blank lines, when there will be a big change in block format
bool nextIsDifferent = false;
bool ending = false;
int blockQuoteIndent = 0;
int nextBlockQuoteIndent = 0;
{
QTextFrame::iterator next = iterator;
++next;
QTextBlockFormat format = iterator.currentBlock().blockFormat();
QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
blockQuoteIndent = format.intProperty(QTextFormat::BlockQuoteLevel);
nextBlockQuoteIndent = nextFormat.intProperty(QTextFormat::BlockQuoteLevel);
if (next.atEnd()) {
nextIsDifferent = true;
ending = true;
} else {
QTextBlockFormat format = iterator.currentBlock().blockFormat();
QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
if (nextFormat.indent() != format.indent() ||
nextFormat.property(QTextFormat::BlockCodeLanguage) != format.property(QTextFormat::BlockCodeLanguage))
nextFormat.property(QTextFormat::BlockCodeLanguage) !=
format.property(QTextFormat::BlockCodeLanguage))
nextIsDifferent = true;
}
}
Expand All @@ -139,8 +144,10 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
tableRow = cell.row();
}
} else if (!block.textList()) {
if (lastWasList)
if (lastWasList) {
m_stream << qtmw_Newline;
m_linePrefixWritten = false;
}
}
int endingCol = writeBlock(block, !table, table && tableRow == 0,
nextIsDifferent && !block.textList());
Expand All @@ -164,8 +171,16 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
} else if (endingCol > 0) {
if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) {
m_stream << qtmw_Newline;
if (block.textList()) {
m_stream << m_linePrefix;
m_linePrefixWritten = true;
}
} else {
m_stream << qtmw_Newline << qtmw_Newline;
m_stream << qtmw_Newline;
if (nextBlockQuoteIndent < blockQuoteIndent)
setLinePrefixForBlockQuote(nextBlockQuoteIndent);
m_stream << m_linePrefix;
m_stream << qtmw_Newline;
m_doubleNewlineWritten = true;
}
}
Expand Down Expand Up @@ -211,6 +226,16 @@ QTextMarkdownWriter::ListInfo QTextMarkdownWriter::listInfo(QTextList *list)
return m_listInfo.value(list);
}

void QTextMarkdownWriter::setLinePrefixForBlockQuote(int level)
{
m_linePrefix.clear();
if (level > 0) {
m_linePrefix.reserve(level * 2);
for (int i = 0; i < level; ++i)
m_linePrefix += u"> ";
}
}

static int nearestWordWrapIndex(const QString &s, int before)
{
before = qMin(before, s.size());
Expand Down Expand Up @@ -336,10 +361,20 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
const bool codeBlock = blockFmt.hasProperty(QTextFormat::BlockCodeFence) ||
blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).size() > 0 ||
blockFmt.nonBreakableLines();
const int blockQuoteLevel = blockFmt.intProperty(QTextFormat::BlockQuoteLevel);
if (m_fencedCodeBlock && !codeBlock) {
m_stream << m_linePrefix << m_codeBlockFence << qtmw_Newline;
m_fencedCodeBlock = false;
m_codeBlockFence.clear();
m_linePrefixWritten = m_linePrefix.size() > 0;
}
m_linePrefix.clear();
if (!blockFmt.headingLevel() && blockQuoteLevel > 0) {
setLinePrefixForBlockQuote(blockQuoteLevel);
if (!m_linePrefixWritten) {
m_stream << m_linePrefix;
m_linePrefixWritten = true;
}
}
if (block.textList()) { // it's a list-item
auto fmt = block.textList()->format();
Expand Down Expand Up @@ -416,31 +451,25 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
if (blockFmt.hasProperty(QTextFormat::BlockIndent))
m_codeBlockFence = QString(m_wrappedLineIndent, qtmw_Space) + m_codeBlockFence;
// A block quote can contain an indented code block, but not vice-versa.
m_stream << m_linePrefix << m_codeBlockFence
<< blockFmt.stringProperty(QTextFormat::BlockCodeLanguage) << qtmw_Newline;
m_stream << m_codeBlockFence << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage)
<< qtmw_Newline << m_linePrefix;
m_fencedCodeBlock = true;
}
wrap = false;
} else if (!blockFmt.indent()) {
m_wrappedLineIndent = 0;
m_linePrefix.clear();
if (blockFmt.hasProperty(QTextFormat::BlockQuoteLevel)) {
int level = blockFmt.intProperty(QTextFormat::BlockQuoteLevel);
QString quoteMarker = QStringLiteral("> ");
m_linePrefix.reserve(level * 2);
for (int i = 0; i < level; ++i)
m_linePrefix += quoteMarker;
}
if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) {
// A block quote can contain an indented code block, but not vice-versa.
m_linePrefix += QString(4, qtmw_Space);
m_indentedCodeBlock = true;
}
if (!m_linePrefixWritten) {
m_stream << m_linePrefix;
m_linePrefixWritten = true;
}
}
if (blockFmt.headingLevel())
m_stream << QByteArray(blockFmt.headingLevel(), '#') << ' ';
else
m_stream << m_linePrefix;

QString wrapIndentString = m_linePrefix + QString(m_wrappedLineIndent, qtmw_Space);
// It would be convenient if QTextStream had a lineCharPos() accessor,
Expand Down Expand Up @@ -584,6 +613,10 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
i = j + 1;
}
} else {
if (!m_linePrefixWritten && col == wrapIndentString.size()) {
m_stream << m_linePrefix;
col += m_linePrefix.size();
}
m_stream << markers << fragmentText;
col += markers.size() + fragmentText.size();
}
Expand Down Expand Up @@ -615,6 +648,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
}
if (missedBlankCodeBlockLine)
m_stream << qtmw_Newline;
m_linePrefixWritten = false;
return col;
}

Expand Down
2 changes: 2 additions & 0 deletions src/gui/text/qtextmarkdownwriter_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Q_GUI_EXPORT QTextMarkdownWriter
};

ListInfo listInfo(QTextList *list);
void setLinePrefixForBlockQuote(int level);

private:
QTextStream &m_stream;
Expand All @@ -53,6 +54,7 @@ class Q_GUI_EXPORT QTextMarkdownWriter
int m_wrappedLineIndent = 0;
int m_lastListIndent = 1;
bool m_doubleNewlineWritten = false;
bool m_linePrefixWritten = false;
bool m_indentedCodeBlock = false;
bool m_fencedCodeBlock = false;
};
Expand Down
6 changes: 3 additions & 3 deletions tests/auto/gui/text/qtextmarkdownwriter/data/blockquotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ MacFarlane writes:

> What distinguishes Markdown from many other lightweight markup syntaxes,
> which are often easier to write, is its readability. As Gruber writes:
>
> > The overriding design goal for Markdown's formatting syntax is to make it
> > as readable as possible. The idea is that a Markdown-formatted document should
> > be publishable as-is, as plain text, without looking like it's been marked up
> > with tags or formatting instructions. (
> > <http://daringfireball.net/projects/markdown/> )
>
> The point can be illustrated by comparing a sample of AsciiDoc with an
> equivalent sample of Markdown. Here is a sample of AsciiDoc from the AsciiDoc
> manual:
>
> ```AsciiDoc
> 1. List item one.
> +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
What if we have a quotation containing a list?

> First some quoted text, and then a list:
>
> - one
> - two is longer and has enough words to form a paragraph with text continuing
> onto the next line
>
> enough of that, let's try a numbered list
>
> 1. List item one
> 2. List item two is longer and has enough words to form a paragraph with
> text continuing onto the next line.
>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
What if we have a list item containing a block quote?

- one
- > two is longer and has enough words to form a paragraph with text continuing
> onto the next line
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,8 @@ void tst_QTextMarkdownWriter::rewriteDocument_data()
QTest::addColumn<QString>("inputFile");

QTest::newRow("block quotes") << "blockquotes.md";
QTest::newRow("block quotes with lists") << "blockquotesWithLists.md";
// QTest::newRow("list item with block quote") << "listItemWithBlockquote.md"; // not supported for now
QTest::newRow("example") << "example.md";
QTest::newRow("list items after headings") << "headingsAndLists.md";
QTest::newRow("word wrap") << "wordWrap.md";
Expand Down Expand Up @@ -629,6 +631,8 @@ void tst_QTextMarkdownWriter::fromHtml()
}
#endif

output = output.trimmed();
expectedOutput = expectedOutput.trimmed();
if (output != expectedOutput && (isMainFontFixed() || isFixedFontProportional()))
QEXPECT_FAIL("", "fixed main font or proportional fixed font (QTBUG-103484)", Continue);
QCOMPARE(output, expectedOutput);
Expand Down

0 comments on commit 3b4901d

Please sign in to comment.