QTextMarkdownWriter: Handle lists in blockquotes correctly
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 f3e528b97f6836b497953935e1dd27fee134e68a) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
a1413a6ecd
commit
3b4901d2d4
@ -112,17 +112,22 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
|
|||||||
// suppress needless blank lines, when there will be a big change in block format
|
// suppress needless blank lines, when there will be a big change in block format
|
||||||
bool nextIsDifferent = false;
|
bool nextIsDifferent = false;
|
||||||
bool ending = false;
|
bool ending = false;
|
||||||
|
int blockQuoteIndent = 0;
|
||||||
|
int nextBlockQuoteIndent = 0;
|
||||||
{
|
{
|
||||||
QTextFrame::iterator next = iterator;
|
QTextFrame::iterator next = iterator;
|
||||||
++next;
|
++next;
|
||||||
|
QTextBlockFormat format = iterator.currentBlock().blockFormat();
|
||||||
|
QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
|
||||||
|
blockQuoteIndent = format.intProperty(QTextFormat::BlockQuoteLevel);
|
||||||
|
nextBlockQuoteIndent = nextFormat.intProperty(QTextFormat::BlockQuoteLevel);
|
||||||
if (next.atEnd()) {
|
if (next.atEnd()) {
|
||||||
nextIsDifferent = true;
|
nextIsDifferent = true;
|
||||||
ending = true;
|
ending = true;
|
||||||
} else {
|
} else {
|
||||||
QTextBlockFormat format = iterator.currentBlock().blockFormat();
|
|
||||||
QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
|
|
||||||
if (nextFormat.indent() != format.indent() ||
|
if (nextFormat.indent() != format.indent() ||
|
||||||
nextFormat.property(QTextFormat::BlockCodeLanguage) != format.property(QTextFormat::BlockCodeLanguage))
|
nextFormat.property(QTextFormat::BlockCodeLanguage) !=
|
||||||
|
format.property(QTextFormat::BlockCodeLanguage))
|
||||||
nextIsDifferent = true;
|
nextIsDifferent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,8 +144,10 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
|
|||||||
tableRow = cell.row();
|
tableRow = cell.row();
|
||||||
}
|
}
|
||||||
} else if (!block.textList()) {
|
} else if (!block.textList()) {
|
||||||
if (lastWasList)
|
if (lastWasList) {
|
||||||
m_stream << qtmw_Newline;
|
m_stream << qtmw_Newline;
|
||||||
|
m_linePrefixWritten = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int endingCol = writeBlock(block, !table, table && tableRow == 0,
|
int endingCol = writeBlock(block, !table, table && tableRow == 0,
|
||||||
nextIsDifferent && !block.textList());
|
nextIsDifferent && !block.textList());
|
||||||
@ -164,8 +171,16 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
|
|||||||
} else if (endingCol > 0) {
|
} else if (endingCol > 0) {
|
||||||
if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) {
|
if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) {
|
||||||
m_stream << qtmw_Newline;
|
m_stream << qtmw_Newline;
|
||||||
|
if (block.textList()) {
|
||||||
|
m_stream << m_linePrefix;
|
||||||
|
m_linePrefixWritten = true;
|
||||||
|
}
|
||||||
} else {
|
} 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;
|
m_doubleNewlineWritten = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,6 +226,16 @@ QTextMarkdownWriter::ListInfo QTextMarkdownWriter::listInfo(QTextList *list)
|
|||||||
return m_listInfo.value(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)
|
static int nearestWordWrapIndex(const QString &s, int before)
|
||||||
{
|
{
|
||||||
before = qMin(before, s.size());
|
before = qMin(before, s.size());
|
||||||
@ -336,10 +361,20 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
|||||||
const bool codeBlock = blockFmt.hasProperty(QTextFormat::BlockCodeFence) ||
|
const bool codeBlock = blockFmt.hasProperty(QTextFormat::BlockCodeFence) ||
|
||||||
blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).size() > 0 ||
|
blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).size() > 0 ||
|
||||||
blockFmt.nonBreakableLines();
|
blockFmt.nonBreakableLines();
|
||||||
|
const int blockQuoteLevel = blockFmt.intProperty(QTextFormat::BlockQuoteLevel);
|
||||||
if (m_fencedCodeBlock && !codeBlock) {
|
if (m_fencedCodeBlock && !codeBlock) {
|
||||||
m_stream << m_linePrefix << m_codeBlockFence << qtmw_Newline;
|
m_stream << m_linePrefix << m_codeBlockFence << qtmw_Newline;
|
||||||
m_fencedCodeBlock = false;
|
m_fencedCodeBlock = false;
|
||||||
m_codeBlockFence.clear();
|
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
|
if (block.textList()) { // it's a list-item
|
||||||
auto fmt = block.textList()->format();
|
auto fmt = block.textList()->format();
|
||||||
@ -416,31 +451,25 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
|||||||
if (blockFmt.hasProperty(QTextFormat::BlockIndent))
|
if (blockFmt.hasProperty(QTextFormat::BlockIndent))
|
||||||
m_codeBlockFence = QString(m_wrappedLineIndent, qtmw_Space) + m_codeBlockFence;
|
m_codeBlockFence = QString(m_wrappedLineIndent, qtmw_Space) + m_codeBlockFence;
|
||||||
// A block quote can contain an indented code block, but not vice-versa.
|
// A block quote can contain an indented code block, but not vice-versa.
|
||||||
m_stream << m_linePrefix << m_codeBlockFence
|
m_stream << m_codeBlockFence << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage)
|
||||||
<< blockFmt.stringProperty(QTextFormat::BlockCodeLanguage) << qtmw_Newline;
|
<< qtmw_Newline << m_linePrefix;
|
||||||
m_fencedCodeBlock = true;
|
m_fencedCodeBlock = true;
|
||||||
}
|
}
|
||||||
wrap = false;
|
wrap = false;
|
||||||
} else if (!blockFmt.indent()) {
|
} else if (!blockFmt.indent()) {
|
||||||
m_wrappedLineIndent = 0;
|
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)) {
|
if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) {
|
||||||
// A block quote can contain an indented code block, but not vice-versa.
|
// A block quote can contain an indented code block, but not vice-versa.
|
||||||
m_linePrefix += QString(4, qtmw_Space);
|
m_linePrefix += QString(4, qtmw_Space);
|
||||||
m_indentedCodeBlock = true;
|
m_indentedCodeBlock = true;
|
||||||
}
|
}
|
||||||
|
if (!m_linePrefixWritten) {
|
||||||
|
m_stream << m_linePrefix;
|
||||||
|
m_linePrefixWritten = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (blockFmt.headingLevel())
|
if (blockFmt.headingLevel())
|
||||||
m_stream << QByteArray(blockFmt.headingLevel(), '#') << ' ';
|
m_stream << QByteArray(blockFmt.headingLevel(), '#') << ' ';
|
||||||
else
|
|
||||||
m_stream << m_linePrefix;
|
|
||||||
|
|
||||||
QString wrapIndentString = m_linePrefix + QString(m_wrappedLineIndent, qtmw_Space);
|
QString wrapIndentString = m_linePrefix + QString(m_wrappedLineIndent, qtmw_Space);
|
||||||
// It would be convenient if QTextStream had a lineCharPos() accessor,
|
// It would be convenient if QTextStream had a lineCharPos() accessor,
|
||||||
@ -584,6 +613,10 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
|||||||
i = j + 1;
|
i = j + 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (!m_linePrefixWritten && col == wrapIndentString.size()) {
|
||||||
|
m_stream << m_linePrefix;
|
||||||
|
col += m_linePrefix.size();
|
||||||
|
}
|
||||||
m_stream << markers << fragmentText;
|
m_stream << markers << fragmentText;
|
||||||
col += markers.size() + fragmentText.size();
|
col += markers.size() + fragmentText.size();
|
||||||
}
|
}
|
||||||
@ -615,6 +648,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
|||||||
}
|
}
|
||||||
if (missedBlankCodeBlockLine)
|
if (missedBlankCodeBlockLine)
|
||||||
m_stream << qtmw_Newline;
|
m_stream << qtmw_Newline;
|
||||||
|
m_linePrefixWritten = false;
|
||||||
return col;
|
return col;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
ListInfo listInfo(QTextList *list);
|
ListInfo listInfo(QTextList *list);
|
||||||
|
void setLinePrefixForBlockQuote(int level);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTextStream &m_stream;
|
QTextStream &m_stream;
|
||||||
@ -53,6 +54,7 @@ private:
|
|||||||
int m_wrappedLineIndent = 0;
|
int m_wrappedLineIndent = 0;
|
||||||
int m_lastListIndent = 1;
|
int m_lastListIndent = 1;
|
||||||
bool m_doubleNewlineWritten = false;
|
bool m_doubleNewlineWritten = false;
|
||||||
|
bool m_linePrefixWritten = false;
|
||||||
bool m_indentedCodeBlock = false;
|
bool m_indentedCodeBlock = false;
|
||||||
bool m_fencedCodeBlock = false;
|
bool m_fencedCodeBlock = false;
|
||||||
};
|
};
|
||||||
|
@ -8,17 +8,17 @@ MacFarlane writes:
|
|||||||
|
|
||||||
> What distinguishes Markdown from many other lightweight markup syntaxes,
|
> What distinguishes Markdown from many other lightweight markup syntaxes,
|
||||||
> which are often easier to write, is its readability. As Gruber writes:
|
> 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
|
> > 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
|
> > 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
|
> > be publishable as-is, as plain text, without looking like it's been marked up
|
||||||
> > with tags or formatting instructions. (
|
> > with tags or formatting instructions. (
|
||||||
> > <http://daringfireball.net/projects/markdown/> )
|
> > <http://daringfireball.net/projects/markdown/> )
|
||||||
|
>
|
||||||
> The point can be illustrated by comparing a sample of AsciiDoc with an
|
> 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
|
> equivalent sample of Markdown. Here is a sample of AsciiDoc from the AsciiDoc
|
||||||
> manual:
|
> manual:
|
||||||
|
>
|
||||||
> ```AsciiDoc
|
> ```AsciiDoc
|
||||||
> 1. List item one.
|
> 1. List item one.
|
||||||
> +
|
> +
|
||||||
|
@ -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.
|
||||||
|
>
|
@ -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
|
||||||
|
|
@ -530,6 +530,8 @@ void tst_QTextMarkdownWriter::rewriteDocument_data()
|
|||||||
QTest::addColumn<QString>("inputFile");
|
QTest::addColumn<QString>("inputFile");
|
||||||
|
|
||||||
QTest::newRow("block quotes") << "blockquotes.md";
|
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("example") << "example.md";
|
||||||
QTest::newRow("list items after headings") << "headingsAndLists.md";
|
QTest::newRow("list items after headings") << "headingsAndLists.md";
|
||||||
QTest::newRow("word wrap") << "wordWrap.md";
|
QTest::newRow("word wrap") << "wordWrap.md";
|
||||||
@ -629,6 +631,8 @@ void tst_QTextMarkdownWriter::fromHtml()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
output = output.trimmed();
|
||||||
|
expectedOutput = expectedOutput.trimmed();
|
||||||
if (output != expectedOutput && (isMainFontFixed() || isFixedFontProportional()))
|
if (output != expectedOutput && (isMainFontFixed() || isFixedFontProportional()))
|
||||||
QEXPECT_FAIL("", "fixed main font or proportional fixed font (QTBUG-103484)", Continue);
|
QEXPECT_FAIL("", "fixed main font or proportional fixed font (QTBUG-103484)", Continue);
|
||||||
QCOMPARE(output, expectedOutput);
|
QCOMPARE(output, expectedOutput);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user