pegmarkdownhighlighter.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. #include "pegmarkdownhighlighter.h"
  2. #include <QTextDocument>
  3. #include <QTimer>
  4. #include <QScrollBar>
  5. #include "pegparser.h"
  6. #include "vconfigmanager.h"
  7. #include "utils/vutils.h"
  8. #include "utils/veditutils.h"
  9. #include "vmdeditor.h"
  10. extern VConfigManager *g_config;
  11. #define LARGE_BLOCK_NUMBER 1000
  12. PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc, VMdEditor *p_editor)
  13. : QSyntaxHighlighter(p_doc),
  14. m_doc(p_doc),
  15. m_editor(p_editor),
  16. m_timeStamp(0),
  17. m_codeBlockTimeStamp(0),
  18. m_parser(NULL),
  19. m_parserExts(pmh_EXT_NOTES | pmh_EXT_STRIKE | pmh_EXT_FRONTMATTER | pmh_EXT_MARK),
  20. m_parseInterval(50),
  21. m_notifyHighlightComplete(false)
  22. {
  23. }
  24. void PegMarkdownHighlighter::init(const QVector<HighlightingStyle> &p_styles,
  25. const QHash<QString, QTextCharFormat> &p_codeBlockStyles,
  26. bool p_mathjaxEnabled,
  27. int p_timerInterval)
  28. {
  29. m_styles = p_styles;
  30. m_codeBlockStyles = p_codeBlockStyles;
  31. if (p_mathjaxEnabled) {
  32. m_parserExts |= pmh_EXT_MATH;
  33. }
  34. m_parseInterval = p_timerInterval;
  35. m_codeBlockFormat.setForeground(QBrush(Qt::darkYellow));
  36. for (int index = 0; index < m_styles.size(); ++index) {
  37. switch (m_styles[index].type) {
  38. case pmh_FENCEDCODEBLOCK:
  39. m_codeBlockFormat = m_styles[index].format;
  40. break;
  41. default:
  42. break;
  43. }
  44. }
  45. m_colorColumnFormat = m_codeBlockFormat;
  46. m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg()));
  47. m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg()));
  48. m_result.reset(new PegHighlighterResult());
  49. m_fastResult.reset(new PegHighlighterFastResult());
  50. m_fastParseBlocks = QPair<int, int>(-1, -1);
  51. m_parser = new PegParser(this);
  52. connect(m_parser, &PegParser::parseResultReady,
  53. this, &PegMarkdownHighlighter::handleParseResult);
  54. m_timer = new QTimer(this);
  55. m_timer->setSingleShot(true);
  56. m_timer->setInterval(m_parseInterval);
  57. connect(m_timer, &QTimer::timeout,
  58. this, &PegMarkdownHighlighter::startParse);
  59. m_fastParseTimer = new QTimer(this);
  60. m_fastParseTimer->setSingleShot(true);
  61. m_fastParseTimer->setInterval(50);
  62. connect(m_fastParseTimer, &QTimer::timeout,
  63. this, [this]() {
  64. startFastParse(m_fastParseInfo.m_position,
  65. m_fastParseInfo.m_charsRemoved,
  66. m_fastParseInfo.m_charsAdded);
  67. });
  68. m_scrollRehighlightTimer = new QTimer(this);
  69. m_scrollRehighlightTimer->setSingleShot(true);
  70. m_scrollRehighlightTimer->setInterval(5);
  71. connect(m_scrollRehighlightTimer, &QTimer::timeout,
  72. this, [this]() {
  73. if (m_result->m_numOfBlocks > LARGE_BLOCK_NUMBER) {
  74. rehighlightSensitiveBlocks();
  75. }
  76. });
  77. m_rehighlightTimer = new QTimer(this);
  78. m_rehighlightTimer->setSingleShot(true);
  79. m_rehighlightTimer->setInterval(10);
  80. connect(m_rehighlightTimer, &QTimer::timeout,
  81. this, &PegMarkdownHighlighter::rehighlightBlocks);
  82. connect(m_doc, &QTextDocument::contentsChange,
  83. this, &PegMarkdownHighlighter::handleContentsChange);
  84. connect(m_editor->verticalScrollBar(), &QScrollBar::valueChanged,
  85. m_scrollRehighlightTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
  86. }
  87. // Just use parse results to highlight block.
  88. // Do not maintain block data and state here.
  89. void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
  90. {
  91. QSharedPointer<PegHighlighterResult> result(m_result);
  92. QTextBlock block = currentBlock();
  93. int blockNum = block.blockNumber();
  94. bool isCodeBlock = currentBlockState() == HighlightBlockState::CodeBlock;
  95. VTextBlockData *blockData = VTextBlockData::blockData(block);
  96. QVector<HLUnit> *cache = &blockData->getBlockHighlightCache();
  97. bool cacheValid = true;
  98. if (result->matched(m_timeStamp)) {
  99. if (preHighlightSingleFormatBlock(result->m_blocksHighlights,
  100. blockNum,
  101. p_text,
  102. isCodeBlock)) {
  103. cacheValid = false;
  104. } else if (blockData->isCacheValid() && blockData->getTimeStamp() == m_timeStamp) {
  105. // Use the cache to highlight.
  106. highlightBlockOne(*cache);
  107. } else {
  108. cache->clear();
  109. highlightBlockOne(result->m_blocksHighlights, blockNum, cache);
  110. }
  111. } else {
  112. // If fast result cover this block, we do not need to use the outdated one.
  113. if (isFastParseBlock(blockNum)) {
  114. if (!preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights,
  115. blockNum,
  116. p_text,
  117. isCodeBlock)) {
  118. highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum, NULL);
  119. }
  120. cacheValid = false;
  121. } else {
  122. if (preHighlightSingleFormatBlock(result->m_blocksHighlights,
  123. blockNum,
  124. p_text,
  125. isCodeBlock)) {
  126. cacheValid = false;
  127. } else if (blockData->isCacheValid() && result->matched(blockData->getTimeStamp())) {
  128. // Use the cache to highlight.
  129. highlightBlockOne(*cache);
  130. } else {
  131. cache->clear();
  132. highlightBlockOne(result->m_blocksHighlights, blockNum, cache);
  133. }
  134. }
  135. }
  136. blockData->setCacheValid(cacheValid);
  137. PegMarkdownHighlighter::updateBlockTimeStamp(block, result->m_timeStamp);
  138. if (isCodeBlock) {
  139. QVector<HLUnitStyle> *cbCache = &blockData->getCodeBlockHighlightCache();
  140. if (blockData->getCodeBlockTimeStamp() == result->m_codeBlockTimeStamp
  141. || !result->m_codeBlockHighlightReceived) {
  142. highlightCodeBlock(*cbCache, p_text);
  143. } else {
  144. cbCache->clear();
  145. highlightCodeBlock(result, blockNum, p_text, cbCache);
  146. PegMarkdownHighlighter::updateBlockCodeBlockTimeStamp(block, result->m_codeBlockTimeStamp);
  147. }
  148. highlightCodeBlockColorColumn(p_text);
  149. }
  150. }
  151. bool PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &p_highlights,
  152. int p_blockNum,
  153. const QString &p_text,
  154. bool p_forced)
  155. {
  156. int sz = p_text.size();
  157. if (sz == 0) {
  158. return false;
  159. }
  160. if (p_highlights.size() <= p_blockNum) {
  161. return false;
  162. }
  163. if (!p_forced && !m_singleFormatBlocks.contains(p_blockNum)) {
  164. return false;
  165. }
  166. const QVector<HLUnit> &units = p_highlights[p_blockNum];
  167. if (units.size() == 1) {
  168. const HLUnit &unit = units[0];
  169. if (unit.start == 0 && (int)unit.length < sz) {
  170. setFormat(0, sz, m_styles[unit.styleIndex].format);
  171. return true;
  172. }
  173. }
  174. return false;
  175. }
  176. bool PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
  177. int p_blockNum,
  178. QVector<HLUnit> *p_cache)
  179. {
  180. bool highlighted = false;
  181. if (p_highlights.size() > p_blockNum) {
  182. // units are sorted by start position and length.
  183. const QVector<HLUnit> &units = p_highlights[p_blockNum];
  184. if (!units.isEmpty()) {
  185. highlighted = true;
  186. if (p_cache) {
  187. p_cache->append(units);
  188. }
  189. highlightBlockOne(units);
  190. }
  191. }
  192. return highlighted;
  193. }
  194. void PegMarkdownHighlighter::highlightBlockOne(const QVector<HLUnit> &p_units)
  195. {
  196. for (int i = 0; i < p_units.size(); ++i) {
  197. const HLUnit &unit = p_units[i];
  198. if (i == 0) {
  199. // No need to merge format.
  200. setFormat(unit.start,
  201. unit.length,
  202. m_styles[unit.styleIndex].format);
  203. } else {
  204. QTextCharFormat newFormat = m_styles[unit.styleIndex].format;
  205. for (int j = i - 1; j >= 0; --j) {
  206. if (p_units[j].start + p_units[j].length <= unit.start) {
  207. // It won't affect current unit.
  208. continue;
  209. } else {
  210. // Merge the format.
  211. QTextCharFormat tmpFormat(newFormat);
  212. newFormat = m_styles[p_units[j].styleIndex].format;
  213. // tmpFormat takes precedence.
  214. newFormat.merge(tmpFormat);
  215. }
  216. }
  217. setFormat(unit.start, unit.length, newFormat);
  218. }
  219. }
  220. }
  221. // highlightBlock() will be called before this function.
  222. void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded)
  223. {
  224. Q_UNUSED(p_position);
  225. if (p_charsRemoved == 0 && p_charsAdded == 0) {
  226. return;
  227. }
  228. ++m_timeStamp;
  229. m_timer->stop();
  230. if (m_timeStamp > 2) {
  231. m_fastParseInfo.m_position = p_position;
  232. m_fastParseInfo.m_charsRemoved = p_charsRemoved;
  233. m_fastParseInfo.m_charsAdded = p_charsAdded;
  234. m_fastParseTimer->start();
  235. }
  236. // We still need a timer to start a complete parse.
  237. m_timer->start(m_timeStamp == 2 ? 0 : m_parseInterval);
  238. }
  239. void PegMarkdownHighlighter::startParse()
  240. {
  241. QSharedPointer<PegParseConfig> config(new PegParseConfig());
  242. config->m_timeStamp = m_timeStamp;
  243. config->m_data = m_doc->toPlainText().toUtf8();
  244. config->m_numOfBlocks = m_doc->blockCount();
  245. config->m_extensions = m_parserExts;
  246. m_parser->parseAsync(config);
  247. }
  248. void PegMarkdownHighlighter::startFastParse(int p_position, int p_charsRemoved, int p_charsAdded)
  249. {
  250. // Get affected block range.
  251. int firstBlockNum, lastBlockNum;
  252. getFastParseBlockRange(p_position, p_charsRemoved, p_charsAdded, firstBlockNum, lastBlockNum);
  253. if (firstBlockNum == -1) {
  254. // We could not let m_fastResult NULL here.
  255. clearFastParseResult();
  256. return;
  257. }
  258. QString text;
  259. QTextBlock block = m_doc->findBlockByNumber(firstBlockNum);
  260. int offset = block.position();
  261. while (block.isValid()) {
  262. int blockNum = block.blockNumber();
  263. if (blockNum > lastBlockNum) {
  264. break;
  265. } else if (blockNum == firstBlockNum) {
  266. text = block.text();
  267. } else {
  268. text = text + "\n" + block.text();
  269. }
  270. block = block.next();
  271. }
  272. m_fastParseBlocks.first = firstBlockNum;
  273. m_fastParseBlocks.second = lastBlockNum;
  274. QSharedPointer<PegParseConfig> config(new PegParseConfig());
  275. config->m_timeStamp = m_timeStamp;
  276. config->m_data = text.toUtf8();
  277. config->m_numOfBlocks = m_doc->blockCount();
  278. config->m_offset = offset;
  279. config->m_extensions = m_parserExts;
  280. config->m_fast = true;
  281. QSharedPointer<PegParseResult> parseRes = m_parser->parse(config);
  282. processFastParseResult(parseRes);
  283. }
  284. void PegMarkdownHighlighter::processFastParseResult(const QSharedPointer<PegParseResult> &p_result)
  285. {
  286. m_fastResult.reset(new PegHighlighterFastResult(this, p_result));
  287. // Add additional single format blocks.
  288. updateSingleFormatBlocks(m_fastResult->m_blocksHighlights);
  289. if (!m_fastResult->matched(m_timeStamp) || m_result->matched(m_timeStamp)) {
  290. return;
  291. }
  292. for (int i = m_fastParseBlocks.first; i <= m_fastParseBlocks.second; ++i) {
  293. QTextBlock block = m_doc->findBlockByNumber(i);
  294. rehighlightBlock(block);
  295. }
  296. }
  297. static bool compHLUnitStyle(const HLUnitStyle &a, const HLUnitStyle &b)
  298. {
  299. if (a.start < b.start) {
  300. return true;
  301. } else if (a.start == b.start) {
  302. return a.length > b.length;
  303. } else {
  304. return false;
  305. }
  306. }
  307. void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp,
  308. const QVector<HLUnitPos> &p_units)
  309. {
  310. QSharedPointer<PegHighlighterResult> result(m_result);
  311. if (!result->matched(p_timeStamp)
  312. || result->m_numOfCodeBlockHighlightsToRecv <= 0) {
  313. return;
  314. }
  315. if (p_units.isEmpty()) {
  316. goto exit;
  317. }
  318. {
  319. QVector<QVector<HLUnitStyle>> highlights(result->m_codeBlocksHighlights.size());
  320. for (auto const &unit : p_units) {
  321. int pos = unit.m_position;
  322. int end = unit.m_position + unit.m_length;
  323. QTextBlock block = m_doc->findBlock(pos);
  324. int startBlockNum = block.blockNumber();
  325. int endBlockNum = m_doc->findBlock(end).blockNumber();
  326. // Text has been changed. Abandon the obsolete parsed result.
  327. if (startBlockNum == -1 || endBlockNum >= highlights.size()) {
  328. goto exit;
  329. }
  330. while (block.isValid()) {
  331. int blockNumber = block.blockNumber();
  332. if (blockNumber > endBlockNum) {
  333. break;
  334. }
  335. int blockStartPos = block.position();
  336. HLUnitStyle hl;
  337. hl.style = unit.m_style;
  338. if (blockNumber == startBlockNum) {
  339. hl.start = pos - blockStartPos;
  340. hl.length = (startBlockNum == endBlockNum) ?
  341. (end - pos) : (block.length() - hl.start);
  342. } else if (blockNumber == endBlockNum) {
  343. hl.start = 0;
  344. hl.length = end - blockStartPos;
  345. } else {
  346. hl.start = 0;
  347. hl.length = block.length();
  348. }
  349. highlights[blockNumber].append(hl);
  350. block = block.next();
  351. }
  352. }
  353. // Need to highlight in order.
  354. for (int i = 0; i < highlights.size(); ++i) {
  355. QVector<HLUnitStyle> &units = highlights[i];
  356. if (!units.isEmpty()) {
  357. if (units.size() > 1) {
  358. std::sort(units.begin(), units.end(), compHLUnitStyle);
  359. }
  360. result->m_codeBlocksHighlights[i].append(units);
  361. }
  362. }
  363. }
  364. exit:
  365. if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) {
  366. result->m_codeBlockTimeStamp = nextCodeBlockTimeStamp();
  367. result->m_codeBlockHighlightReceived = true;
  368. rehighlightBlocksLater();
  369. }
  370. }
  371. void PegMarkdownHighlighter::updateHighlight()
  372. {
  373. m_timer->stop();
  374. if (m_result->matched(m_timeStamp)) {
  375. // No need to parse again. Already the latest.
  376. updateCodeBlocks(m_result);
  377. rehighlightBlocksLater();
  378. completeHighlight(m_result);
  379. } else {
  380. startParse();
  381. }
  382. }
  383. void PegMarkdownHighlighter::handleParseResult(const QSharedPointer<PegParseResult> &p_result)
  384. {
  385. if (!m_result.isNull() && m_result->m_timeStamp > p_result->m_timeStamp) {
  386. return;
  387. }
  388. clearFastParseResult();
  389. m_result.reset(new PegHighlighterResult(this, p_result));
  390. m_result->m_codeBlockTimeStamp = nextCodeBlockTimeStamp();
  391. m_singleFormatBlocks.clear();
  392. updateSingleFormatBlocks(m_result->m_blocksHighlights);
  393. bool matched = m_result->matched(m_timeStamp);
  394. if (matched) {
  395. clearAllBlocksUserDataAndState(m_result);
  396. updateAllBlocksUserState(m_result);
  397. updateCodeBlocks(m_result);
  398. }
  399. if (m_result->m_timeStamp == 2) {
  400. m_notifyHighlightComplete = true;
  401. rehighlightBlocks();
  402. } else {
  403. rehighlightBlocksLater();
  404. }
  405. if (matched) {
  406. completeHighlight(m_result);
  407. }
  408. }
  409. void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector<QVector<HLUnit>> &p_highlights)
  410. {
  411. for (int i = 0; i < p_highlights.size(); ++i) {
  412. const QVector<HLUnit> &units = p_highlights[i];
  413. if (units.size() == 1) {
  414. const HLUnit &unit = units[0];
  415. if (unit.start == 0 && unit.length > 0) {
  416. QTextBlock block = m_doc->findBlockByNumber(i);
  417. if (block.length() - 1 <= (int)unit.length) {
  418. m_singleFormatBlocks.insert(i);
  419. }
  420. }
  421. }
  422. }
  423. }
  424. void PegMarkdownHighlighter::updateCodeBlocks(const QSharedPointer<PegHighlighterResult> &p_result)
  425. {
  426. // Only need to receive code block highlights when it is empty.
  427. if (g_config->getEnableCodeBlockHighlight()) {
  428. int cbSz = p_result->m_codeBlocks.size();
  429. if (cbSz > 0) {
  430. if (PegMarkdownHighlighter::isEmptyCodeBlockHighlights(p_result->m_codeBlocksHighlights)) {
  431. p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks);
  432. p_result->m_numOfCodeBlockHighlightsToRecv = cbSz;
  433. }
  434. } else {
  435. p_result->m_codeBlockHighlightReceived = true;
  436. }
  437. } else {
  438. p_result->m_codeBlockHighlightReceived = true;
  439. }
  440. emit codeBlocksUpdated(p_result->m_timeStamp, p_result->m_codeBlocks);
  441. }
  442. void PegMarkdownHighlighter::clearAllBlocksUserDataAndState(const QSharedPointer<PegHighlighterResult> &p_result)
  443. {
  444. QTextBlock block = m_doc->firstBlock();
  445. while (block.isValid()) {
  446. clearBlockUserData(p_result, block);
  447. block.setUserState(HighlightBlockState::Normal);
  448. block = block.next();
  449. }
  450. }
  451. void PegMarkdownHighlighter::clearBlockUserData(const QSharedPointer<PegHighlighterResult> &p_result,
  452. QTextBlock &p_block)
  453. {
  454. Q_UNUSED(p_result);
  455. int blockNum = p_block.blockNumber();
  456. VTextBlockData *data = VTextBlockData::blockData(p_block);
  457. if (!data) {
  458. return;
  459. }
  460. data->setCodeBlockIndentation(-1);
  461. if (data->getPreviews().isEmpty()) {
  462. m_possiblePreviewBlocks.remove(blockNum);
  463. } else {
  464. m_possiblePreviewBlocks.insert(blockNum);
  465. }
  466. }
  467. void PegMarkdownHighlighter::updateAllBlocksUserState(const QSharedPointer<PegHighlighterResult> &p_result)
  468. {
  469. // Code blocks.
  470. bool hlColumn = g_config->getColorColumn() > 0;
  471. const QHash<int, HighlightBlockState> &cbStates = p_result->m_codeBlocksState;
  472. for (auto it = cbStates.begin(); it != cbStates.end(); ++it) {
  473. QTextBlock block = m_doc->findBlockByNumber(it.key());
  474. if (!block.isValid()) {
  475. continue;
  476. }
  477. // Set code block indentation.
  478. if (hlColumn) {
  479. VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
  480. Q_ASSERT(blockData);
  481. switch (it.value()) {
  482. case HighlightBlockState::CodeBlockStart:
  483. {
  484. int startLeadingSpaces = 0;
  485. QRegExp reg(VUtils::c_fencedCodeBlockStartRegExp);
  486. int idx = reg.indexIn(block.text());
  487. if (idx >= 0) {
  488. startLeadingSpaces = reg.capturedTexts()[1].size();
  489. }
  490. blockData->setCodeBlockIndentation(startLeadingSpaces);
  491. break;
  492. }
  493. case HighlightBlockState::CodeBlock:
  494. V_FALLTHROUGH;
  495. case HighlightBlockState::CodeBlockEnd:
  496. {
  497. int startLeadingSpaces = 0;
  498. VTextBlockData *preBlockData = previousBlockData(block);
  499. if (preBlockData) {
  500. startLeadingSpaces = preBlockData->getCodeBlockIndentation();
  501. }
  502. blockData->setCodeBlockIndentation(startLeadingSpaces);
  503. break;
  504. }
  505. default:
  506. Q_ASSERT(false);
  507. break;
  508. }
  509. }
  510. block.setUserState(it.value());
  511. }
  512. // HRule blocks.
  513. foreach (int blk, p_result->m_hruleBlocks) {
  514. QTextBlock block = m_doc->findBlockByNumber(blk);
  515. if (block.isValid()) {
  516. block.setUserState(HighlightBlockState::HRule);
  517. }
  518. }
  519. }
  520. void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
  521. int p_blockNum,
  522. const QString &p_text,
  523. QVector<HLUnitStyle> *p_cache)
  524. {
  525. // Brush the indentation spaces.
  526. if (currentBlockState() == HighlightBlockState::CodeBlock) {
  527. int spaces = VEditUtils::fetchIndentation(p_text);
  528. if (spaces > 0) {
  529. setFormat(0, spaces, m_codeBlockFormat);
  530. }
  531. }
  532. if (p_result->m_codeBlocksHighlights.size() > p_blockNum) {
  533. const QVector<HLUnitStyle> &units = p_result->m_codeBlocksHighlights[p_blockNum];
  534. if (!units.isEmpty()) {
  535. if (p_cache) {
  536. p_cache->append(units);
  537. }
  538. highlightCodeBlockOne(units);
  539. }
  540. }
  541. }
  542. void PegMarkdownHighlighter::highlightCodeBlock(const QVector<HLUnitStyle> &p_units,
  543. const QString &p_text)
  544. {
  545. // Brush the indentation spaces.
  546. if (currentBlockState() == HighlightBlockState::CodeBlock) {
  547. int spaces = VEditUtils::fetchIndentation(p_text);
  548. if (spaces > 0) {
  549. setFormat(0, spaces, m_codeBlockFormat);
  550. }
  551. }
  552. if (!p_units.isEmpty()) {
  553. highlightCodeBlockOne(p_units);
  554. }
  555. }
  556. void PegMarkdownHighlighter::highlightCodeBlockOne(const QVector<HLUnitStyle> &p_units)
  557. {
  558. QVector<QTextCharFormat *> formats(p_units.size(), NULL);
  559. for (int i = 0; i < p_units.size(); ++i) {
  560. const HLUnitStyle &unit = p_units[i];
  561. auto it = m_codeBlockStyles.find(unit.style);
  562. if (it == m_codeBlockStyles.end()) {
  563. continue;
  564. }
  565. formats[i] = &(*it);
  566. QTextCharFormat newFormat = m_codeBlockFormat;
  567. newFormat.merge(*it);
  568. for (int j = i - 1; j >= 0; --j) {
  569. if (p_units[j].start + p_units[j].length <= unit.start) {
  570. // It won't affect current unit.
  571. continue;
  572. } else {
  573. // Merge the format.
  574. if (formats[j]) {
  575. QTextCharFormat tmpFormat(newFormat);
  576. newFormat = *(formats[j]);
  577. // tmpFormat takes precedence.
  578. newFormat.merge(tmpFormat);
  579. }
  580. }
  581. }
  582. setFormat(unit.start, unit.length, newFormat);
  583. }
  584. }
  585. void PegMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text)
  586. {
  587. int cc = g_config->getColorColumn();
  588. if (cc <= 0) {
  589. return;
  590. }
  591. VTextBlockData *blockData = currentBlockData();
  592. Q_ASSERT(blockData);
  593. int indent = blockData->getCodeBlockIndentation();
  594. if (indent == -1) {
  595. return;
  596. }
  597. cc += indent;
  598. if (p_text.size() < cc) {
  599. return;
  600. }
  601. setFormat(cc - 1, 1, m_colorColumnFormat);
  602. }
  603. void PegMarkdownHighlighter::completeHighlight(QSharedPointer<PegHighlighterResult> p_result)
  604. {
  605. m_notifyHighlightComplete = true;
  606. if (isMathJaxEnabled()) {
  607. emit mathjaxBlocksUpdated(p_result->m_mathjaxBlocks);
  608. }
  609. emit imageLinksUpdated(p_result->m_imageRegions);
  610. emit headersUpdated(p_result->m_headerRegions);
  611. }
  612. void PegMarkdownHighlighter::getFastParseBlockRange(int p_position,
  613. int p_charsRemoved,
  614. int p_charsAdded,
  615. int &p_firstBlock,
  616. int &p_lastBlock) const
  617. {
  618. const int maxNumOfBlocks = 50;
  619. int charsChanged = p_charsRemoved + p_charsAdded;
  620. QTextBlock firstBlock = m_doc->findBlock(p_position);
  621. // May be an invalid block.
  622. QTextBlock lastBlock = m_doc->findBlock(qMax(0, p_position + charsChanged));
  623. if (!lastBlock.isValid()) {
  624. lastBlock = m_doc->lastBlock();
  625. }
  626. int num = lastBlock.blockNumber() - firstBlock.blockNumber() + 1;
  627. if (num > maxNumOfBlocks) {
  628. p_firstBlock = p_lastBlock = -1;
  629. return;
  630. }
  631. // Look up.
  632. while (firstBlock.isValid() && num <= maxNumOfBlocks) {
  633. QTextBlock preBlock = firstBlock.previous();
  634. if (!preBlock.isValid()) {
  635. break;
  636. }
  637. // Check code block.
  638. int state = firstBlock.userState();
  639. if (state == HighlightBlockState::CodeBlock
  640. || state == HighlightBlockState::CodeBlockEnd) {
  641. goto goup;
  642. }
  643. // Empty block.
  644. if (VEditUtils::isEmptyBlock(firstBlock)) {
  645. goto goup;
  646. }
  647. if (VEditUtils::fetchIndentation(firstBlock) < 4) {
  648. // If previous block is empty, then we could stop now.
  649. if (VEditUtils::isEmptyBlock(preBlock)) {
  650. int preState = preBlock.userState();
  651. if (preState != HighlightBlockState::CodeBlockStart
  652. && preState != HighlightBlockState::CodeBlock) {
  653. break;
  654. }
  655. }
  656. }
  657. goup:
  658. firstBlock = preBlock;
  659. ++num;
  660. }
  661. // Look down.
  662. bool inCodeBlock = false;
  663. while (lastBlock.isValid() && num <= maxNumOfBlocks) {
  664. QTextBlock nextBlock = lastBlock.next();
  665. if (!nextBlock.isValid()) {
  666. break;
  667. }
  668. // Check code block.
  669. switch (lastBlock.userState()) {
  670. case HighlightBlockState::CodeBlockStart:
  671. V_FALLTHROUGH;
  672. case HighlightBlockState::CodeBlock:
  673. inCodeBlock = true;
  674. goto godown;
  675. case HighlightBlockState::CodeBlockEnd:
  676. inCodeBlock = false;
  677. break;
  678. default:
  679. break;
  680. }
  681. // Empty block.
  682. if (VEditUtils::isEmptyBlock(nextBlock) && !inCodeBlock) {
  683. int nstate = nextBlock.userState();
  684. if (nstate != HighlightBlockState::CodeBlockStart
  685. && nstate != HighlightBlockState::CodeBlock
  686. && nstate != HighlightBlockState::CodeBlockEnd) {
  687. break;
  688. }
  689. }
  690. godown:
  691. lastBlock = nextBlock;
  692. ++num;
  693. }
  694. p_firstBlock = firstBlock.blockNumber();
  695. p_lastBlock = lastBlock.blockNumber();
  696. if (p_lastBlock < p_firstBlock) {
  697. p_lastBlock = p_firstBlock;
  698. } else if (p_lastBlock - p_firstBlock + 1 > maxNumOfBlocks) {
  699. p_firstBlock = p_lastBlock = -1;
  700. }
  701. }
  702. void PegMarkdownHighlighter::rehighlightSensitiveBlocks()
  703. {
  704. QTextBlock cb = m_editor->textCursorW().block();
  705. int first, last;
  706. m_editor->visibleBlockRange(first, last);
  707. bool cursorVisible = cb.blockNumber() >= first && cb.blockNumber() <= last;
  708. // Include extra blocks.
  709. const int nrUpExtra = 5;
  710. const int nrDownExtra = 20;
  711. first = qMax(0, first - nrUpExtra);
  712. last = qMin(m_doc->blockCount() - 1, last + nrDownExtra);
  713. if (rehighlightBlockRange(first, last)) {
  714. if (cursorVisible) {
  715. m_editor->ensureCursorVisibleW();
  716. }
  717. }
  718. }
  719. void PegMarkdownHighlighter::rehighlightBlocks()
  720. {
  721. if (m_result->m_numOfBlocks <= LARGE_BLOCK_NUMBER) {
  722. rehighlightBlockRange(0, m_result->m_numOfBlocks - 1);
  723. } else {
  724. rehighlightSensitiveBlocks();
  725. }
  726. if (m_notifyHighlightComplete) {
  727. m_notifyHighlightComplete = false;
  728. emit highlightCompleted();
  729. }
  730. }
  731. bool PegMarkdownHighlighter::rehighlightBlockRange(int p_first, int p_last)
  732. {
  733. bool highlighted = false;
  734. const QHash<int, HighlightBlockState> &cbStates = m_result->m_codeBlocksState;
  735. const QVector<QVector<HLUnit>> &hls = m_result->m_blocksHighlights;
  736. const QVector<QVector<HLUnitStyle>> &cbHls = m_result->m_codeBlocksHighlights;
  737. int nr = 0;
  738. QTextBlock block = m_doc->findBlockByNumber(p_first);
  739. while (block.isValid()) {
  740. int blockNum = block.blockNumber();
  741. if (blockNum > p_last) {
  742. break;
  743. }
  744. bool needHL = false;
  745. bool updateTS = false;
  746. VTextBlockData *data = VTextBlockData::blockData(block);
  747. if (PegMarkdownHighlighter::blockTimeStamp(block) != m_result->m_timeStamp) {
  748. needHL = true;
  749. // Try to find cache.
  750. if (blockNum < hls.size()) {
  751. if (data->isBlockHighlightCacheMatched(hls[blockNum])) {
  752. needHL = false;
  753. updateTS = true;
  754. }
  755. }
  756. }
  757. if (!needHL) {
  758. // FIXME: what about a previous code block turn into a non-code block? For now,
  759. // they can be distinguished by block highlights.
  760. auto it = cbStates.find(blockNum);
  761. if (it != cbStates.end() && it.value() == HighlightBlockState::CodeBlock) {
  762. if (PegMarkdownHighlighter::blockCodeBlockTimeStamp(block) != m_result->m_codeBlockTimeStamp
  763. && m_result->m_codeBlockHighlightReceived) {
  764. needHL = true;
  765. // Try to find cache.
  766. if (blockNum < cbHls.size()) {
  767. if (data->isCodeBlockHighlightCacheMatched(cbHls[blockNum])) {
  768. needHL = false;
  769. updateTS = true;
  770. }
  771. }
  772. }
  773. }
  774. }
  775. if (!needHL && !data->getPreviews().isEmpty()) {
  776. needHL = true;
  777. }
  778. if (needHL) {
  779. highlighted = true;
  780. rehighlightBlock(block);
  781. ++nr;
  782. } else if (updateTS) {
  783. data->setCacheValid(true);
  784. data->setTimeStamp(m_result->m_timeStamp);
  785. data->setCodeBlockTimeStamp(m_result->m_codeBlockTimeStamp);
  786. }
  787. block = block.next();
  788. }
  789. qDebug() << "rehighlightBlockRange" << p_first << p_last << nr;
  790. return highlighted;
  791. }
  792. void PegMarkdownHighlighter::clearFastParseResult()
  793. {
  794. m_fastParseBlocks.first = -1;
  795. m_fastParseBlocks.second = -1;
  796. m_fastResult->clear();
  797. }
  798. void PegMarkdownHighlighter::rehighlightBlocksLater()
  799. {
  800. m_rehighlightTimer->start();
  801. }