diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2019-05-20 17:40:12 +0200 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2019-06-04 08:01:34 +0200 |
commit | b3cc9403c4f7814d3e14915fdacee42b3a805073 (patch) | |
tree | 903a727b9b78301b9ed7dd5031ac063f3483dc19 /src/gui/text/qtextmarkdownwriter.cpp | |
parent | 57f38bc49d43140f3b04e625a47eb1e6de8d2ae3 (diff) |
QTextMarkdownWriter: write fenced code blocks with language declaration
MD4C now makes it possible to detect indented and fenced code blocks:
https://github.com/mity/md4c/issues/81
Fenced code blocks have the advantages of being easier to write by hand,
and having an "info string" following the opening fence, which is commonly
used to declare the language.
Also, the HTML parser now recognizes tags of the form
<pre class="language-foo">
which is one convention for declaring the programming language
(as opposed to human language, for which the lang attribute would be used):
https://stackoverflow.com/questions/5134242/semantics-standards-and-using-the-lang-attribute-for-source-code-in-markup
So it's possible to read HTML and write markdown without losing this information.
It's also possible to read markdown with any type of code block:
fenced with ``` or ~~~, or indented, and rewrite it the same way.
Change-Id: I33c2bf7d7b66c8f3ba5bdd41ab32572f09349c47
Reviewed-by: Gatis Paeglis <gatis.paeglis@qt.io>
Diffstat (limited to 'src/gui/text/qtextmarkdownwriter.cpp')
-rw-r--r-- | src/gui/text/qtextmarkdownwriter.cpp | 68 |
1 files changed, 60 insertions, 8 deletions
diff --git a/src/gui/text/qtextmarkdownwriter.cpp b/src/gui/text/qtextmarkdownwriter.cpp index 58e0c86b95..f351c8d20b 100644 --- a/src/gui/text/qtextmarkdownwriter.cpp +++ b/src/gui/text/qtextmarkdownwriter.cpp @@ -134,6 +134,24 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) writeFrame(iterator.currentFrame()); else { // no frame, it's a block QTextBlock block = iterator.currentBlock(); + // Look ahead and detect some cases when we should + // suppress needless blank lines, when there will be a big change in block format + bool nextIsDifferent = false; + bool ending = false; + { + QTextFrame::iterator next = iterator; + ++next; + 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)) + nextIsDifferent = true; + } + } if (table) { QTextTableCell cell = table->cellAt(block.position()); if (tableRow < cell.row()) { @@ -150,7 +168,7 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) if (lastWasList) m_stream << Newline; } - int endingCol = writeBlock(block, !table, table && tableRow == 0); + int endingCol = writeBlock(block, !table, table && tableRow == 0, nextIsDifferent); m_doubleNewlineWritten = false; if (table) { QTextTableCell cell = table->cellAt(block.position()); @@ -162,11 +180,19 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) m_stream << QString(paddingLen, Space); for (int col = cell.column(); col < spanEndCol; ++col) m_stream << "|"; - } else if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) { + } else if (m_fencedCodeBlock && ending) { + m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space) + << m_codeBlockFence << Newline << Newline; + m_codeBlockFence.clear(); + } else if (m_indentedCodeBlock && nextIsDifferent) { m_stream << Newline; } else if (endingCol > 0) { - m_stream << Newline << Newline; - m_doubleNewlineWritten = true; + if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) { + m_stream << Newline; + } else { + m_stream << Newline << Newline; + m_doubleNewlineWritten = true; + } } lastWasList = block.textList(); } @@ -259,11 +285,13 @@ static void maybeEscapeFirstChar(QString &s) } } -int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat) +int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat, bool ignoreEmpty) { + if (block.text().isEmpty() && ignoreEmpty) + return 0; const int ColumnLimit = 80; QTextBlockFormat blockFmt = block.blockFormat(); - bool indentedCodeBlock = false; + bool missedBlankCodeBlockLine = false; if (block.textList()) { // it's a list-item auto fmt = block.textList()->format(); const int listLevel = fmt.indent(); @@ -324,7 +352,28 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign } else if (blockFmt.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { m_stream << "- - -\n"; // unambiguous horizontal rule, not an underline under a heading return 0; + } else if (blockFmt.hasProperty(QTextFormat::BlockCodeFence) || blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).length() > 0) { + // It's important to preserve blank lines in code blocks. But blank lines in code blocks + // inside block quotes are getting preserved anyway (along with the "> " prefix). + if (!blockFmt.hasProperty(QTextFormat::BlockQuoteLevel)) + missedBlankCodeBlockLine = true; // only if we don't get any fragments below + if (!m_fencedCodeBlock) { + QString fenceChar = blockFmt.stringProperty(QTextFormat::BlockCodeFence); + if (fenceChar.isEmpty()) + fenceChar = QLatin1String("`"); + m_codeBlockFence = QString(3, fenceChar.at(0)); + // A block quote can contain an indented code block, but not vice-versa. + m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space) << m_codeBlockFence + << Space << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage) << Newline; + m_fencedCodeBlock = true; + } } else if (!blockFmt.indent()) { + if (m_fencedCodeBlock) { + m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space) + << m_codeBlockFence << Newline; + m_fencedCodeBlock = false; + m_codeBlockFence.clear(); + } m_wrappedLineIndent = 0; m_linePrefix.clear(); if (blockFmt.hasProperty(QTextFormat::BlockQuoteLevel)) { @@ -337,7 +386,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) { // A block quote can contain an indented code block, but not vice-versa. m_linePrefix += QString(4, Space); - indentedCodeBlock = true; + m_indentedCodeBlock = true; } } if (blockFmt.headingLevel()) @@ -358,6 +407,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign bool strikeOut = false; QString backticks(Backtick); for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) { + missedBlankCodeBlockLine = false; QString fragmentText = frag.fragment().text(); while (fragmentText.endsWith(Newline)) fragmentText.chop(1); @@ -401,7 +451,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign bool monoFrag = fontInfo.fixedPitch(); QString markers; if (!ignoreFormat) { - if (monoFrag != mono && !indentedCodeBlock) { + if (monoFrag != mono && !m_indentedCodeBlock && !m_fencedCodeBlock) { if (monoFrag) backticks = QString(adjacentBackticksCount(fragmentText) + 1, Backtick); markers += backticks; @@ -501,6 +551,8 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign m_stream << "~~"; col += 2; } + if (missedBlankCodeBlockLine) + m_stream << Newline; return col; } |