vmdeditoperations.cpp 32 KB

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