vlivepreviewhelper.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. #include "vlivepreviewhelper.h"
  2. #include <QByteArray>
  3. #include <QTimer>
  4. #include "veditor.h"
  5. #include "vdocument.h"
  6. #include "vconfigmanager.h"
  7. #include "vgraphvizhelper.h"
  8. #include "vplantumlhelper.h"
  9. #include "vmainwindow.h"
  10. #include "veditarea.h"
  11. #include "vmathjaxpreviewhelper.h"
  12. #include "utils/veditutils.h"
  13. extern VConfigManager *g_config;
  14. extern VMainWindow *g_mainWin;
  15. // Use the highest 4 bits (31-28) to indicate the lang.
  16. #define LANG_PREFIX_GRAPHVIZ 0x10000000UL
  17. #define LANG_PREFIX_PLANTUML 0x20000000UL
  18. #define LANG_PREFIX_MATHJAX 0x30000000UL
  19. #define LANG_PREFIX_MASK 0xf0000000UL
  20. // Use th 27th bit to indicate the preview type.
  21. #define TYPE_LIVE_PREVIEW 0x0UL
  22. #define TYPE_INPLACE_PREVIEW 0x08000000UL
  23. #define TYPE_MASK 0x08000000UL
  24. #define INDEX_MASK 0x00ffffffUL
  25. CodeBlockPreviewInfo::CodeBlockPreviewInfo()
  26. {
  27. }
  28. CodeBlockPreviewInfo::CodeBlockPreviewInfo(const VCodeBlock &p_cb)
  29. : m_codeBlock(p_cb)
  30. {
  31. }
  32. void CodeBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor,
  33. const QTextDocument *p_doc,
  34. const QPixmap &p_image,
  35. const QString &p_imageName,
  36. const QString &p_background)
  37. {
  38. QTextBlock block = p_doc->findBlockByNumber(m_codeBlock.m_endBlock);
  39. if (block.isValid()) {
  40. VImageToPreview *preview = new VImageToPreview();
  41. preview->m_startPos = block.position();
  42. preview->m_endPos = block.position() + block.length();
  43. preview->m_blockPos = block.position();
  44. preview->m_blockNumber = m_codeBlock.m_endBlock;
  45. preview->m_padding = VPreviewManager::calculateBlockMargin(block,
  46. p_editor->tabStopWidthW());
  47. if (!p_imageName.isEmpty()) {
  48. preview->m_name = p_imageName;
  49. } else {
  50. preview->m_name = QString::number(getImageIndex());
  51. }
  52. preview->m_background = p_background;
  53. preview->m_isBlock = true;
  54. preview->m_image = p_image;
  55. m_inplacePreview.reset(preview);
  56. } else {
  57. m_inplacePreview.clear();
  58. }
  59. }
  60. #define CODE_BLOCK_IMAGE_CACHE_SIZE_DIFF 10
  61. #define CODE_BLOCK_IMAGE_CACHE_TIME_DIFF 5
  62. VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor,
  63. VDocument *p_document,
  64. QObject *p_parent)
  65. : QObject(p_parent),
  66. m_editor(p_editor),
  67. m_document(p_document),
  68. m_doc(p_editor->documentW()),
  69. m_cbIndex(-1),
  70. m_livePreviewEnabled(false),
  71. m_inplacePreviewEnabled(false),
  72. m_graphvizHelper(NULL),
  73. m_plantUMLHelper(NULL),
  74. m_lastInplacePreviewSize(0),
  75. m_timeStamp(0),
  76. m_scaleFactor(VUtils::calculateScaleFactor()),
  77. m_lastCursorBlock(-1)
  78. {
  79. m_livePreviewTimer = new QTimer(this);
  80. m_livePreviewTimer->setSingleShot(true);
  81. m_livePreviewTimer->setInterval(100);
  82. connect(m_livePreviewTimer, &QTimer::timeout,
  83. this, &VLivePreviewHelper::handleCursorPositionChanged);
  84. connect(m_editor->object(), SIGNAL(cursorPositionChanged()),
  85. m_livePreviewTimer, SLOT(start()));
  86. m_flowchartEnabled = g_config->getEnableFlowchart();
  87. m_mermaidEnabled = g_config->getEnableMermaid();
  88. m_plantUMLMode = g_config->getPlantUMLMode();
  89. m_graphvizEnabled = g_config->getEnableGraphviz();
  90. m_mathjaxEnabled = g_config->getEnableMathjax();
  91. m_mathJaxHelper = g_mainWin->getEditArea()->getMathJaxPreviewHelper();
  92. m_mathJaxID = m_mathJaxHelper->registerIdentifier();
  93. connect(m_mathJaxHelper, &VMathJaxPreviewHelper::mathjaxPreviewResultReady,
  94. this, &VLivePreviewHelper::mathjaxPreviewResultReady);
  95. connect(m_mathJaxHelper, &VMathJaxPreviewHelper::diagramPreviewResultReady,
  96. // The same handle logics.
  97. this, &VLivePreviewHelper::mathjaxPreviewResultReady);
  98. }
  99. void VLivePreviewHelper::checkLang(const QString &p_lang,
  100. bool &p_livePreview,
  101. bool &p_inplacePreview) const
  102. {
  103. if (m_flowchartEnabled && (p_lang == "flow" || p_lang == "flowchart")) {
  104. p_livePreview = p_inplacePreview = true;
  105. } else if (m_plantUMLMode != PlantUMLMode::DisablePlantUML && p_lang == "puml") {
  106. p_livePreview = true;
  107. p_inplacePreview = m_plantUMLMode == PlantUMLMode::LocalPlantUML;
  108. } else if (m_graphvizEnabled && p_lang == "dot") {
  109. p_livePreview = p_inplacePreview = true;
  110. } else if (m_mermaidEnabled && p_lang == "mermaid") {
  111. p_livePreview = true;
  112. p_inplacePreview = false;
  113. } else if (m_mathjaxEnabled && p_lang == "mathjax") {
  114. p_livePreview = false;
  115. p_inplacePreview = true;
  116. } else {
  117. p_livePreview = p_inplacePreview = false;
  118. }
  119. }
  120. void VLivePreviewHelper::updateCodeBlocks(TimeStamp p_timeStamp, const QVector<VCodeBlock> &p_codeBlocks)
  121. {
  122. Q_UNUSED(p_timeStamp);
  123. if (!m_livePreviewEnabled && !m_inplacePreviewEnabled) {
  124. return;
  125. }
  126. ++m_timeStamp;
  127. int lastIndex = m_cbIndex;
  128. m_cbIndex = -1;
  129. int cursorBlock = m_editor->textCursorW().block().blockNumber();
  130. bool needUpdate = m_livePreviewEnabled;
  131. bool manualInplacePreview = m_inplacePreviewEnabled;
  132. m_codeBlocks.clear();
  133. for (int i = 0; i < p_codeBlocks.size(); ++i) {
  134. const VCodeBlock &vcb = p_codeBlocks[i];
  135. bool livePreview = false, inplacePreview = false;
  136. checkLang(vcb.m_lang, livePreview, inplacePreview);
  137. if (!livePreview && !inplacePreview) {
  138. continue;
  139. }
  140. const QString &text = vcb.m_text;
  141. bool cached = false;
  142. m_codeBlocks.append(CodeBlockPreviewInfo(vcb));
  143. int idx = m_codeBlocks.size() - 1;
  144. bool oldCache = false;
  145. auto it = m_cache.find(text);
  146. if (it != m_cache.end()) {
  147. QSharedPointer<CodeBlockImageCacheEntry> &entry = it.value();
  148. // If this cache is not used at the last timestamp, we still need to
  149. // update the live preview.
  150. if (entry->m_ts < m_timeStamp - 1) {
  151. oldCache = true;
  152. }
  153. entry->m_ts = m_timeStamp;
  154. cached = true;
  155. m_codeBlocks[idx].setImageData(entry->m_imgFormat, entry->m_imgData);
  156. m_codeBlocks[idx].updateInplacePreview(m_editor,
  157. m_doc,
  158. entry->m_image,
  159. entry->m_imageName,
  160. entry->m_imageBackground);
  161. }
  162. if (m_inplacePreviewEnabled
  163. && inplacePreview
  164. && (!cached || !m_codeBlocks[idx].inplacePreviewReady())) {
  165. manualInplacePreview = false;
  166. processForInplacePreview(idx);
  167. }
  168. if (m_livePreviewEnabled
  169. && livePreview
  170. && vcb.m_startBlock <= cursorBlock
  171. && vcb.m_endBlock >= cursorBlock) {
  172. if (lastIndex == idx && cached && !oldCache) {
  173. needUpdate = false;
  174. }
  175. m_cbIndex = idx;
  176. }
  177. }
  178. if (manualInplacePreview) {
  179. updateInplacePreview();
  180. }
  181. if (needUpdate) {
  182. updateLivePreview();
  183. }
  184. clearObsoleteCache();
  185. }
  186. void VLivePreviewHelper::handleCursorPositionChanged()
  187. {
  188. if (!m_livePreviewEnabled || m_codeBlocks.isEmpty()) {
  189. m_lastCursorBlock = -1;
  190. return;
  191. }
  192. int cursorBlock = m_editor->textCursorW().block().blockNumber();
  193. if (m_lastCursorBlock == cursorBlock) {
  194. return;
  195. }
  196. m_lastCursorBlock = cursorBlock;
  197. int left = 0, right = m_codeBlocks.size() - 1;
  198. int mid = left;
  199. while (left <= right) {
  200. mid = (left + right) / 2;
  201. const CodeBlockPreviewInfo &cb = m_codeBlocks[mid];
  202. const VCodeBlock &vcb = cb.codeBlock();
  203. if (vcb.m_startBlock <= cursorBlock && vcb.m_endBlock >= cursorBlock) {
  204. break;
  205. } else if (vcb.m_startBlock > cursorBlock) {
  206. right = mid - 1;
  207. } else {
  208. left = mid + 1;
  209. }
  210. }
  211. if (left <= right) {
  212. if (m_cbIndex != mid) {
  213. m_cbIndex = mid;
  214. updateLivePreview();
  215. }
  216. }
  217. }
  218. void VLivePreviewHelper::updateLivePreview()
  219. {
  220. if (m_cbIndex < 0) {
  221. return;
  222. }
  223. Q_ASSERT(!(m_cbIndex & ~INDEX_MASK));
  224. const CodeBlockPreviewInfo &cb = m_codeBlocks[m_cbIndex];
  225. const VCodeBlock &vcb = cb.codeBlock();
  226. if (vcb.m_lang == "dot") {
  227. if (!m_graphvizHelper) {
  228. m_graphvizHelper = new VGraphvizHelper(this);
  229. connect(m_graphvizHelper, &VGraphvizHelper::resultReady,
  230. this, &VLivePreviewHelper::localAsyncResultReady);
  231. }
  232. if (!cb.hasImageData()) {
  233. m_graphvizHelper->processAsync(m_cbIndex | LANG_PREFIX_GRAPHVIZ | TYPE_LIVE_PREVIEW,
  234. m_timeStamp,
  235. "svg",
  236. VEditUtils::removeCodeBlockFence(vcb.m_text));
  237. } else {
  238. m_document->setPreviewContent(vcb.m_lang, cb.imageData());
  239. }
  240. } else if (vcb.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) {
  241. if (!m_plantUMLHelper) {
  242. m_plantUMLHelper = new VPlantUMLHelper(this);
  243. connect(m_plantUMLHelper, &VPlantUMLHelper::resultReady,
  244. this, &VLivePreviewHelper::localAsyncResultReady);
  245. }
  246. if (!cb.hasImageData()) {
  247. m_plantUMLHelper->processAsync(m_cbIndex | LANG_PREFIX_PLANTUML | TYPE_LIVE_PREVIEW,
  248. m_timeStamp,
  249. "svg",
  250. VEditUtils::removeCodeBlockFence(vcb.m_text));
  251. } else {
  252. m_document->setPreviewContent(vcb.m_lang, cb.imageData());
  253. }
  254. } else if (vcb.m_lang != "mathjax") {
  255. // No need to live preview MathJax.
  256. m_document->previewCodeBlock(m_cbIndex,
  257. vcb.m_lang,
  258. VEditUtils::removeCodeBlockFence(vcb.m_text),
  259. true);
  260. }
  261. }
  262. void VLivePreviewHelper::setLivePreviewEnabled(bool p_enabled)
  263. {
  264. if (m_livePreviewEnabled == p_enabled) {
  265. return;
  266. }
  267. m_livePreviewEnabled = p_enabled;
  268. if (!m_livePreviewEnabled) {
  269. m_cbIndex = -1;
  270. m_document->previewCodeBlock(-1, "", "", true);
  271. if (!m_inplacePreviewEnabled) {
  272. m_codeBlocks.clear();
  273. m_cache.clear();
  274. updateInplacePreview();
  275. }
  276. }
  277. }
  278. void VLivePreviewHelper::setInplacePreviewEnabled(bool p_enabled)
  279. {
  280. if (m_inplacePreviewEnabled == p_enabled) {
  281. return;
  282. }
  283. m_inplacePreviewEnabled = p_enabled;
  284. if (!m_inplacePreviewEnabled && !m_livePreviewEnabled) {
  285. m_codeBlocks.clear();
  286. m_cache.clear();
  287. }
  288. updateInplacePreview();
  289. }
  290. void VLivePreviewHelper::localAsyncResultReady(int p_id,
  291. TimeStamp p_timeStamp,
  292. const QString &p_format,
  293. const QString &p_result)
  294. {
  295. if (p_timeStamp != m_timeStamp) {
  296. return;
  297. }
  298. Q_UNUSED(p_format);
  299. Q_ASSERT(p_format == "svg");
  300. int idx = p_id & INDEX_MASK;
  301. if (idx >= m_codeBlocks.size()) {
  302. return;
  303. }
  304. bool livePreview = (p_id & TYPE_MASK) == TYPE_LIVE_PREVIEW;
  305. QString lang;
  306. QString background;
  307. switch (p_id & LANG_PREFIX_MASK) {
  308. case LANG_PREFIX_PLANTUML:
  309. lang = "puml";
  310. background = g_config->getEditorPreviewImageBg();
  311. break;
  312. case LANG_PREFIX_GRAPHVIZ:
  313. lang = "dot";
  314. break;
  315. default:
  316. return;
  317. }
  318. CodeBlockPreviewInfo &cb = m_codeBlocks[idx];
  319. const QString &text = cb.codeBlock().m_text;
  320. QSharedPointer<CodeBlockImageCacheEntry> entry(new CodeBlockImageCacheEntry(p_timeStamp,
  321. p_format,
  322. p_result,
  323. background,
  324. getScaleFactor(cb)));
  325. m_cache.insert(text, entry);
  326. cb.setImageData(p_format, p_result);
  327. cb.updateInplacePreview(m_editor, m_doc, entry->m_image, QString(), background);
  328. if (cb.inplacePreview()) {
  329. entry->m_imageName = cb.inplacePreview()->m_name;
  330. }
  331. if (livePreview) {
  332. if (idx != m_cbIndex) {
  333. return;
  334. }
  335. m_document->setPreviewContent(lang, p_result);
  336. } else {
  337. // Inplace preview.
  338. updateInplacePreview();
  339. }
  340. }
  341. void VLivePreviewHelper::processForInplacePreview(int p_idx)
  342. {
  343. CodeBlockPreviewInfo &cb = m_codeBlocks[p_idx];
  344. const VCodeBlock &vcb = cb.codeBlock();
  345. Q_ASSERT(!cb.hasImageData());
  346. if (vcb.m_lang == "dot") {
  347. if (!m_graphvizHelper) {
  348. m_graphvizHelper = new VGraphvizHelper(this);
  349. connect(m_graphvizHelper, &VGraphvizHelper::resultReady,
  350. this, &VLivePreviewHelper::localAsyncResultReady);
  351. }
  352. m_graphvizHelper->processAsync(p_idx | LANG_PREFIX_GRAPHVIZ | TYPE_INPLACE_PREVIEW,
  353. m_timeStamp,
  354. "svg",
  355. VEditUtils::removeCodeBlockFence(vcb.m_text));
  356. } else if (vcb.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) {
  357. if (!m_plantUMLHelper) {
  358. m_plantUMLHelper = new VPlantUMLHelper(this);
  359. connect(m_plantUMLHelper, &VPlantUMLHelper::resultReady,
  360. this, &VLivePreviewHelper::localAsyncResultReady);
  361. }
  362. m_plantUMLHelper->processAsync(p_idx | LANG_PREFIX_PLANTUML | TYPE_INPLACE_PREVIEW,
  363. m_timeStamp,
  364. "svg",
  365. VEditUtils::removeCodeBlockFence(vcb.m_text));
  366. } else if (vcb.m_lang == "flow"
  367. || vcb.m_lang == "flowchart") {
  368. m_mathJaxHelper->previewDiagram(m_mathJaxID,
  369. p_idx,
  370. m_timeStamp,
  371. vcb.m_lang,
  372. VEditUtils::removeCodeBlockFence(vcb.m_text));
  373. } else if (vcb.m_lang == "mathjax") {
  374. m_mathJaxHelper->previewMathJax(m_mathJaxID,
  375. p_idx,
  376. m_timeStamp,
  377. VEditUtils::removeCodeBlockFence(vcb.m_text));
  378. }
  379. }
  380. void VLivePreviewHelper::updateInplacePreview()
  381. {
  382. QSet<int> blocks;
  383. QVector<QSharedPointer<VImageToPreview> > images;
  384. for (int i = 0; i < m_codeBlocks.size(); ++i) {
  385. CodeBlockPreviewInfo &cb = m_codeBlocks[i];
  386. if (cb.inplacePreviewReady()) {
  387. if (!cb.inplacePreview()->m_image.isNull()) {
  388. images.append(cb.inplacePreview());
  389. } else {
  390. blocks.insert(cb.inplacePreview()->m_blockNumber);
  391. }
  392. } else {
  393. blocks.insert(cb.codeBlock().m_endBlock);
  394. }
  395. }
  396. if (images.isEmpty() && m_lastInplacePreviewSize == 0) {
  397. return;
  398. }
  399. emit inplacePreviewCodeBlockUpdated(images);
  400. m_lastInplacePreviewSize = images.size();
  401. if (!blocks.isEmpty()) {
  402. emit checkBlocksForObsoletePreview(blocks.toList());
  403. }
  404. }
  405. void VLivePreviewHelper::mathjaxPreviewResultReady(int p_identitifer,
  406. int p_id,
  407. TimeStamp p_timeStamp,
  408. const QString &p_format,
  409. const QByteArray &p_data)
  410. {
  411. if (p_identitifer != m_mathJaxID || p_timeStamp != m_timeStamp) {
  412. return;
  413. }
  414. if (p_id >= m_codeBlocks.size() || p_data.isEmpty()) {
  415. updateInplacePreview();
  416. return;
  417. }
  418. CodeBlockPreviewInfo &cb = m_codeBlocks[p_id];
  419. const QString &text = cb.codeBlock().m_text;
  420. QSharedPointer<CodeBlockImageCacheEntry> entry(new CodeBlockImageCacheEntry(p_timeStamp,
  421. p_format,
  422. p_data,
  423. "",
  424. getScaleFactor(cb)));
  425. m_cache.insert(text, entry);
  426. cb.updateInplacePreview(m_editor, m_doc, entry->m_image);
  427. if (cb.inplacePreview()) {
  428. entry->m_imageName = cb.inplacePreview()->m_name;
  429. }
  430. updateInplacePreview();
  431. }
  432. void VLivePreviewHelper::clearObsoleteCache()
  433. {
  434. if (m_cache.size() - m_codeBlocks.size() <= CODE_BLOCK_IMAGE_CACHE_SIZE_DIFF) {
  435. return;
  436. }
  437. for (auto it = m_cache.begin(); it != m_cache.end();) {
  438. if (m_timeStamp - it.value()->m_ts > CODE_BLOCK_IMAGE_CACHE_TIME_DIFF) {
  439. it.value().clear();
  440. it = m_cache.erase(it);
  441. } else {
  442. ++it;
  443. }
  444. }
  445. }