Convert <pre> to Markdown ``` and vice-versa with nonBreakableLines
The HTML parser calls QTextBlockFormat::setNonBreakableLines(true) when it sees a <pre> tag; so for symmetry, the markdown reader now does the same when it sees a fenced code block, and the markdown writer honors the nonBreakableLines property by writing a fenced code block. This preserves the meaning better when reading HTML and writing markdown or vice-versa, without modifying HTML reading or writing code. Added a test tst_QTextMarkdownImporter::fencedCodeBlocks() which unfortunately also highlights a known bug in the markdown reader: each fenced code block ends with an extra empty block. That can be fixed separately. tst_QTextMarkdownWriter::fromHtml(preformats with embedded backticks) that we re-enabled in 1abaf9d5d6ea9c6554362e851903ddd214a6f659 was not a very useful test: ``` with a space and some words but no newline is not a fence: it's just like a `monospace` span. We have had trouble with those in CI because of missing monospace fonts, or inconsistency when a supposedly mono font's QFontInfo::fixedPitch() returns false. So just test proper <pre>/fence conversion for now. Pick-to: 6.3 Fixes: QTBUG-100515 Fixes: QTBUG-100981 Task-number: QTBUG-101031 Change-Id: I88f0ede0810d8a9480b30eb0cd780e1af67cc5f2 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
This commit is contained in:
parent
53604df833
commit
04a60bb033
@ -571,8 +571,10 @@ void QTextMarkdownImporter::insertBlock()
|
||||
}
|
||||
if (m_codeBlock) {
|
||||
blockFormat.setProperty(QTextFormat::BlockCodeLanguage, m_blockCodeLanguage);
|
||||
if (m_blockCodeFence)
|
||||
if (m_blockCodeFence) {
|
||||
blockFormat.setNonBreakableLines(true);
|
||||
blockFormat.setProperty(QTextFormat::BlockCodeFence, QString(QLatin1Char(m_blockCodeFence)));
|
||||
}
|
||||
charFormat.setFont(m_monoFont);
|
||||
} else {
|
||||
blockFormat.setTopMargin(m_paragraphMargin);
|
||||
|
@ -334,7 +334,8 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
||||
QTextBlockFormat blockFmt = block.blockFormat();
|
||||
bool missedBlankCodeBlockLine = false;
|
||||
const bool codeBlock = blockFmt.hasProperty(QTextFormat::BlockCodeFence) ||
|
||||
blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).length() > 0;
|
||||
blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).length() > 0 ||
|
||||
blockFmt.nonBreakableLines();
|
||||
if (m_fencedCodeBlock && !codeBlock) {
|
||||
m_stream << m_linePrefix << m_codeBlockFence << Newline;
|
||||
m_fencedCodeBlock = false;
|
||||
|
@ -41,6 +41,8 @@ private slots:
|
||||
void fragmentsAndProperties();
|
||||
void pathological_data();
|
||||
void pathological();
|
||||
void fencedCodeBlocks_data();
|
||||
void fencedCodeBlocks();
|
||||
|
||||
private:
|
||||
bool isMainFontFixed();
|
||||
@ -490,5 +492,78 @@ void tst_QTextMarkdownImporter::pathological() // avoid crashing on crazy input
|
||||
QTextDocument().setMarkdown(f.readAll());
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownImporter::fencedCodeBlocks_data()
|
||||
{
|
||||
QTest::addColumn<QString>("input");
|
||||
QTest::addColumn<int>("expectedCodeBlockCount");
|
||||
QTest::addColumn<int>("expectedPlainBlockCount");
|
||||
QTest::addColumn<QString>("expectedLanguage");
|
||||
QTest::addColumn<QString>("expectedFenceChar");
|
||||
QTest::addColumn<QString>("rewrite");
|
||||
|
||||
// TODO shouldn't add empty blocks: QTBUG-101031
|
||||
QTest::newRow("backtick fence with language")
|
||||
<< "```pseudocode\nprint('hello world\\n')\n```\n"
|
||||
<< 2 << 0 << "pseudocode" << "`"
|
||||
<< "```pseudocode\nprint('hello world\\n')\n```\n\n";
|
||||
QTest::newRow("tilde fence with language")
|
||||
<< "~~~pseudocode\nprint('hello world\\n')\n~~~\n"
|
||||
<< 2 << 0 << "pseudocode" << "~"
|
||||
<< "~~~pseudocode\nprint('hello world\\n')\n~~~\n\n";
|
||||
QTest::newRow("embedded backticks")
|
||||
<< "```\nnone `one` ``two``\n```\nplain\n```\n```three``` ````four````\n```\nplain\n"
|
||||
<< 4 << 2 << QString() << "`"
|
||||
<< "```\nnone `one` ``two``\n```\nplain\n\n```\n```three``` ````four````\n```\nplain\n\n";
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownImporter::fencedCodeBlocks()
|
||||
{
|
||||
QFETCH(QString, input);
|
||||
QFETCH(int, expectedCodeBlockCount);
|
||||
QFETCH(int, expectedPlainBlockCount);
|
||||
QFETCH(QString, expectedLanguage);
|
||||
QFETCH(QString, expectedFenceChar);
|
||||
QFETCH(QString, rewrite);
|
||||
|
||||
QTextDocument doc;
|
||||
doc.setMarkdown(input);
|
||||
|
||||
#ifdef DEBUG_WRITE_HTML
|
||||
{
|
||||
QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".html");
|
||||
out.open(QFile::WriteOnly);
|
||||
out.write(doc.toHtml().toLatin1());
|
||||
out.close();
|
||||
}
|
||||
#endif
|
||||
|
||||
QTextFrame::iterator iterator = doc.rootFrame()->begin();
|
||||
QTextFrame *currentFrame = iterator.currentFrame();
|
||||
int codeBlockCount = 0;
|
||||
int plainBlockCount = 0;
|
||||
while (!iterator.atEnd()) {
|
||||
// There are no child frames
|
||||
QCOMPARE(iterator.currentFrame(), currentFrame);
|
||||
// Check whether the block is code or plain
|
||||
QTextBlock block = iterator.currentBlock();
|
||||
const bool codeBlock = block.blockFormat().hasProperty(QTextFormat::BlockCodeFence);
|
||||
QCOMPARE(block.blockFormat().nonBreakableLines(), codeBlock);
|
||||
QCOMPARE(block.blockFormat().stringProperty(QTextFormat::BlockCodeLanguage), codeBlock ? expectedLanguage : QString());
|
||||
if (codeBlock) {
|
||||
QCOMPARE(block.blockFormat().stringProperty(QTextFormat::BlockCodeFence), expectedFenceChar);
|
||||
++codeBlockCount;
|
||||
} else {
|
||||
++plainBlockCount;
|
||||
}
|
||||
qCDebug(lcTests) << (codeBlock ? "code" : "text") << block.text() << block.charFormat().fontFamilies();
|
||||
++iterator;
|
||||
}
|
||||
QCOMPARE(codeBlockCount, expectedCodeBlockCount);
|
||||
QCOMPARE(plainBlockCount, expectedPlainBlockCount);
|
||||
if (doc.toMarkdown() != rewrite && isMainFontFixed())
|
||||
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
|
||||
QCOMPARE(doc.toMarkdown(), rewrite);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QTextMarkdownImporter)
|
||||
#include "tst_qtextmarkdownimporter.moc"
|
||||
|
@ -500,8 +500,8 @@ void tst_QTextMarkdownWriter::fromHtml_data()
|
||||
// "<p>(The first sentence of this paragraph is a line, the next paragraph has a number</p>13) but that's not part of an ordered list" <<
|
||||
// "(The first sentence of this paragraph is a line, the next paragraph has a number\n\n13\\) but that's not part of an ordered list\n\n";
|
||||
QTest::newRow("preformats with embedded backticks") <<
|
||||
"<pre>none `one` ``two``</pre><pre>```three``` ````four````</pre>plain" <<
|
||||
"``` none `one` ``two`` ```\n\n````` ```three``` ````four```` `````\n\nplain\n\n";
|
||||
"<pre>none `one` ``two``</pre>plain<pre>```three``` ````four````</pre>plain" <<
|
||||
"```\nnone `one` ``two``\n\n```\nplain\n\n```\n```three``` ````four````\n\n```\nplain\n\n";
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::fromHtml()
|
||||
|
Loading…
x
Reference in New Issue
Block a user