Add QTextDocFragment::to/fromMarkdown() & QTextCursor::insertMarkdown()
Also add the beginnings of an autotest for QTextCursor::insertHtml(), for comparison purposes. We can see that the block to be inserted is merged with an existing block by default rather than being inserted as a new one, with both HTML and Markdown insertions. So now we test for leading and trailing newlines in the markdown to be inserted, to determine whether we need a new block into which to insert, and to "hit enter" at the end of the insertion. QSKIP the toMarkdown() comparisons if GeneralFont is mono. This happens on Boot2Qt systems in CI. Task-number: QTBUG-76105 Task-number: QTBUG-94462 Task-number: QTBUG-100515 Task-number: QTBUG-103484 Change-Id: I51a05c6a7cd0be4f2817f4a922f45fa663982293 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
This commit is contained in:
parent
e69ebf93ca
commit
7c76064604
@ -2290,6 +2290,28 @@ void QTextCursor::insertHtml(const QString &html)
|
||||
|
||||
#endif // QT_NO_TEXTHTMLPARSER
|
||||
|
||||
/*!
|
||||
\since 6.4
|
||||
Inserts the \a markdown text at the current position(),
|
||||
with the specified Markdown \a features. The default is GitHub dialect.
|
||||
*/
|
||||
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
|
||||
void QTextCursor::insertMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
|
||||
{
|
||||
if (!d || !d->priv)
|
||||
return;
|
||||
QTextDocumentFragment fragment = QTextDocumentFragment::fromMarkdown(markdown, features);
|
||||
if (markdown.startsWith(QLatin1Char('\n')))
|
||||
insertBlock(fragment.d->doc->firstBlock().blockFormat());
|
||||
insertFragment(fragment);
|
||||
if (!atEnd() && markdown.endsWith(QLatin1Char('\n')))
|
||||
insertText(QLatin1String("\n"));
|
||||
}
|
||||
|
||||
#endif // textmarkdownreader
|
||||
|
||||
/*!
|
||||
\overload
|
||||
\since 4.2
|
||||
|
@ -43,12 +43,11 @@
|
||||
#include <QtGui/qtguiglobal.h>
|
||||
#include <QtCore/qstring.h>
|
||||
#include <QtCore/qshareddata.h>
|
||||
#include <QtGui/qtextdocument.h>
|
||||
#include <QtGui/qtextformat.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
||||
class QTextDocument;
|
||||
class QTextCursorPrivate;
|
||||
class QTextDocumentFragment;
|
||||
class QTextCharFormat;
|
||||
@ -201,6 +200,10 @@ public:
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
void insertHtml(const QString &html);
|
||||
#endif // QT_NO_TEXTHTMLPARSER
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
void insertMarkdown(const QString &markdown,
|
||||
QTextDocument::MarkdownFeatures features = QTextDocument::MarkdownDialectGitHub);
|
||||
#endif // textmarkdownreader
|
||||
|
||||
void insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment);
|
||||
void insertImage(const QTextImageFormat &format);
|
||||
|
@ -41,6 +41,12 @@
|
||||
#include "qtextdocumentfragment_p.h"
|
||||
#include "qtextcursor_p.h"
|
||||
#include "qtextlist.h"
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
#include "qtextmarkdownimporter_p.h"
|
||||
#endif
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
#include "qtextmarkdownwriter_p.h"
|
||||
#endif
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qbytearray.h>
|
||||
@ -412,6 +418,26 @@ QString QTextDocumentFragment::toHtml() const
|
||||
|
||||
#endif // QT_NO_TEXTHTMLPARSER
|
||||
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
|
||||
/*!
|
||||
\since 6.4
|
||||
|
||||
Returns the contents of the document fragment as Markdown,
|
||||
with the specified \a features. The default is GitHub dialect.
|
||||
|
||||
\sa toPlainText(), QTextDocument::toMarkdown()
|
||||
*/
|
||||
QString QTextDocumentFragment::toMarkdown(QTextDocument::MarkdownFeatures features) const
|
||||
{
|
||||
if (!d)
|
||||
return QString();
|
||||
|
||||
return d->doc->toMarkdown(features);
|
||||
}
|
||||
|
||||
#endif // textmarkdownwriter
|
||||
|
||||
/*!
|
||||
Returns a document fragment that contains the given \a plainText.
|
||||
|
||||
@ -1277,9 +1303,6 @@ void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFor
|
||||
compressNextWhitespace = RemoveWhiteSpace;
|
||||
}
|
||||
|
||||
#endif // QT_NO_TEXTHTMLPARSER
|
||||
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
/*!
|
||||
\fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider)
|
||||
\since 4.2
|
||||
@ -1305,4 +1328,31 @@ QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const
|
||||
|
||||
#endif // QT_NO_TEXTHTMLPARSER
|
||||
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
|
||||
/*!
|
||||
\fn QTextDocumentFragment QTextDocumentFragment::fromMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
|
||||
\since 6.4
|
||||
|
||||
Returns a QTextDocumentFragment based on the given \a markdown text with
|
||||
the specified \a features. The default is GitHub dialect.
|
||||
|
||||
The formatting is preserved as much as possible; for example, \c {**bold**}
|
||||
will become a document fragment containing the text "bold" with a bold
|
||||
character style.
|
||||
|
||||
\note Loading external resources is not supported.
|
||||
*/
|
||||
QTextDocumentFragment QTextDocumentFragment::fromMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
|
||||
{
|
||||
QTextDocumentFragment res;
|
||||
res.d = new QTextDocumentFragmentPrivate;
|
||||
|
||||
QTextMarkdownImporter importer(features);
|
||||
importer.import(res.d->doc, markdown);
|
||||
return res;
|
||||
}
|
||||
|
||||
#endif // textmarkdownreader
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -41,13 +41,13 @@
|
||||
#define QTEXTDOCUMENTFRAGMENT_H
|
||||
|
||||
#include <QtGui/qtguiglobal.h>
|
||||
#include <QtGui/qtextdocument.h>
|
||||
#include <QtCore/qstring.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
||||
class QTextStream;
|
||||
class QTextDocument;
|
||||
class QTextDocumentFragmentPrivate;
|
||||
class QTextCursor;
|
||||
|
||||
@ -68,11 +68,18 @@ public:
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
QString toHtml() const;
|
||||
#endif // QT_NO_TEXTHTMLPARSER
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
QString toMarkdown(QTextDocument::MarkdownFeatures features = QTextDocument::MarkdownDialectGitHub) const;
|
||||
#endif
|
||||
|
||||
static QTextDocumentFragment fromPlainText(const QString &plainText);
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
static QTextDocumentFragment fromHtml(const QString &html, const QTextDocument *resourceProvider = nullptr);
|
||||
#endif // QT_NO_TEXTHTMLPARSER
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
static QTextDocumentFragment fromMarkdown(const QString &markdown,
|
||||
QTextDocument::MarkdownFeatures features = QTextDocument::MarkdownDialectGitHub);
|
||||
#endif
|
||||
|
||||
private:
|
||||
QTextDocumentFragmentPrivate *d;
|
||||
|
@ -28,7 +28,9 @@
|
||||
|
||||
|
||||
#include <QTest>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include <qfontinfo.h>
|
||||
#include <qtextdocument.h>
|
||||
#include <qtexttable.h>
|
||||
#include <qvariant.h>
|
||||
@ -41,6 +43,8 @@
|
||||
|
||||
#include <private/qtextcursor_p.h>
|
||||
|
||||
Q_LOGGING_CATEGORY(lcTests, "qt.gui.tests")
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QTextDocument)
|
||||
|
||||
class tst_QTextCursor : public QObject
|
||||
@ -110,6 +114,14 @@ private slots:
|
||||
void selectVisually();
|
||||
|
||||
void insertText();
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
void insertHtml_data();
|
||||
void insertHtml();
|
||||
#endif
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
void insertMarkdown_data();
|
||||
void insertMarkdown();
|
||||
#endif
|
||||
|
||||
void insertFragmentShouldUseCurrentCharFormat();
|
||||
|
||||
@ -1428,6 +1440,216 @@ void tst_QTextCursor::insertText()
|
||||
QCOMPARE(cursor.block().text(), QString("yoyodyne"));
|
||||
}
|
||||
|
||||
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
|
||||
void tst_QTextCursor::insertHtml_data()
|
||||
{
|
||||
QTest::addColumn<QString>("initialText");
|
||||
QTest::addColumn<int>("expectedInitialBlockCount");
|
||||
QTest::addColumn<bool>("insertBlock");
|
||||
QTest::addColumn<bool>("insertAsPlainText");
|
||||
QTest::addColumn<int>("insertPosition");
|
||||
QTest::addColumn<QString>("insertText");
|
||||
QTest::addColumn<QString>("expectedSelText");
|
||||
QTest::addColumn<QString>("expectedText");
|
||||
QTest::addColumn<QString>("expectedMarkdown");
|
||||
|
||||
const QString htmlHeadingString("<h1>Hello World</h1>");
|
||||
|
||||
QTest::newRow("insert as html at end of heading")
|
||||
<< htmlHeadingString << 1
|
||||
<< false << false << 11 << QString("Other\ntext")
|
||||
<< QString("Hello WorldOther text")
|
||||
<< QString("Hello WorldOther text")
|
||||
<< QString("# Hello WorldOther text\n\n");
|
||||
|
||||
QTest::newRow("insert as html in new block at end of heading")
|
||||
<< htmlHeadingString << 1
|
||||
<< false << true << 11 << QString("Other\ntext")
|
||||
<< QString("Hello WorldOther\u2029text")
|
||||
<< QString("Hello WorldOther\ntext")
|
||||
<< QString("# Hello WorldOther\n\n# text\n\n");
|
||||
|
||||
QTest::newRow("insert as html in middle of heading")
|
||||
<< htmlHeadingString << 1
|
||||
<< false << false << 6 << QString("\n\nOther\ntext\n\n")
|
||||
<< QString("Hello Other text World")
|
||||
<< QString("Hello Other text World")
|
||||
<< QString("# Hello Other text World\n\n");
|
||||
|
||||
QTest::newRow("insert as text at end of heading")
|
||||
<< htmlHeadingString << 1
|
||||
<< true << false << 11 << QString("\n\nOther\ntext")
|
||||
<< QString("Hello World\u2029Other text")
|
||||
<< QString("Hello World\nOther text")
|
||||
<< QString("# Hello World\n\nOther text\n\n");
|
||||
|
||||
QTest::newRow("insert as text in new block at end of heading")
|
||||
<< htmlHeadingString << 1
|
||||
<< true << true << 11 << QString("\n\nOther\ntext")
|
||||
<< QString("Hello World\u2029\u2029\u2029Other\u2029text")
|
||||
<< QString("Hello World\n\n\nOther\ntext")
|
||||
<< QString("# Hello World\n\n**Other**\n\n**text**\n\n");
|
||||
|
||||
QTest::newRow("insert as text in middle of heading")
|
||||
<< htmlHeadingString << 1
|
||||
<< true << false << 6 << QString("Other\ntext")
|
||||
<< QString("Hello \u2029Other textWorld")
|
||||
<< QString("Hello \nOther textWorld")
|
||||
<< QString("# Hello \n\nOther text**World**\n\n");
|
||||
}
|
||||
|
||||
void tst_QTextCursor::insertHtml()
|
||||
{
|
||||
QFETCH(QString, initialText);
|
||||
QFETCH(int, expectedInitialBlockCount);
|
||||
QFETCH(bool, insertBlock);
|
||||
QFETCH(bool, insertAsPlainText);
|
||||
QFETCH(int, insertPosition);
|
||||
QFETCH(QString, insertText);
|
||||
QFETCH(QString, expectedSelText);
|
||||
QFETCH(QString, expectedText);
|
||||
QFETCH(QString, expectedMarkdown);
|
||||
|
||||
cursor.insertHtml(initialText);
|
||||
QCOMPARE(blockCount(), expectedInitialBlockCount);
|
||||
cursor.setPosition(insertPosition);
|
||||
if (insertBlock)
|
||||
cursor.insertBlock(QTextBlockFormat());
|
||||
qCDebug(lcTests) << "pos" << cursor.position() << "block" << cursor.blockNumber()
|
||||
<< "heading" << cursor.blockFormat().headingLevel();
|
||||
if (insertAsPlainText)
|
||||
cursor.insertText(insertText);
|
||||
else
|
||||
cursor.insertHtml(insertText);
|
||||
cursor.select(QTextCursor::Document);
|
||||
qCDebug(lcTests) << "sel text after insertion" << cursor.selectedText();
|
||||
qCDebug(lcTests) << "text after insertion" << cursor.document()->toPlainText();
|
||||
qCDebug(lcTests) << "html after insertion" << cursor.document()->toHtml();
|
||||
qCDebug(lcTests) << "markdown after insertion" << cursor.document()->toMarkdown();
|
||||
QCOMPARE(cursor.selectedText(), expectedSelText);
|
||||
QCOMPARE(cursor.document()->toPlainText(), expectedText);
|
||||
if (auto defaultFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFontInfo(defaultFont).fixedPitch()) {
|
||||
qWarning() << defaultFont << "is QFontDatabase::GeneralFont, and is fixedPitch";
|
||||
QSKIP("cannot reliably distinguish normal and monospace markdown spans on this system (QTBUG-103484)");
|
||||
}
|
||||
QCOMPARE(cursor.document()->toMarkdown(), expectedMarkdown);
|
||||
}
|
||||
|
||||
#endif // QT_NO_TEXTHTMLPARSER
|
||||
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
|
||||
void tst_QTextCursor::insertMarkdown_data()
|
||||
{
|
||||
QTest::addColumn<QString>("initialText");
|
||||
QTest::addColumn<int>("expectedInitialBlockCount");
|
||||
QTest::addColumn<int>("insertPosition");
|
||||
QTest::addColumn<QString>("insertText");
|
||||
QTest::addColumn<QString>("expectedSelText");
|
||||
QTest::addColumn<QString>("expectedText");
|
||||
QTest::addColumn<QString>("expectedMarkdown");
|
||||
|
||||
QTest::newRow("bold fragment in italic span")
|
||||
<< "someone said *hello world*" << 1
|
||||
<< 19 << QString(" **crazy** ")
|
||||
<< QString("someone said hello crazyworld")
|
||||
<< QString("someone said hello crazyworld")
|
||||
<< QString("someone said *hello ***crazy***world*\n\n"); // explicit B+I: not necessary but OK
|
||||
|
||||
QTest::newRow("list in a paragraph")
|
||||
<< "hello list with 3 items" << 1
|
||||
<< 10 << QString("1. one\n2. two\n")
|
||||
<< QString("hello list\u2029one\u2029two\u2029 with 3 items")
|
||||
<< QString("hello list\none\ntwo\n with 3 items")
|
||||
<< QString("hello list\n\n1. one\n2. two\n3. with 3 items\n");
|
||||
|
||||
QTest::newRow("list in a list")
|
||||
<< "1) bread\n2) milk\n" << 2
|
||||
<< 6 << QString("0) eggs\n1) maple syrup\n")
|
||||
<< QString("bread\u2029eggs\u2029maple syrup\u2029milk")
|
||||
<< QString("bread\neggs\nmaple syrup\nmilk")
|
||||
<< QString("1) bread\n2) eggs\n1) maple syrup\n2) milk\n");
|
||||
// renumbering would happen if we re-read the whole document
|
||||
|
||||
QTest::newRow("list after a list")
|
||||
<< "1) bread\n2) milk\n\n" << 2
|
||||
<< 13 << QString("\n0) eggs\n1) maple syrup\n")
|
||||
<< QString("bread\u2029milk\u2029eggs\u2029maple syrup")
|
||||
<< QString("bread\nmilk\neggs\nmaple syrup")
|
||||
<< QString("1) bread\n2) milk\n3) eggs\n1) maple syrup\n");
|
||||
|
||||
const QString markdownHeadingString("# Hello\nWorld\n");
|
||||
|
||||
QTest::newRow("markdown heading at end of markdown heading")
|
||||
<< markdownHeadingString << 2
|
||||
<< 11 << QString("\n\n## Other text")
|
||||
<< QString("Hello\u2029World\u2029Other text")
|
||||
<< QString("Hello\nWorld\nOther text")
|
||||
<< QString("# Hello\n\nWorld\n\n## Other text\n\n");
|
||||
|
||||
QTest::newRow("markdown heading into middle of markdown heading")
|
||||
<< markdownHeadingString << 2
|
||||
<< 6 << QString("## Other\ntext\n\n")
|
||||
<< QString("Hello\u2029Other\u2029text\u2029World")
|
||||
<< QString("Hello\nOther\ntext\nWorld")
|
||||
<< QString("# Hello\n\n**Other**\n\ntext\n\nWorld\n\n");
|
||||
|
||||
QTest::newRow("markdown heading without trailing newline into middle of markdown heading")
|
||||
<< markdownHeadingString << 2
|
||||
<< 6 << QString("## Other\ntext")
|
||||
<< QString("Hello\u2029Other\u2029textWorld")
|
||||
<< QString("Hello\nOther\ntextWorld")
|
||||
<< QString("# Hello\n\n**Other**\n\ntextWorld\n\n");
|
||||
|
||||
QTest::newRow("text into middle of markdown heading after newline")
|
||||
<< markdownHeadingString << 2
|
||||
<< 6 << QString("Other ")
|
||||
<< QString("Hello\u2029OtherWorld")
|
||||
<< QString("Hello\nOtherWorld")
|
||||
<< QString("# Hello\n\nOtherWorld\n\n");
|
||||
|
||||
QTest::newRow("text into middle of markdown heading before newline")
|
||||
<< markdownHeadingString << 2
|
||||
<< 5 << QString(" Other ")
|
||||
<< QString("HelloOther\u2029World")
|
||||
<< QString("HelloOther\nWorld")
|
||||
<< QString("# HelloOther\n\nWorld\n\n");
|
||||
}
|
||||
|
||||
void tst_QTextCursor::insertMarkdown()
|
||||
{
|
||||
QFETCH(QString, initialText);
|
||||
QFETCH(int, expectedInitialBlockCount);
|
||||
QFETCH(int, insertPosition);
|
||||
QFETCH(QString, insertText);
|
||||
QFETCH(QString, expectedSelText);
|
||||
QFETCH(QString, expectedText);
|
||||
QFETCH(QString, expectedMarkdown);
|
||||
|
||||
cursor.insertMarkdown(initialText);
|
||||
QCOMPARE(blockCount(), expectedInitialBlockCount);
|
||||
cursor.setPosition(insertPosition);
|
||||
qCDebug(lcTests) << "pos" << cursor.position() << "block" << cursor.blockNumber()
|
||||
<< "heading" << cursor.blockFormat().headingLevel();
|
||||
cursor.insertMarkdown(insertText);
|
||||
cursor.select(QTextCursor::Document);
|
||||
qCDebug(lcTests) << "sel text after insertion" << cursor.selectedText();
|
||||
qCDebug(lcTests) << "text after insertion" << cursor.document()->toPlainText();
|
||||
qCDebug(lcTests) << "html after insertion" << cursor.document()->toHtml();
|
||||
qCDebug(lcTests) << "markdown after insertion" << cursor.document()->toMarkdown();
|
||||
QCOMPARE(cursor.selectedText(), expectedSelText);
|
||||
QCOMPARE(cursor.document()->toPlainText(), expectedText);
|
||||
if (auto defaultFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFontInfo(defaultFont).fixedPitch()) {
|
||||
qWarning() << defaultFont << "is QFontDatabase::GeneralFont, and is fixedPitch";
|
||||
QSKIP("cannot reliably distinguish normal and monospace markdown spans on this system (QTBUG-103484)");
|
||||
}
|
||||
QCOMPARE(cursor.document()->toMarkdown(), expectedMarkdown);
|
||||
}
|
||||
|
||||
#endif // textmarkdownreader
|
||||
|
||||
void tst_QTextCursor::insertFragmentShouldUseCurrentCharFormat()
|
||||
{
|
||||
QTextDocumentFragment fragment = QTextDocumentFragment::fromPlainText("Hello World");
|
||||
|
Loading…
Reference in New Issue
Block a user