vmdeditoperations.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. #include <QtDebug>
  2. #include <QImage>
  3. #include <QVariant>
  4. #include <QMimeData>
  5. #include <QWidget>
  6. #include <QImageReader>
  7. #include <QDir>
  8. #include <QMessageBox>
  9. #include <QKeyEvent>
  10. #include <QTextCursor>
  11. #include <QTimer>
  12. #include <QGuiApplication>
  13. #include <QApplication>
  14. #include <QClipboard>
  15. #include "vmdeditoperations.h"
  16. #include "dialog/vinsertimagedialog.h"
  17. #include "dialog/vselectdialog.h"
  18. #include "utils/vutils.h"
  19. #include "veditor.h"
  20. #include "vdownloader.h"
  21. #include "vfile.h"
  22. #include "vmdeditor.h"
  23. #include "vconfigmanager.h"
  24. #include "utils/vvim.h"
  25. #include "utils/veditutils.h"
  26. extern VConfigManager *g_config;
  27. const QString VMdEditOperations::c_defaultImageTitle = "";
  28. VMdEditOperations::VMdEditOperations(VEditor *p_editor, VFile *p_file)
  29. : VEditOperations(p_editor, p_file), m_autoIndentPos(-1)
  30. {
  31. }
  32. bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
  33. {
  34. QImage image = qvariant_cast<QImage>(source->imageData());
  35. if (image.isNull()) {
  36. return false;
  37. }
  38. VInsertImageDialog dialog(tr("Insert Image From Clipboard"),
  39. c_defaultImageTitle,
  40. "",
  41. m_editor->getEditor());
  42. dialog.setBrowseable(false);
  43. dialog.setImage(image);
  44. if (dialog.exec() == QDialog::Accepted) {
  45. insertImageFromQImage(dialog.getImageTitleInput(),
  46. m_file->fetchImageFolderPath(),
  47. m_file->getImageFolderInLink(),
  48. image);
  49. }
  50. return true;
  51. }
  52. void VMdEditOperations::insertImageFromQImage(const QString &title, const QString &path,
  53. const QString &folderInLink, const QImage &image)
  54. {
  55. QString fileName = VUtils::generateImageFileName(path, title);
  56. QString filePath = QDir(path).filePath(fileName);
  57. V_ASSERT(!QFile(filePath).exists());
  58. QString errStr;
  59. bool ret = VUtils::makePath(path);
  60. if (!ret) {
  61. errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
  62. .arg(g_config->c_dataTextStyle).arg(path);
  63. } else {
  64. ret = image.save(filePath);
  65. if (!ret) {
  66. errStr = tr("Fail to save image <span style=\"%1\">%2</span>.")
  67. .arg(g_config->c_dataTextStyle).arg(filePath);
  68. }
  69. }
  70. if (!ret) {
  71. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  72. tr("Fail to insert image <span style=\"%1\">%2</span>.").arg(g_config->c_dataTextStyle).arg(title),
  73. errStr,
  74. QMessageBox::Ok,
  75. QMessageBox::Ok,
  76. m_editor->getEditor());
  77. return;
  78. }
  79. QString md = QString("![%1](%2/%3)").arg(title).arg(folderInLink).arg(fileName);
  80. insertTextAtCurPos(md);
  81. qDebug() << "insert image" << title << filePath;
  82. VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
  83. Q_ASSERT(mdEditor);
  84. mdEditor->imageInserted(filePath);
  85. }
  86. void VMdEditOperations::insertImageFromPath(const QString &title, const QString &path,
  87. const QString &folderInLink, const QString &oriImagePath)
  88. {
  89. QString fileName = VUtils::generateImageFileName(path, title, QFileInfo(oriImagePath).suffix());
  90. QString filePath = QDir(path).filePath(fileName);
  91. V_ASSERT(!QFile(filePath).exists());
  92. QString errStr;
  93. bool ret = VUtils::makePath(path);
  94. if (!ret) {
  95. errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
  96. .arg(g_config->c_dataTextStyle).arg(path);
  97. } else {
  98. ret = QFile::copy(oriImagePath, filePath);
  99. if (!ret) {
  100. errStr = tr("Fail to copy image <span style=\"%1\">%2</span>.")
  101. .arg(g_config->c_dataTextStyle).arg(filePath);
  102. }
  103. }
  104. if (!ret) {
  105. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  106. tr("Fail to insert image <span style=\"%1\">%2</span>.").arg(g_config->c_dataTextStyle).arg(title),
  107. errStr,
  108. QMessageBox::Ok,
  109. QMessageBox::Ok,
  110. m_editor->getEditor());
  111. return;
  112. }
  113. QString md = QString("![%1](%2/%3)").arg(title).arg(folderInLink).arg(fileName);
  114. insertTextAtCurPos(md);
  115. qDebug() << "insert image" << title << filePath;
  116. VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
  117. Q_ASSERT(mdEditor);
  118. mdEditor->imageInserted(filePath);
  119. }
  120. bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
  121. {
  122. QString imagePath;
  123. QImage image;
  124. bool isLocal = imageUrl.isLocalFile();
  125. QString title;
  126. // Whether it is a local file or web URL
  127. if (isLocal) {
  128. imagePath = imageUrl.toLocalFile();
  129. image = QImage(imagePath);
  130. if (image.isNull()) {
  131. qWarning() << "image is null";
  132. return false;
  133. }
  134. title = "Insert Image From File";
  135. } else {
  136. imagePath = imageUrl.toString();
  137. title = "Insert Image From Network";
  138. }
  139. VInsertImageDialog dialog(title, c_defaultImageTitle,
  140. imagePath, m_editor->getEditor());
  141. dialog.setBrowseable(false, true);
  142. if (isLocal) {
  143. dialog.setImage(image);
  144. } else {
  145. // Download it to a QImage
  146. VDownloader *downloader = new VDownloader(&dialog);
  147. connect(downloader, &VDownloader::downloadFinished,
  148. &dialog, &VInsertImageDialog::imageDownloaded);
  149. downloader->download(imageUrl.toString());
  150. }
  151. if (dialog.exec() == QDialog::Accepted) {
  152. if (isLocal) {
  153. insertImageFromPath(dialog.getImageTitleInput(),
  154. m_file->fetchImageFolderPath(),
  155. m_file->getImageFolderInLink(),
  156. imagePath);
  157. } else {
  158. insertImageFromQImage(dialog.getImageTitleInput(),
  159. m_file->fetchImageFolderPath(),
  160. m_file->getImageFolderInLink(),
  161. dialog.getImage());
  162. }
  163. }
  164. return true;
  165. }
  166. bool VMdEditOperations::insertImage()
  167. {
  168. VInsertImageDialog dialog(tr("Insert Image From File"),
  169. c_defaultImageTitle, "", m_editor->getEditor());
  170. if (dialog.exec() == QDialog::Accepted) {
  171. QString title = dialog.getImageTitleInput();
  172. QString imagePath = dialog.getPathInput();
  173. qDebug() << "insert image from" << imagePath << "as" << title;
  174. insertImageFromPath(title,
  175. m_file->fetchImageFolderPath(),
  176. m_file->getImageFolderInLink(),
  177. imagePath);
  178. }
  179. return true;
  180. }
  181. bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
  182. {
  183. if (m_editConfig->m_enableVimMode
  184. && m_vim->handleKeyPressEvent(p_event, &m_autoIndentPos)) {
  185. return true;
  186. }
  187. bool ret = false;
  188. int key = p_event->key();
  189. int modifiers = p_event->modifiers();
  190. switch (key) {
  191. case Qt::Key_1:
  192. case Qt::Key_2:
  193. case Qt::Key_3:
  194. case Qt::Key_4:
  195. case Qt::Key_5:
  196. case Qt::Key_6:
  197. case Qt::Key_7:
  198. {
  199. if (modifiers == Qt::ControlModifier) {
  200. // Ctrl + <N>: insert title at level <N>.
  201. if (decorateHeading(key == Qt::Key_7 ? 0 : key - Qt::Key_0)) {
  202. p_event->accept();
  203. ret = true;
  204. goto exit;
  205. }
  206. }
  207. break;
  208. }
  209. case Qt::Key_Tab:
  210. {
  211. if (handleKeyTab(p_event)) {
  212. ret = true;
  213. goto exit;
  214. }
  215. break;
  216. }
  217. case Qt::Key_Backtab:
  218. {
  219. if (handleKeyBackTab(p_event)) {
  220. ret = true;
  221. goto exit;
  222. }
  223. break;
  224. }
  225. case Qt::Key_B:
  226. {
  227. if (modifiers == Qt::ControlModifier) {
  228. decorateBold();
  229. p_event->accept();
  230. ret = true;
  231. }
  232. break;
  233. }
  234. case Qt::Key_H:
  235. {
  236. if (handleKeyH(p_event)) {
  237. ret = true;
  238. goto exit;
  239. }
  240. break;
  241. }
  242. case Qt::Key_I:
  243. {
  244. if (modifiers == Qt::ControlModifier) {
  245. decorateItalic();
  246. p_event->accept();
  247. ret = true;
  248. }
  249. break;
  250. }
  251. case Qt::Key_L:
  252. {
  253. if (modifiers == Qt::ControlModifier) {
  254. m_editor->insertLink();
  255. p_event->accept();
  256. ret = true;
  257. }
  258. break;
  259. }
  260. case Qt::Key_M:
  261. {
  262. if (modifiers == Qt::ControlModifier) {
  263. decorateCodeBlock();
  264. p_event->accept();
  265. ret = true;
  266. }
  267. break;
  268. }
  269. case Qt::Key_K:
  270. {
  271. if (modifiers == Qt::ControlModifier) {
  272. decorateInlineCode();
  273. p_event->accept();
  274. ret = true;
  275. }
  276. break;
  277. }
  278. case Qt::Key_U:
  279. {
  280. if (handleKeyU(p_event)) {
  281. ret = true;
  282. goto exit;
  283. }
  284. break;
  285. }
  286. case Qt::Key_W:
  287. {
  288. if (handleKeyW(p_event)) {
  289. ret = true;
  290. goto exit;
  291. }
  292. break;
  293. }
  294. case Qt::Key_BracketLeft:
  295. {
  296. if (handleKeyBracketLeft(p_event)) {
  297. ret = true;
  298. goto exit;
  299. }
  300. break;
  301. }
  302. case Qt::Key_Escape:
  303. {
  304. if (handleKeyEsc(p_event)) {
  305. ret = true;
  306. goto exit;
  307. }
  308. break;
  309. }
  310. case Qt::Key_Enter:
  311. // Fall through.
  312. case Qt::Key_Return:
  313. {
  314. if (handleKeyReturn(p_event)) {
  315. ret = true;
  316. goto exit;
  317. }
  318. break;
  319. }
  320. case Qt::Key_D:
  321. {
  322. if (modifiers == Qt::ControlModifier) {
  323. decorateStrikethrough();
  324. p_event->accept();
  325. ret = true;
  326. }
  327. break;
  328. }
  329. default:
  330. break;
  331. }
  332. exit:
  333. // Qt::Key_Return, Qt::Key_Tab and Qt::Key_Backtab will handle m_autoIndentPos.
  334. if (key != Qt::Key_Return
  335. && key != Qt::Key_Enter
  336. && key != Qt::Key_Tab
  337. && key != Qt::Key_Backtab
  338. && key != Qt::Key_Shift
  339. && key != Qt::Key_Control) {
  340. m_autoIndentPos = -1;
  341. }
  342. return ret;
  343. }
  344. // Let Ctrl+[ behave exactly like ESC.
  345. bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event)
  346. {
  347. // 1. If there is any selection, clear it.
  348. // 2. Otherwise, ignore this event and let parent handles it.
  349. if (p_event->modifiers() == Qt::ControlModifier) {
  350. QTextCursor cursor = m_editor->textCursorW();
  351. if (cursor.hasSelection()) {
  352. cursor.clearSelection();
  353. m_editor->setTextCursorW(cursor);
  354. p_event->accept();
  355. return true;
  356. }
  357. }
  358. return false;
  359. }
  360. bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
  361. {
  362. QTextDocument *doc = m_editor->documentW();
  363. QString text(m_editConfig->m_tabSpaces);
  364. if (p_event->modifiers() == Qt::NoModifier) {
  365. QTextCursor cursor = m_editor->textCursorW();
  366. if (cursor.hasSelection()) {
  367. m_autoIndentPos = -1;
  368. cursor.beginEditBlock();
  369. // Indent each selected line.
  370. VEditUtils::indentSelectedBlocks(doc, cursor, text, true);
  371. cursor.endEditBlock();
  372. m_editor->setTextCursorW(cursor);
  373. } else {
  374. // If it is a Tab key following auto list, increase the indent level.
  375. QTextBlock block = cursor.block();
  376. int seq = -1;
  377. if (m_autoIndentPos == cursor.position()
  378. && VEditUtils::isListBlock(block, &seq)) {
  379. QTextCursor blockCursor(block);
  380. blockCursor.beginEditBlock();
  381. blockCursor.insertText(text);
  382. if (seq != -1) {
  383. changeListBlockSeqNumber(block, 1);
  384. }
  385. blockCursor.endEditBlock();
  386. // Change m_autoIndentPos to let it can be repeated.
  387. m_autoIndentPos = m_editor->textCursorW().position();
  388. } else {
  389. // Just insert "tab".
  390. insertTextAtCurPos(text);
  391. m_autoIndentPos = -1;
  392. }
  393. }
  394. } else {
  395. m_autoIndentPos = -1;
  396. return false;
  397. }
  398. p_event->accept();
  399. return true;
  400. }
  401. bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
  402. {
  403. if (p_event->modifiers() != Qt::ShiftModifier) {
  404. m_autoIndentPos = -1;
  405. return false;
  406. }
  407. QTextDocument *doc = m_editor->documentW();
  408. QTextCursor cursor = m_editor->textCursorW();
  409. QTextBlock block = doc->findBlock(cursor.selectionStart());
  410. bool continueAutoIndent = false;
  411. int seq = -1;
  412. if (cursor.position() == m_autoIndentPos
  413. && VEditUtils::isListBlock(block, &seq) &&
  414. !cursor.hasSelection()) {
  415. continueAutoIndent = true;
  416. }
  417. cursor.beginEditBlock();
  418. if (continueAutoIndent && seq != -1) {
  419. changeListBlockSeqNumber(block, 1);
  420. }
  421. VEditUtils::indentSelectedBlocks(doc, cursor, m_editConfig->m_tabSpaces, false);
  422. cursor.endEditBlock();
  423. if (continueAutoIndent) {
  424. m_autoIndentPos = m_editor->textCursorW().position();
  425. } else {
  426. m_autoIndentPos = -1;
  427. }
  428. p_event->accept();
  429. return true;
  430. }
  431. bool VMdEditOperations::handleKeyH(QKeyEvent *p_event)
  432. {
  433. if (p_event->modifiers() == Qt::ControlModifier) {
  434. // Ctrl+H, equal to backspace.
  435. QTextCursor cursor = m_editor->textCursorW();
  436. cursor.deletePreviousChar();
  437. p_event->accept();
  438. return true;
  439. }
  440. return false;
  441. }
  442. bool VMdEditOperations::handleKeyU(QKeyEvent *p_event)
  443. {
  444. if (p_event->modifiers() == Qt::ControlModifier) {
  445. // Ctrl+U, delete till the start of line.
  446. QTextCursor cursor = m_editor->textCursorW();
  447. bool ret;
  448. if (cursor.atBlockStart()) {
  449. ret = cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
  450. } else {
  451. ret = cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
  452. }
  453. if (ret) {
  454. cursor.removeSelectedText();
  455. }
  456. p_event->accept();
  457. return true;
  458. }
  459. return false;
  460. }
  461. bool VMdEditOperations::handleKeyW(QKeyEvent *p_event)
  462. {
  463. if (p_event->modifiers() == Qt::ControlModifier) {
  464. // Ctrl+W, delete till the start of previous word.
  465. QTextCursor cursor = m_editor->textCursorW();
  466. if (cursor.hasSelection()) {
  467. cursor.removeSelectedText();
  468. } else {
  469. bool ret = cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
  470. if (ret) {
  471. cursor.removeSelectedText();
  472. }
  473. }
  474. p_event->accept();
  475. return true;
  476. }
  477. return false;
  478. }
  479. bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event)
  480. {
  481. // 1. If there is any selection, clear it.
  482. // 2. Otherwise, ignore this event and let parent handles it.
  483. QTextCursor cursor = m_editor->textCursorW();
  484. if (cursor.hasSelection()) {
  485. cursor.clearSelection();
  486. m_editor->setTextCursorW(cursor);
  487. p_event->accept();
  488. return true;
  489. }
  490. return false;
  491. }
  492. bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
  493. {
  494. bool autolist = true;
  495. if (p_event->modifiers() & Qt::ControlModifier) {
  496. m_autoIndentPos = -1;
  497. return false;
  498. } else if (p_event->modifiers() & Qt::ShiftModifier) {
  499. // Insert two spaces and a new line.
  500. m_autoIndentPos = -1;
  501. QTextCursor cursor = m_editor->textCursorW();
  502. cursor.beginEditBlock();
  503. cursor.removeSelectedText();
  504. cursor.insertText(" ");
  505. cursor.endEditBlock();
  506. // Let remaining logics handle inserting the new block except that we
  507. // do not need to insert auto list.
  508. autolist = false;
  509. }
  510. // See if we need to cancel auto indent.
  511. if (m_autoIndentPos > -1) {
  512. // Cancel the auto indent/list if the pos is the same and cursor is at
  513. // the end of a block.
  514. QTextCursor cursor = m_editor->textCursorW();
  515. if (VEditUtils::needToCancelAutoIndent(m_autoIndentPos, cursor)) {
  516. m_autoIndentPos = -1;
  517. VEditUtils::deleteIndentAndListMark(cursor);
  518. m_editor->setTextCursorW(cursor);
  519. return true;
  520. }
  521. }
  522. bool handled = false;
  523. m_autoIndentPos = -1;
  524. if (g_config->getAutoIndent()) {
  525. handled = true;
  526. QTextCursor cursor = m_editor->textCursorW();
  527. bool textInserted = false;
  528. cursor.beginEditBlock();
  529. cursor.removeSelectedText();
  530. // Indent the new line as previous line.
  531. textInserted = VEditUtils::insertBlockWithIndent(cursor);
  532. // Continue the list from previous line.
  533. if (g_config->getAutoList() && autolist) {
  534. textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor) || textInserted;
  535. }
  536. cursor.endEditBlock();
  537. m_editor->setTextCursorW(cursor);
  538. if (textInserted) {
  539. m_autoIndentPos = m_editor->textCursorW().position();
  540. }
  541. }
  542. return handled;
  543. }
  544. void VMdEditOperations::changeListBlockSeqNumber(QTextBlock &p_block, int p_seq)
  545. {
  546. QString text = p_block.text();
  547. QRegExp regExp("^(\\s*)(\\d+)\\.\\s");
  548. int idx = regExp.indexIn(text);
  549. if (idx == -1 || regExp.captureCount() != 2) {
  550. return;
  551. }
  552. int oriSeq = -1;
  553. bool ok = false;
  554. oriSeq = regExp.capturedTexts()[2].toInt(&ok);
  555. if (ok && oriSeq == p_seq) {
  556. return;
  557. }
  558. QTextCursor cursor(p_block);
  559. bool ret = cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor,
  560. regExp.capturedTexts()[1].size());
  561. if (!ret) {
  562. return;
  563. }
  564. ret = cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,
  565. regExp.capturedTexts()[2].size());
  566. if (!ret) {
  567. return;
  568. }
  569. cursor.removeSelectedText();
  570. cursor.insertText(QString::number(p_seq));
  571. }
  572. bool VMdEditOperations::decorateHeading(int p_level)
  573. {
  574. QTextDocument *doc = m_editor->documentW();
  575. QTextCursor cursor = m_editor->textCursorW();
  576. int firstBlock = cursor.block().blockNumber();
  577. int lastBlock = firstBlock;
  578. if (cursor.hasSelection()) {
  579. // Insert title # in front of the selected blocks.
  580. int start = cursor.selectionStart();
  581. int end = cursor.selectionEnd();
  582. firstBlock = doc->findBlock(start).blockNumber();
  583. lastBlock = doc->findBlock(end).blockNumber();
  584. }
  585. cursor.beginEditBlock();
  586. for (int i = firstBlock; i <= lastBlock; ++i) {
  587. VEditUtils::insertTitleMark(cursor, doc->findBlockByNumber(i), p_level);
  588. }
  589. cursor.endEditBlock();
  590. m_editor->setTextCursorW(cursor);
  591. return true;
  592. }
  593. void VMdEditOperations::decorateText(TextDecoration p_decoration, int p_level)
  594. {
  595. if (p_decoration == TextDecoration::None) {
  596. return;
  597. }
  598. bool validDecoration = true;
  599. switch (p_decoration) {
  600. case TextDecoration::Bold:
  601. decorateBold();
  602. break;
  603. case TextDecoration::Italic:
  604. decorateItalic();
  605. break;
  606. case TextDecoration::Strikethrough:
  607. decorateStrikethrough();
  608. break;
  609. case TextDecoration::InlineCode:
  610. decorateInlineCode();
  611. break;
  612. case TextDecoration::CodeBlock:
  613. decorateCodeBlock();
  614. break;
  615. case TextDecoration::Heading:
  616. decorateHeading(p_level);
  617. break;
  618. default:
  619. validDecoration = false;
  620. qDebug() << "decoration" << (int)p_decoration << "is not implemented yet";
  621. break;
  622. }
  623. if (validDecoration && m_editConfig->m_enableVimMode) {
  624. Q_ASSERT(m_vim);
  625. m_vim->setMode(VimMode::Insert, false);
  626. }
  627. }
  628. void VMdEditOperations::decorateBold()
  629. {
  630. QTextCursor cursor = m_editor->textCursorW();
  631. cursor.beginEditBlock();
  632. if (cursor.hasSelection()) {
  633. // Insert ** around the selected text.
  634. int start = cursor.selectionStart();
  635. int end = cursor.selectionEnd();
  636. cursor.clearSelection();
  637. cursor.setPosition(start, QTextCursor::MoveAnchor);
  638. cursor.insertText("**");
  639. cursor.setPosition(end + 2, QTextCursor::MoveAnchor);
  640. cursor.insertText("**");
  641. } else {
  642. // Insert **** and place cursor in the middle.
  643. // Or if there are two * after current cursor, just skip them or delete
  644. // them if four * appear.
  645. int pos = cursor.positionInBlock();
  646. bool hasStars = false;
  647. bool emptyMarkers = false;
  648. QString text = cursor.block().text();
  649. if (pos <= text.size() - 2) {
  650. if (text[pos] == '*' && text[pos + 1] == '*') {
  651. hasStars = true;
  652. if (pos >= 2
  653. && text[pos - 1] == '*'
  654. && text[pos - 2] == '*') {
  655. emptyMarkers = true;
  656. }
  657. }
  658. }
  659. if (hasStars) {
  660. if (emptyMarkers) {
  661. cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 2);
  662. cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
  663. cursor.removeSelectedText();
  664. } else {
  665. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2);
  666. }
  667. } else {
  668. cursor.insertText("****");
  669. cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2);
  670. }
  671. }
  672. cursor.endEditBlock();
  673. m_editor->setTextCursorW(cursor);
  674. }
  675. void VMdEditOperations::decorateItalic()
  676. {
  677. QTextCursor cursor = m_editor->textCursorW();
  678. cursor.beginEditBlock();
  679. if (cursor.hasSelection()) {
  680. // Insert * around the selected text.
  681. int start = cursor.selectionStart();
  682. int end = cursor.selectionEnd();
  683. cursor.clearSelection();
  684. cursor.setPosition(start, QTextCursor::MoveAnchor);
  685. cursor.insertText("*");
  686. cursor.setPosition(end + 1, QTextCursor::MoveAnchor);
  687. cursor.insertText("*");
  688. } else {
  689. // Insert ** and place cursor in the middle.
  690. // Or if there are one * after current cursor, just skip them or delete
  691. // them if two * appear.
  692. int pos = cursor.positionInBlock();
  693. bool hasStar = false;
  694. bool emptyMarkers = false;
  695. QString text = cursor.block().text();
  696. if (pos <= text.size() - 1) {
  697. if (text[pos] == '*'
  698. && (pos == text.size() - 1 || text[pos + 1] != '*')) {
  699. hasStar = true;
  700. if (pos >= 1 && text[pos - 1] == '*') {
  701. emptyMarkers = true;
  702. }
  703. }
  704. }
  705. if (hasStar) {
  706. if (emptyMarkers) {
  707. cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 1);
  708. cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2);
  709. cursor.removeSelectedText();
  710. } else {
  711. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
  712. }
  713. } else {
  714. cursor.insertText("**");
  715. cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1);
  716. }
  717. }
  718. cursor.endEditBlock();
  719. m_editor->setTextCursorW(cursor);
  720. }
  721. void VMdEditOperations::decorateInlineCode()
  722. {
  723. QTextCursor cursor = m_editor->textCursorW();
  724. cursor.beginEditBlock();
  725. if (cursor.hasSelection()) {
  726. // Insert ` around the selected text.
  727. int start = cursor.selectionStart();
  728. int end = cursor.selectionEnd();
  729. cursor.clearSelection();
  730. cursor.setPosition(start, QTextCursor::MoveAnchor);
  731. cursor.insertText("`");
  732. cursor.setPosition(end + 1, QTextCursor::MoveAnchor);
  733. cursor.insertText("`");
  734. } else {
  735. // Insert `` and place cursor in the middle.
  736. // Or if there are one ` after current cursor, just skip them or delete
  737. // them if two ` appear.
  738. int pos = cursor.positionInBlock();
  739. bool hasBackquote = false;
  740. bool emptyMarkers = false;
  741. QString text = cursor.block().text();
  742. if (pos <= text.size() - 1) {
  743. if (text[pos] == '`') {
  744. hasBackquote = true;
  745. if (pos >= 1 && text[pos - 1] == '`') {
  746. emptyMarkers = true;
  747. }
  748. }
  749. }
  750. if (hasBackquote) {
  751. if (emptyMarkers) {
  752. cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 1);
  753. cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2);
  754. cursor.removeSelectedText();
  755. } else {
  756. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
  757. }
  758. } else {
  759. cursor.insertText("``");
  760. cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1);
  761. }
  762. }
  763. cursor.endEditBlock();
  764. m_editor->setTextCursorW(cursor);
  765. }
  766. void VMdEditOperations::decorateCodeBlock()
  767. {
  768. const QString marker("```");
  769. QTextCursor cursor = m_editor->textCursorW();
  770. cursor.beginEditBlock();
  771. if (cursor.hasSelection()) {
  772. // Insert ``` around the selected text.
  773. int start = cursor.selectionStart();
  774. int end = cursor.selectionEnd();
  775. QString indentation = VEditUtils::fetchIndentSpaces(cursor.block());
  776. // Insert the end marker first.
  777. cursor.setPosition(end, QTextCursor::MoveAnchor);
  778. VEditUtils::insertBlock(cursor, false);
  779. VEditUtils::indentBlock(cursor, indentation);
  780. cursor.insertText(marker);
  781. // Insert the start marker.
  782. cursor.setPosition(start, QTextCursor::MoveAnchor);
  783. VEditUtils::insertBlock(cursor, true);
  784. VEditUtils::indentBlock(cursor, indentation);
  785. cursor.insertText(marker);
  786. } else {
  787. // Insert ``` ``` and place cursor after the first marker.
  788. // Or if current block or next block is ```, we will skip it.
  789. QTextBlock block = cursor.block();
  790. int state = block.userState();
  791. if (state == HighlightBlockState::CodeBlock
  792. || state == HighlightBlockState::CodeBlockStart
  793. || state == HighlightBlockState::CodeBlockEnd) {
  794. // Find the block end.
  795. QTextBlock endBlock = block;
  796. while (endBlock.isValid()) {
  797. if (endBlock.userState() == HighlightBlockState::CodeBlockEnd) {
  798. break;
  799. }
  800. endBlock = endBlock.next();
  801. }
  802. if (endBlock.isValid()) {
  803. // It is CodeBlockEnd.
  804. if (endBlock.previous().isValid()
  805. && endBlock.previous().userState() == HighlightBlockState::CodeBlockStart) {
  806. // Delete empty code blocks.
  807. cursor.setPosition(endBlock.previous().position());
  808. cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
  809. cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
  810. cursor.removeSelectedText();
  811. } else {
  812. cursor.setPosition(endBlock.position());
  813. if (endBlock.next().isValid()) {
  814. cursor.movePosition(QTextCursor::NextBlock);
  815. cursor.movePosition(QTextCursor::StartOfBlock);
  816. } else {
  817. cursor.movePosition(QTextCursor::EndOfBlock);
  818. }
  819. }
  820. } else {
  821. // Reach the end of the document.
  822. cursor.movePosition(QTextCursor::End);
  823. }
  824. } else {
  825. bool insertInline = false;
  826. if (!cursor.atBlockEnd()) {
  827. cursor.insertBlock();
  828. cursor.movePosition(QTextCursor::PreviousBlock);
  829. } else if (cursor.atBlockStart() || VEditUtils::isSpaceBlock(block)) {
  830. insertInline = true;
  831. }
  832. if (!insertInline) {
  833. VEditUtils::insertBlock(cursor, false);
  834. VEditUtils::indentBlockAsBlock(cursor, false);
  835. }
  836. cursor.insertText(marker);
  837. VEditUtils::insertBlock(cursor, true);
  838. VEditUtils::indentBlockAsBlock(cursor, true);
  839. cursor.insertText(marker);
  840. }
  841. }
  842. cursor.endEditBlock();
  843. m_editor->setTextCursorW(cursor);
  844. }
  845. void VMdEditOperations::decorateStrikethrough()
  846. {
  847. QTextCursor cursor = m_editor->textCursorW();
  848. cursor.beginEditBlock();
  849. if (cursor.hasSelection()) {
  850. // Insert ~~ around the selected text.
  851. int start = cursor.selectionStart();
  852. int end = cursor.selectionEnd();
  853. cursor.clearSelection();
  854. cursor.setPosition(start, QTextCursor::MoveAnchor);
  855. cursor.insertText("~~");
  856. cursor.setPosition(end + 2, QTextCursor::MoveAnchor);
  857. cursor.insertText("~~");
  858. } else {
  859. // Insert ~~~~ and place cursor in the middle.
  860. // Or if there are one ~~ after current cursor, just skip it or delete
  861. // it if for ~ appear.
  862. int pos = cursor.positionInBlock();
  863. bool hasStrikethrough = false;
  864. bool emptyMarkers = false;
  865. QString text = cursor.block().text();
  866. if (pos <= text.size() - 2) {
  867. if (text[pos] == '~' && text[pos + 1] == '~') {
  868. hasStrikethrough = true;
  869. if (pos >= 2
  870. && text[pos - 1] == '~'
  871. && text[pos - 2] == '~') {
  872. emptyMarkers = true;
  873. }
  874. }
  875. }
  876. if (hasStrikethrough) {
  877. if (emptyMarkers) {
  878. cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, 2);
  879. cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
  880. cursor.removeSelectedText();
  881. } else {
  882. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2);
  883. }
  884. } else {
  885. cursor.insertText("~~~~");
  886. cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2);
  887. }
  888. }
  889. cursor.endEditBlock();
  890. m_editor->setTextCursorW(cursor);
  891. }
  892. bool VMdEditOperations::insertLink(const QString &p_linkText,
  893. const QString &p_linkUrl)
  894. {
  895. QString link = QString("[%1](%2)").arg(p_linkText).arg(p_linkUrl);
  896. QTextCursor cursor = m_editor->textCursorW();
  897. cursor.insertText(link);
  898. m_editor->setTextCursorW(cursor);
  899. setVimMode(VimMode::Insert);
  900. return true;
  901. }