vmdeditoperations.cpp 37 KB

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