diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2019-04-26 07:40:34 +0200 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2019-05-08 20:28:28 +0000 |
commit | 040dd7fe26bfa34aae19e2db698ff0d69346ed56 (patch) | |
tree | 180b60f5a9e171609fadabf6e36b81ad3337b2d0 /src/gui/text/qtextmarkdownwriter.cpp | |
parent | 6a58a68ae3feb27dca48ebb3274b748f784b3d12 (diff) |
Markdown: fix several issues with lists and continuations
Importer fixes:
- the first list item after a heading doesn't keep the heading font
- the first text fragment after a bullet is the bullet text, not a
separate paragraph
- detect continuation lines and append to the list item text
- detect continuation paragraphs and indent them properly
- indent nested list items properly
- add a test for QTextMarkdownImporter
Writer fixes:
- after bullet items, continuation lines and paragraphs are indented
- indentation of continuations isn't affected by checkboxes
- add extra newlines between list items in "loose" lists
- avoid writing triple newlines
- enhance the test for QTextMarkdownWriter
Change-Id: Ib1dda514832f6dc0cdad177aa9a423a7038ac8c6
Reviewed-by: Gatis Paeglis <gatis.paeglis@qt.io>
Diffstat (limited to 'src/gui/text/qtextmarkdownwriter.cpp')
-rw-r--r-- | src/gui/text/qtextmarkdownwriter.cpp | 107 |
1 files changed, 89 insertions, 18 deletions
diff --git a/src/gui/text/qtextmarkdownwriter.cpp b/src/gui/text/qtextmarkdownwriter.cpp index 313d62bb8a..2f4c8587ad 100644 --- a/src/gui/text/qtextmarkdownwriter.cpp +++ b/src/gui/text/qtextmarkdownwriter.cpp @@ -46,12 +46,17 @@ #include "qtexttable.h" #include "qtextcursor.h" #include "qtextimagehandler_p.h" +#include "qloggingcategory.h" QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcMDW, "qt.text.markdown.writer") + static const QChar Space = QLatin1Char(' '); static const QChar Newline = QLatin1Char('\n'); +static const QChar LineBreak = QChar(0x2028); static const QChar Backtick = QLatin1Char('`'); +static const QChar Period = QLatin1Char('.'); QTextMarkdownWriter::QTextMarkdownWriter(QTextStream &stream, QTextDocument::MarkdownFeatures features) : m_stream(stream), m_features(features) @@ -93,6 +98,7 @@ void QTextMarkdownWriter::writeTable(const QAbstractTableModel &table) } m_stream << '|'<< Qt::endl; } + m_listInfo.clear(); } void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) @@ -144,6 +150,7 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) m_stream << Newline; } int endingCol = writeBlock(block, !table, table && tableRow == 0); + m_doubleNewlineWritten = false; if (table) { QTextTableCell cell = table->cellAt(block.position()); int paddingLen = -endingCol; @@ -158,14 +165,48 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) m_stream << Newline; } else if (endingCol > 0) { m_stream << Newline << Newline; + m_doubleNewlineWritten = true; } lastWasList = block.textList(); } child = iterator.currentFrame(); ++iterator; } - if (table) + if (table) { m_stream << Newline << Newline; + m_doubleNewlineWritten = true; + } + m_listInfo.clear(); +} + +QTextMarkdownWriter::ListInfo QTextMarkdownWriter::listInfo(QTextList *list) +{ + if (!m_listInfo.contains(list)) { + // decide whether this list is loose or tight + ListInfo info; + info.loose = false; + if (list->count() > 1) { + QTextBlock first = list->item(0); + QTextBlock last = list->item(list->count() - 1); + QTextBlock next = first.next(); + while (next.isValid()) { + if (next == last) + break; + qCDebug(lcMDW) << "next block in list" << list << next.text() << "part of list?" << next.textList(); + if (!next.textList()) { + // If we find a continuation paragraph, this list is "loose" + // because it will need a blank line to separate that paragraph. + qCDebug(lcMDW) << "decided list beginning with" << first.text() << "is loose after" << next.text(); + info.loose = true; + break; + } + next = next.next(); + } + } + m_listInfo.insert(list, info); + return info; + } + return m_listInfo.value(list); } static int nearestWordWrapIndex(const QString &s, int before) @@ -211,7 +252,6 @@ static void maybeEscapeFirstChar(QString &s) int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat) { int ColumnLimit = 80; - int wrapIndent = 0; if (block.textList()) { // it's a list-item auto fmt = block.textList()->format(); const int listLevel = fmt.indent(); @@ -219,9 +259,18 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign QByteArray bullet = " "; bool numeric = false; switch (fmt.style()) { - case QTextListFormat::ListDisc: bullet = "-"; break; - case QTextListFormat::ListCircle: bullet = "*"; break; - case QTextListFormat::ListSquare: bullet = "+"; break; + case QTextListFormat::ListDisc: + bullet = "-"; + m_wrappedLineIndent = 2; + break; + case QTextListFormat::ListCircle: + bullet = "*"; + m_wrappedLineIndent = 2; + break; + case QTextListFormat::ListSquare: + bullet = "+"; + m_wrappedLineIndent = 2; + break; case QTextListFormat::ListStyleUndefined: break; case QTextListFormat::ListDecimal: case QTextListFormat::ListLowerAlpha: @@ -229,6 +278,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign case QTextListFormat::ListLowerRoman: case QTextListFormat::ListUpperRoman: numeric = true; + m_wrappedLineIndent = 4; break; } switch (block.blockFormat().marker()) { @@ -241,23 +291,36 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign default: break; } - QString prefix((listLevel - 1) * (numeric ? 4 : 2), Space); - if (numeric) - prefix += QString::number(number) + fmt.numberSuffix() + Space; - else + int indentFirstLine = (listLevel - 1) * (numeric ? 4 : 2); + m_wrappedLineIndent += indentFirstLine; + if (m_lastListIndent != listLevel && !m_doubleNewlineWritten && listInfo(block.textList()).loose) + m_stream << Newline; + m_lastListIndent = listLevel; + QString prefix(indentFirstLine, Space); + if (numeric) { + QString suffix = fmt.numberSuffix(); + if (suffix.isEmpty()) + suffix = QString(Period); + QString numberStr = QString::number(number) + suffix + Space; + if (numberStr.length() == 3) + numberStr += Space; + prefix += numberStr; + } else { prefix += QLatin1String(bullet) + Space; + } m_stream << prefix; - wrapIndent = prefix.length(); + } else if (!block.blockFormat().indent()) { + m_wrappedLineIndent = 0; } if (block.blockFormat().headingLevel()) m_stream << QByteArray(block.blockFormat().headingLevel(), '#') << ' '; - QString wrapIndentString(wrapIndent, Space); + QString wrapIndentString(m_wrappedLineIndent, Space); // It would be convenient if QTextStream had a lineCharPos() accessor, // to keep track of how many characters (not bytes) have been written on the current line, // but it doesn't. So we have to keep track with this col variable. - int col = wrapIndent; + int col = m_wrappedLineIndent; bool mono = false; bool startsOrEndsWithBacktick = false; bool bold = false; @@ -267,8 +330,16 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign QString backticks(Backtick); for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) { QString fragmentText = frag.fragment().text(); - while (fragmentText.endsWith(QLatin1Char('\n'))) + while (fragmentText.endsWith(Newline)) fragmentText.chop(1); + if (block.textList()) { // <li>first line</br>continuation</li> + QString newlineIndent = QString(Newline) + QString(m_wrappedLineIndent, Space); + fragmentText.replace(QString(LineBreak), newlineIndent); + } else if (block.blockFormat().indent() > 0) { // <li>first line<p>continuation</p></li> + m_stream << QString(m_wrappedLineIndent, Space); + } else { + fragmentText.replace(LineBreak, Newline); + } startsOrEndsWithBacktick |= fragmentText.startsWith(Backtick) || fragmentText.endsWith(Backtick); QTextCharFormat fmt = frag.fragment().charFormat(); if (fmt.isImageFormat()) { @@ -276,7 +347,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign QString s = QLatin1String("![image](") + ifmt.name() + QLatin1Char(')'); if (wrap && col + s.length() > ColumnLimit) { m_stream << Newline << wrapIndentString; - col = wrapIndent; + col = m_wrappedLineIndent; } m_stream << s; col += s.length(); @@ -285,7 +356,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign fmt.property(QTextFormat::AnchorHref).toString() + QLatin1Char(')'); if (wrap && col + s.length() > ColumnLimit) { m_stream << Newline << wrapIndentString; - col = wrapIndent; + col = m_wrappedLineIndent; } m_stream << s; col += s.length(); @@ -296,7 +367,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign if (!ignoreFormat) { if (monoFrag != mono) { if (monoFrag) - backticks = QString::fromLatin1(QByteArray(adjacentBackticksCount(fragmentText) + 1, '`')); + backticks = QString(adjacentBackticksCount(fragmentText) + 1, Backtick); markers += backticks; if (startsOrEndsWithBacktick) markers += Space; @@ -347,12 +418,12 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign m_stream << markers; col += markers.length(); } - if (col == wrapIndent) + if (col == m_wrappedLineIndent) maybeEscapeFirstChar(subfrag); m_stream << subfrag; if (breakingLine) { m_stream << Newline << wrapIndentString; - col = wrapIndent; + col = m_wrappedLineIndent; } else { col += subfrag.length(); } |