vmdeditoperations.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  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. {
  198. if (modifiers == Qt::ControlModifier) {
  199. // Ctrl + <N>: insert title at level <N>.
  200. if (insertTitle(key - Qt::Key_0)) {
  201. p_event->accept();
  202. ret = true;
  203. goto exit;
  204. }
  205. }
  206. break;
  207. }
  208. case Qt::Key_Tab:
  209. {
  210. if (handleKeyTab(p_event)) {
  211. ret = true;
  212. goto exit;
  213. }
  214. break;
  215. }
  216. case Qt::Key_Backtab:
  217. {
  218. if (handleKeyBackTab(p_event)) {
  219. ret = true;
  220. goto exit;
  221. }
  222. break;
  223. }
  224. case Qt::Key_B:
  225. {
  226. if (modifiers == Qt::ControlModifier) {
  227. decorateBold();
  228. p_event->accept();
  229. ret = true;
  230. }
  231. break;
  232. }
  233. case Qt::Key_H:
  234. {
  235. if (handleKeyH(p_event)) {
  236. ret = true;
  237. goto exit;
  238. }
  239. break;
  240. }
  241. case Qt::Key_I:
  242. {
  243. if (modifiers == Qt::ControlModifier) {
  244. decorateItalic();
  245. p_event->accept();
  246. ret = true;
  247. }
  248. break;
  249. }
  250. case Qt::Key_L:
  251. {
  252. if (modifiers == Qt::ControlModifier) {
  253. m_editor->insertLink();
  254. p_event->accept();
  255. ret = true;
  256. }
  257. break;
  258. }
  259. case Qt::Key_M:
  260. {
  261. if (modifiers == Qt::ControlModifier) {
  262. decorateCodeBlock();
  263. p_event->accept();
  264. ret = true;
  265. }
  266. break;
  267. }
  268. case Qt::Key_K:
  269. {
  270. if (modifiers == Qt::ControlModifier) {
  271. decorateInlineCode();
  272. p_event->accept();
  273. ret = true;
  274. }
  275. break;
  276. }
  277. case Qt::Key_U:
  278. {
  279. if (handleKeyU(p_event)) {
  280. ret = true;
  281. goto exit;
  282. }
  283. break;
  284. }
  285. case Qt::Key_W:
  286. {
  287. if (handleKeyW(p_event)) {
  288. ret = true;
  289. goto exit;
  290. }
  291. break;
  292. }
  293. case Qt::Key_BracketLeft:
  294. {
  295. if (handleKeyBracketLeft(p_event)) {
  296. ret = true;
  297. goto exit;
  298. }
  299. break;
  300. }
  301. case Qt::Key_Escape:
  302. {
  303. if (handleKeyEsc(p_event)) {
  304. ret = true;
  305. goto exit;
  306. }
  307. break;
  308. }
  309. case Qt::Key_Enter:
  310. // Fall through.
  311. case Qt::Key_Return:
  312. {
  313. if (handleKeyReturn(p_event)) {
  314. ret = true;
  315. goto exit;
  316. }
  317. break;
  318. }
  319. case Qt::Key_D:
  320. {
  321. if (modifiers == Qt::ControlModifier) {
  322. decorateStrikethrough();
  323. p_event->accept();
  324. ret = true;
  325. }
  326. break;
  327. }
  328. default:
  329. break;
  330. }
  331. exit:
  332. // Qt::Key_Return, Qt::Key_Tab and Qt::Key_Backtab will handle m_autoIndentPos.
  333. if (key != Qt::Key_Return
  334. && key != Qt::Key_Enter
  335. && key != Qt::Key_Tab
  336. && key != Qt::Key_Backtab
  337. && key != Qt::Key_Shift
  338. && key != Qt::Key_Control) {
  339. m_autoIndentPos = -1;
  340. }
  341. return ret;
  342. }
  343. // Let Ctrl+[ behave exactly like ESC.
  344. bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event)
  345. {
  346. // 1. If there is any selection, clear it.
  347. // 2. Otherwise, ignore this event and let parent handles it.
  348. if (p_event->modifiers() == Qt::ControlModifier) {
  349. QTextCursor cursor = m_editor->textCursorW();
  350. if (cursor.hasSelection()) {
  351. cursor.clearSelection();
  352. m_editor->setTextCursorW(cursor);
  353. p_event->accept();
  354. return true;
  355. }
  356. }
  357. return false;
  358. }
  359. bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
  360. {
  361. QTextDocument *doc = m_editor->documentW();
  362. QString text(m_editConfig->m_tabSpaces);
  363. if (p_event->modifiers() == Qt::NoModifier) {
  364. QTextCursor cursor = m_editor->textCursorW();
  365. if (cursor.hasSelection()) {
  366. m_autoIndentPos = -1;
  367. cursor.beginEditBlock();
  368. // Indent each selected line.
  369. VEditUtils::indentSelectedBlocks(doc, cursor, text, true);
  370. cursor.endEditBlock();
  371. m_editor->setTextCursorW(cursor);
  372. } else {
  373. // If it is a Tab key following auto list, increase the indent level.
  374. QTextBlock block = cursor.block();
  375. int seq = -1;
  376. if (m_autoIndentPos == cursor.position()
  377. && VEditUtils::isListBlock(block, &seq)) {
  378. QTextCursor blockCursor(block);
  379. blockCursor.beginEditBlock();
  380. blockCursor.insertText(text);
  381. if (seq != -1) {
  382. changeListBlockSeqNumber(block, 1);
  383. }
  384. blockCursor.endEditBlock();
  385. // Change m_autoIndentPos to let it can be repeated.
  386. m_autoIndentPos = m_editor->textCursorW().position();
  387. } else {
  388. // Just insert "tab".
  389. insertTextAtCurPos(text);
  390. m_autoIndentPos = -1;
  391. }
  392. }
  393. } else {
  394. m_autoIndentPos = -1;
  395. return false;
  396. }
  397. p_event->accept();
  398. return true;
  399. }
  400. bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
  401. {
  402. if (p_event->modifiers() != Qt::ShiftModifier) {
  403. m_autoIndentPos = -1;
  404. return false;
  405. }
  406. QTextDocument *doc = m_editor->documentW();
  407. QTextCursor cursor = m_editor->textCursorW();
  408. QTextBlock block = doc->findBlock(cursor.selectionStart());
  409. bool continueAutoIndent = false;
  410. int seq = -1;
  411. if (cursor.position() == m_autoIndentPos
  412. && VEditUtils::isListBlock(block, &seq) &&
  413. !cursor.hasSelection()) {
  414. continueAutoIndent = true;
  415. }
  416. cursor.beginEditBlock();
  417. if (continueAutoIndent && seq != -1) {
  418. changeListBlockSeqNumber(block, 1);
  419. }
  420. VEditUtils::indentSelectedBlocks(doc, cursor, m_editConfig->m_tabSpaces, false);
  421. cursor.endEditBlock();
  422. if (continueAutoIndent) {
  423. m_autoIndentPos = m_editor->textCursorW().position();
  424. } else {
  425. m_autoIndentPos = -1;
  426. }
  427. p_event->accept();
  428. return true;
  429. }
  430. bool VMdEditOperations::handleKeyH(QKeyEvent *p_event)
  431. {
  432. if (p_event->modifiers() == Qt::ControlModifier) {
  433. // Ctrl+H, equal to backspace.
  434. QTextCursor cursor = m_editor->textCursorW();
  435. cursor.deletePreviousChar();
  436. p_event->accept();
  437. return true;
  438. }
  439. return false;
  440. }
  441. bool VMdEditOperations::handleKeyU(QKeyEvent *p_event)
  442. {
  443. if (p_event->modifiers() == Qt::ControlModifier) {
  444. // Ctrl+U, delete till the start of line.
  445. QTextCursor cursor = m_editor->textCursorW();
  446. bool ret;
  447. if (cursor.atBlockStart()) {
  448. ret = cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
  449. } else {
  450. ret = cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
  451. }
  452. if (ret) {
  453. cursor.removeSelectedText();
  454. }
  455. p_event->accept();
  456. return true;
  457. }
  458. return false;
  459. }
  460. bool VMdEditOperations::handleKeyW(QKeyEvent *p_event)
  461. {
  462. if (p_event->modifiers() == Qt::ControlModifier) {
  463. // Ctrl+W, delete till the start of previous word.
  464. QTextCursor cursor = m_editor->textCursorW();
  465. if (cursor.hasSelection()) {
  466. cursor.removeSelectedText();
  467. } else {
  468. bool ret = cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
  469. if (ret) {
  470. cursor.removeSelectedText();
  471. }
  472. }
  473. p_event->accept();
  474. return true;
  475. }
  476. return false;
  477. }
  478. bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event)
  479. {
  480. // 1. If there is any selection, clear it.
  481. // 2. Otherwise, ignore this event and let parent handles it.
  482. QTextCursor cursor = m_editor->textCursorW();
  483. if (cursor.hasSelection()) {
  484. cursor.clearSelection();
  485. m_editor->setTextCursorW(cursor);
  486. p_event->accept();
  487. return true;
  488. }
  489. return false;
  490. }
  491. bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
  492. {
  493. bool autolist = true;
  494. if (p_event->modifiers() & Qt::ControlModifier) {
  495. m_autoIndentPos = -1;
  496. return false;
  497. } else if (p_event->modifiers() & Qt::ShiftModifier) {
  498. // Insert two spaces and a new line.
  499. m_autoIndentPos = -1;
  500. QTextCursor cursor = m_editor->textCursorW();
  501. cursor.beginEditBlock();
  502. cursor.removeSelectedText();
  503. cursor.insertText(" ");
  504. cursor.endEditBlock();
  505. // Let remaining logics handle inserting the new block except that we
  506. // do not need to insert auto list.
  507. autolist = false;
  508. }
  509. // See if we need to cancel auto indent.
  510. if (m_autoIndentPos > -1) {
  511. // Cancel the auto indent/list if the pos is the same and cursor is at
  512. // the end of a block.
  513. QTextCursor cursor = m_editor->textCursorW();
  514. if (VEditUtils::needToCancelAutoIndent(m_autoIndentPos, cursor)) {
  515. m_autoIndentPos = -1;
  516. VEditUtils::deleteIndentAndListMark(cursor);
  517. m_editor->setTextCursorW(cursor);
  518. return true;
  519. }
  520. }
  521. bool handled = false;
  522. m_autoIndentPos = -1;
  523. if (g_config->getAutoIndent()) {
  524. handled = true;
  525. QTextCursor cursor = m_editor->textCursorW();
  526. bool textInserted = false;
  527. cursor.beginEditBlock();
  528. cursor.removeSelectedText();
  529. // Indent the new line as previous line.
  530. textInserted = VEditUtils::insertBlockWithIndent(cursor);
  531. // Continue the list from previous line.
  532. if (g_config->getAutoList() && autolist) {
  533. textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor) || textInserted;
  534. }
  535. cursor.endEditBlock();
  536. m_editor->setTextCursorW(cursor);
  537. if (textInserted) {
  538. m_autoIndentPos = m_editor->textCursorW().position();
  539. }
  540. }
  541. return handled;
  542. }
  543. void VMdEditOperations::changeListBlockSeqNumber(QTextBlock &p_block, int p_seq)
  544. {
  545. QString text = p_block.text();
  546. QRegExp regExp("^(\\s*)(\\d+)\\.\\s");
  547. int idx = regExp.indexIn(text);
  548. if (idx == -1 || regExp.captureCount() != 2) {
  549. return;
  550. }
  551. int oriSeq = -1;
  552. bool ok = false;
  553. oriSeq = regExp.capturedTexts()[2].toInt(&ok);
  554. if (ok && oriSeq == p_seq) {
  555. return;
  556. }
  557. QTextCursor cursor(p_block);
  558. bool ret = cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor,
  559. regExp.capturedTexts()[1].size());
  560. if (!ret) {
  561. return;
  562. }
  563. ret = cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,
  564. regExp.capturedTexts()[2].size());
  565. if (!ret) {
  566. return;
  567. }
  568. cursor.removeSelectedText();
  569. cursor.insertText(QString::number(p_seq));
  570. }
  571. bool VMdEditOperations::insertTitle(int p_level)
  572. {
  573. QTextDocument *doc = m_editor->documentW();
  574. QTextCursor cursor = m_editor->textCursorW();
  575. int firstBlock = cursor.block().blockNumber();
  576. int lastBlock = firstBlock;
  577. if (cursor.hasSelection()) {
  578. // Insert title # in front of the selected blocks.
  579. int start = cursor.selectionStart();
  580. int end = cursor.selectionEnd();
  581. firstBlock = doc->findBlock(start).blockNumber();
  582. lastBlock = doc->findBlock(end).blockNumber();
  583. }
  584. cursor.beginEditBlock();
  585. for (int i = firstBlock; i <= lastBlock; ++i) {
  586. VEditUtils::insertTitleMark(cursor, doc->findBlockByNumber(i), p_level);
  587. }
  588. cursor.endEditBlock();
  589. m_editor->setTextCursorW(cursor);
  590. return true;
  591. }
  592. void VMdEditOperations::decorateText(TextDecoration p_decoration)
  593. {
  594. if (p_decoration == TextDecoration::None) {
  595. return;
  596. }
  597. bool validDecoration = true;
  598. switch (p_decoration) {
  599. case TextDecoration::Bold:
  600. decorateBold();
  601. break;
  602. case TextDecoration::Italic:
  603. decorateItalic();
  604. break;
  605. case TextDecoration::Strikethrough:
  606. decorateStrikethrough();
  607. break;
  608. case TextDecoration::InlineCode:
  609. decorateInlineCode();
  610. break;
  611. case TextDecoration::CodeBlock:
  612. decorateCodeBlock();
  613. break;
  614. default:
  615. validDecoration = false;
  616. qDebug() << "decoration" << (int)p_decoration << "is not implemented yet";
  617. break;
  618. }
  619. if (validDecoration && m_editConfig->m_enableVimMode) {
  620. Q_ASSERT(m_vim);
  621. m_vim->setMode(VimMode::Insert, false);
  622. }
  623. }
  624. void VMdEditOperations::decorateBold()
  625. {
  626. QTextCursor cursor = m_editor->textCursorW();
  627. cursor.beginEditBlock();
  628. if (cursor.hasSelection()) {
  629. // Insert ** around the selected text.
  630. int start = cursor.selectionStart();
  631. int end = cursor.selectionEnd();
  632. cursor.clearSelection();
  633. cursor.setPosition(start, QTextCursor::MoveAnchor);
  634. cursor.insertText("**");
  635. cursor.setPosition(end + 2, QTextCursor::MoveAnchor);
  636. cursor.insertText("**");
  637. } else {
  638. // Insert **** and place cursor in the middle.
  639. // Or if there are two * after current cursor, just skip them.
  640. int pos = cursor.positionInBlock();
  641. bool hasStars = false;
  642. QString text = cursor.block().text();
  643. if (pos <= text.size() - 2) {
  644. if (text[pos] == '*' && text[pos + 1] == '*') {
  645. hasStars = true;
  646. }
  647. }
  648. if (hasStars) {
  649. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2);
  650. } else {
  651. cursor.insertText("****");
  652. cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2);
  653. }
  654. }
  655. cursor.endEditBlock();
  656. m_editor->setTextCursorW(cursor);
  657. }
  658. void VMdEditOperations::decorateItalic()
  659. {
  660. QTextCursor cursor = m_editor->textCursorW();
  661. cursor.beginEditBlock();
  662. if (cursor.hasSelection()) {
  663. // Insert * around the selected text.
  664. int start = cursor.selectionStart();
  665. int end = cursor.selectionEnd();
  666. cursor.clearSelection();
  667. cursor.setPosition(start, QTextCursor::MoveAnchor);
  668. cursor.insertText("*");
  669. cursor.setPosition(end + 1, QTextCursor::MoveAnchor);
  670. cursor.insertText("*");
  671. } else {
  672. // Insert ** and place cursor in the middle.
  673. // Or if there are one * after current cursor, just skip it.
  674. int pos = cursor.positionInBlock();
  675. bool hasStar = false;
  676. QString text = cursor.block().text();
  677. if (pos <= text.size() - 1) {
  678. if (text[pos] == '*') {
  679. hasStar = true;
  680. }
  681. }
  682. if (hasStar) {
  683. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
  684. } else {
  685. cursor.insertText("**");
  686. cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1);
  687. }
  688. }
  689. cursor.endEditBlock();
  690. m_editor->setTextCursorW(cursor);
  691. }
  692. void VMdEditOperations::decorateInlineCode()
  693. {
  694. QTextCursor cursor = m_editor->textCursorW();
  695. cursor.beginEditBlock();
  696. if (cursor.hasSelection()) {
  697. // Insert ` around the selected text.
  698. int start = cursor.selectionStart();
  699. int end = cursor.selectionEnd();
  700. cursor.clearSelection();
  701. cursor.setPosition(start, QTextCursor::MoveAnchor);
  702. cursor.insertText("`");
  703. cursor.setPosition(end + 1, QTextCursor::MoveAnchor);
  704. cursor.insertText("`");
  705. } else {
  706. // Insert `` and place cursor in the middle.
  707. // Or if there are one ` after current cursor, just skip it.
  708. int pos = cursor.positionInBlock();
  709. bool hasBackquote = false;
  710. QString text = cursor.block().text();
  711. if (pos <= text.size() - 1) {
  712. if (text[pos] == '`') {
  713. hasBackquote = true;
  714. }
  715. }
  716. if (hasBackquote) {
  717. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
  718. } else {
  719. cursor.insertText("``");
  720. cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1);
  721. }
  722. }
  723. cursor.endEditBlock();
  724. m_editor->setTextCursorW(cursor);
  725. }
  726. void VMdEditOperations::decorateCodeBlock()
  727. {
  728. const QString marker("```");
  729. QTextCursor cursor = m_editor->textCursorW();
  730. cursor.beginEditBlock();
  731. if (cursor.hasSelection()) {
  732. // Insert ``` around the selected text.
  733. int start = cursor.selectionStart();
  734. int end = cursor.selectionEnd();
  735. QString indentation = VEditUtils::fetchIndentSpaces(cursor.block());
  736. // Insert the end marker first.
  737. cursor.setPosition(end, QTextCursor::MoveAnchor);
  738. VEditUtils::insertBlock(cursor, false);
  739. VEditUtils::indentBlock(cursor, indentation);
  740. cursor.insertText(marker);
  741. // Insert the start marker.
  742. cursor.setPosition(start, QTextCursor::MoveAnchor);
  743. VEditUtils::insertBlock(cursor, true);
  744. VEditUtils::indentBlock(cursor, indentation);
  745. cursor.insertText(marker);
  746. } else {
  747. // Insert ``` ``` and place cursor after the first marker.
  748. // Or if current block or next block is ```, we will skip it.
  749. QTextBlock block = cursor.block();
  750. int state = block.userState();
  751. if (state == HighlightBlockState::CodeBlock
  752. || state == HighlightBlockState::CodeBlockStart
  753. || state == HighlightBlockState::CodeBlockEnd) {
  754. // Find the block end.
  755. while (block.isValid()) {
  756. if (block.userState() == HighlightBlockState::CodeBlockEnd) {
  757. break;
  758. }
  759. block = block.next();
  760. }
  761. if (block.isValid()) {
  762. // It is CodeBlockEnd.
  763. cursor.setPosition(block.position());
  764. if (block.next().isValid()) {
  765. cursor.movePosition(QTextCursor::NextBlock);
  766. cursor.movePosition(QTextCursor::StartOfBlock);
  767. } else {
  768. cursor.movePosition(QTextCursor::EndOfBlock);
  769. }
  770. } else {
  771. // Reach the end of the document.
  772. cursor.movePosition(QTextCursor::End);
  773. }
  774. } else {
  775. bool insertInline = false;
  776. if (!cursor.atBlockEnd()) {
  777. cursor.insertBlock();
  778. cursor.movePosition(QTextCursor::PreviousBlock);
  779. } else if (cursor.atBlockStart() || VEditUtils::isSpaceBlock(block)) {
  780. insertInline = true;
  781. }
  782. if (!insertInline) {
  783. VEditUtils::insertBlock(cursor, false);
  784. VEditUtils::indentBlockAsBlock(cursor, false);
  785. }
  786. cursor.insertText(marker);
  787. VEditUtils::insertBlock(cursor, true);
  788. VEditUtils::indentBlockAsBlock(cursor, true);
  789. cursor.insertText(marker);
  790. }
  791. }
  792. cursor.endEditBlock();
  793. m_editor->setTextCursorW(cursor);
  794. }
  795. void VMdEditOperations::decorateStrikethrough()
  796. {
  797. QTextCursor cursor = m_editor->textCursorW();
  798. cursor.beginEditBlock();
  799. if (cursor.hasSelection()) {
  800. // Insert ~~ around the selected text.
  801. int start = cursor.selectionStart();
  802. int end = cursor.selectionEnd();
  803. cursor.clearSelection();
  804. cursor.setPosition(start, QTextCursor::MoveAnchor);
  805. cursor.insertText("~~");
  806. cursor.setPosition(end + 2, QTextCursor::MoveAnchor);
  807. cursor.insertText("~~");
  808. } else {
  809. // Insert ~~~~ and place cursor in the middle.
  810. // Or if there are one ~~ after current cursor, just skip it.
  811. int pos = cursor.positionInBlock();
  812. bool hasStrikethrough = false;
  813. QString text = cursor.block().text();
  814. if (pos <= text.size() - 2) {
  815. if (text[pos] == '~' && text[pos + 1] == '~') {
  816. hasStrikethrough = true;
  817. }
  818. }
  819. if (hasStrikethrough) {
  820. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2);
  821. } else {
  822. cursor.insertText("~~~~");
  823. cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2);
  824. }
  825. }
  826. cursor.endEditBlock();
  827. m_editor->setTextCursorW(cursor);
  828. }
  829. bool VMdEditOperations::insertLink(const QString &p_linkText,
  830. const QString &p_linkUrl)
  831. {
  832. QString link = QString("[%1](%2)").arg(p_linkText).arg(p_linkUrl);
  833. QTextCursor cursor = m_editor->textCursorW();
  834. cursor.insertText(link);
  835. m_editor->setTextCursorW(cursor);
  836. setVimMode(VimMode::Insert);
  837. return true;
  838. }