1 #include "MarkdownDocument.h"
5 MarkdownDocument::MarkdownDocument(QQuickItem *parent) : QQuickItem(parent),
7 m_textDocument(Q_NULLPTR),
12 m_highlighter(Q_NULLPTR),
17 connect(
this, &MarkdownDocument::textDocumentChanged,
this, &MarkdownDocument::onDocumentChanged);
18 connect(
this, &MarkdownDocument::optionsChanged,
this, &MarkdownDocument::onDocumentChanged);
19 m_blockQuote.setPattern(
"^ {0,3}(>\\s*)+");
20 m_numList.setPattern(
"^\\s*([0-9]+)[.)]\\s+");
21 m_bulletList.setPattern(
"^\\s*[+*-]\\s+");
22 m_taskList.setPattern(
"^\\s*[-] \\[([x ])\\]\\s+");
24 m_pairs.insert(
'"',
'"');
25 m_pairs.insert(
'\'',
'\'');
26 m_pairs.insert(
'(',
')');
27 m_pairs.insert(
'[',
']');
28 m_pairs.insert(
'{',
'}');
29 m_pairs.insert(
'*',
'*');
30 m_pairs.insert(
'_',
'_');
31 m_pairs.insert(
'`',
'`');
32 m_pairs.insert(
'<',
'>');
34 m_matches.insert(
'"',
true);
35 m_matches.insert(
'\'',
true);
36 m_matches.insert(
'(',
true);
37 m_matches.insert(
'[',
true);
38 m_matches.insert(
'{',
true);
39 m_matches.insert(
'*',
true);
40 m_matches.insert(
'_',
true);
41 m_matches.insert(
'`',
true);
42 m_matches.insert(
'<',
true);
45 int MarkdownDocument::cursorPosition()
const
47 return m_cursor.position();
50 void MarkdownDocument::indentText()
53 const int tabWidth = m_options->get_tabWidth();
55 QTextBlock start = document()->findBlock(m_selectionStart);
56 QTextBlock end = document()->findBlock(m_selectionEnd).next();
57 m_cursor.beginEditBlock();
59 while (start != end) {
60 setCursorPosition(start.position());
61 if (m_options->get_spacesForTabs()) {
62 QString indentText =
"";
63 for (
int i = 0; i < tabWidth; ++i) {
64 indentText += QStringLiteral(
" ");
66 m_cursor.insertText(indentText);
68 m_cursor.insertText(
"\t");
72 m_cursor.endEditBlock();
74 int indent = tabWidth;
75 QString indentText =
"";
76 m_cursor.beginEditBlock();
78 switch (m_cursor.block().userState()) {
81 indent = tabWidth - (m_cursor.positionInBlock() % tabWidth);
85 if (m_options->get_spacesForTabs()) {
86 for (
int i = 0; i < indent; ++i) {
87 indentText += QStringLiteral(
" ");
90 indentText = QStringLiteral(
"\t");
92 m_cursor.insertText(indentText);
93 m_cursor.endEditBlock();
95 setCursorPosition(m_cursor.position());
98 void MarkdownDocument::unindentText()
103 if (m_hasSelection) {
104 start = document()->findBlock(m_selectionStart);
105 end = document()->findBlock(m_selectionEnd);
107 start = m_cursor.block();
111 m_cursor.beginEditBlock();
113 while (start != end) {
114 setCursorPosition(start.position());
115 if (document()->characterAt(m_cursor.position()) == QChar(
'\t')) {
116 m_cursor.deleteChar();
119 while (document()->characterAt(m_cursor.position()) == QChar(
' ') && p < m_options->get_tabWidth()) {
121 m_cursor.deleteChar();
124 start = start.next();
126 m_cursor.endEditBlock();
127 setCursorPosition(m_cursor.position());
130 void MarkdownDocument::setCursorPosition(
int cursorPosition)
132 if (m_cursorPosition == cursorPosition)
135 m_cursorPosition = cursorPosition;
136 m_cursor.setPosition(cursorPosition);
137 emit cursorPositionChanged(cursorPosition);
140 QTextDocument *MarkdownDocument::document()
const
142 if (m_textDocument != Q_NULLPTR) {
143 return m_textDocument->textDocument();
148 void MarkdownDocument::onDocumentChanged()
150 if (document() != Q_NULLPTR && m_options != Q_NULLPTR) {
151 connect(document(), &QTextDocument::contentsChange,
this, &MarkdownDocument::onContentsChange);
152 m_cursor = textCursor();
157 void MarkdownDocument::onContentsChange(
const int &pos,
const int &rm,
const int &add)
165 QTextCursor MarkdownDocument::textCursor()
167 return document()->rootFrame()->firstCursorPosition();
170 bool MarkdownDocument::insertPair(
const QChar &c)
172 if (m_pairs.contains(c)) {
173 QChar p = m_pairs.value(c);
176 if (m_hasSelection) {
177 block = document()->findBlock(m_selectionStart);
178 end = document()->findBlock(m_selectionEnd);
180 auto s = m_selectionStart;
181 auto e = m_selectionEnd;
182 m_cursor.beginEditBlock();
183 setCursorPosition(s);
184 m_cursor.insertText(c);
185 setCursorPosition(e + 1);
186 m_cursor.insertText(p);
187 setCursorPosition(s);
188 setCursorPosition(e);
189 m_cursor.endEditBlock();
192 }
else if (m_options->get_autoMatchEnabled() && m_matches.value(c)) {
193 m_cursor.insertText(c);
194 m_cursor.insertText(p);
195 m_cursor.movePosition(QTextCursor::PreviousCharacter);
196 setCursorPosition(m_cursor.position());
203 bool MarkdownDocument::endPairHandled(
const QChar &c)
205 bool lookAhead =
false;
206 if (m_options->get_autoMatchEnabled() && !m_cursor.hasSelection()) {
207 if (m_pairs.values().contains(c)) {
208 auto key = m_pairs.key(c);
209 if (m_matches.value(key)) {
216 auto text = m_cursor.block().text();
217 auto pos = m_cursor.positionInBlock();
218 if (pos < text.length()) {
219 if (text[pos] == c) {
220 m_cursor.movePosition(QTextCursor::NextCharacter);
228 void MarkdownDocument::keyPressEvent(QKeyEvent *event)
231 int key =
event->key();
232 bool accepted =
false;
235 if (!m_hasSelection) {
236 if (event->modifiers() & Qt::ShiftModifier) {
237 m_cursor.insertText(
" ");
240 if (event->modifiers() & Qt::ControlModifier) {
241 m_cursor.insertText(
"\n");
252 case Qt::Key_Backtab:
256 case Qt::Key_Backspace:
257 accepted = handleBackspace();
260 if (event->text().size() == 1) {
261 auto c =
event->text().at(0);
262 if (!endPairHandled(c) && !insertPair(c)) {
270 event->setAccepted(accepted);
273 void MarkdownDocument::handleCRLF()
275 QString autoInsertText =
"";
276 bool endList =
false;
277 if (m_cursor.positionInBlock() < (m_cursor.block().length() - 1)) {
278 autoInsertText = getPreviousIndentation();
279 if (m_cursor.positionInBlock() < autoInsertText.length()) {
280 autoInsertText.truncate(m_cursor.positionInBlock());
283 switch (m_cursor.block().userState()) {
284 case MarkdownTokenizer::TokenState::NumList:
286 autoInsertText = getBlockStart(m_numList);
287 QStringList c = m_numList.capturedTexts();
288 if (!autoInsertText.isEmpty() && c.size() == 2) {
289 if (m_cursor.block().text().length() == autoInsertText.length()) {
293 int liNum = c.at(1).toInt();
295 autoInsertText = autoInsertText.replace(num, QString(
"%1").arg(liNum));
298 autoInsertText = getPreviousIndentation();
302 case MarkdownTokenizer::TokenState::BulletList:
304 autoInsertText = getBlockStart(m_taskList);
305 if (autoInsertText.isEmpty()) {
306 autoInsertText = getBlockStart(m_bulletList);
307 if (autoInsertText.isEmpty()) {
308 autoInsertText = getPreviousIndentation();
309 }
else if (m_cursor.block().text().length() == autoInsertText.length()) {
313 if (m_cursor.block().text().length() == autoInsertText.length()) {
316 autoInsertText = autoInsertText.replace(
'x',
' ');
321 case MarkdownTokenizer::TokenState::Blockquote:
323 autoInsertText = getBlockStart(m_blockQuote);
327 autoInsertText = getPreviousIndentation();
334 autoInsertText = getPreviousIndentation();
335 m_cursor.movePosition(QTextCursor::StartOfBlock);
336 m_cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
337 setCursorPosition(m_cursor.position());
338 m_cursor.insertText(autoInsertText);
342 m_cursor.insertText(QStringLiteral(
"\n") + autoInsertText);
345 bool MarkdownDocument::handleBackspace()
347 if (m_hasSelection) {
351 int backtrackIndex = -1;
353 switch (m_cursor.block().userState())
355 case MarkdownTokenizer::TokenState::NumList:
357 if (m_numList.exactMatch(m_cursor.block().text()))
359 backtrackIndex = m_cursor.block().text().indexOf(QRegExp(
"\\d"));
363 case MarkdownTokenizer::TokenState::BulletList:
365 if( m_bulletList.exactMatch(m_cursor.block().text())
366 || m_taskList.exactMatch(m_cursor.block().text())) {
368 backtrackIndex = m_cursor.block().text().indexOf(QRegExp(
"[+*-]"));
372 case MarkdownTokenizer::TokenState::Blockquote:
374 if (m_blockQuote.exactMatch(m_cursor.block().text()))
376 backtrackIndex = m_cursor.block().text().lastIndexOf(
'>');
384 if (m_options->get_autoMatchEnabled() && (m_cursor.positionInBlock() > 0))
386 QString blockText = m_cursor.block().text();
388 if (m_cursor.positionInBlock() < blockText.length())
390 QChar currentChar = blockText[m_cursor.positionInBlock()];
391 QChar previousChar = blockText[m_cursor.positionInBlock() - 1];
393 if (m_pairs.value(previousChar) == currentChar)
395 m_cursor.movePosition(QTextCursor::Left);
396 m_cursor.movePosition
399 QTextCursor::KeepAnchor,
402 setCursorPosition(m_cursor.position());
403 m_cursor.removeSelectedText();
411 if (backtrackIndex >= 0)
413 m_cursor.movePosition(QTextCursor::StartOfBlock);
414 m_cursor.movePosition
417 QTextCursor::MoveAnchor,
421 m_cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
422 setCursorPosition(m_cursor.position());
423 m_cursor.removeSelectedText();
430 QString MarkdownDocument::getBlockStart(QRegExp ®exp)
432 QTextBlock block = m_cursor.block();
434 QString text = block.text();
436 if (regexp.indexIn(text, 0) >= 0)
438 return text.left(regexp.matchedLength());
444 QString MarkdownDocument::getPreviousIndentation()
447 QTextBlock block = m_cursor.block();
449 QString text = block.text();
451 for (
int i = 0; i < text.length(); i++)
453 if (text[i].isSpace())