hgmarkdownhighlighter.h 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. #ifndef HGMARKDOWNHIGHLIGHTER_H
  2. #define HGMARKDOWNHIGHLIGHTER_H
  3. #include <QTextCharFormat>
  4. #include <QSyntaxHighlighter>
  5. #include <QAtomicInt>
  6. #include <QMap>
  7. #include <QSet>
  8. #include <QString>
  9. #include "vtextblockdata.h"
  10. extern "C" {
  11. #include <pmh_parser.h>
  12. }
  13. QT_BEGIN_NAMESPACE
  14. class QTextDocument;
  15. QT_END_NAMESPACE
  16. struct HighlightingStyle
  17. {
  18. pmh_element_type type;
  19. QTextCharFormat format;
  20. };
  21. // One continuous region for a certain markdown highlight style
  22. // within a QTextBlock.
  23. // Pay attention to the change of HighlightingStyles[]
  24. struct HLUnit
  25. {
  26. // Highlight offset @start and @length with style HighlightingStyles[styleIndex]
  27. // within a QTextBlock
  28. unsigned long start;
  29. unsigned long length;
  30. unsigned int styleIndex;
  31. };
  32. struct HLUnitStyle
  33. {
  34. unsigned long start;
  35. unsigned long length;
  36. QString style;
  37. };
  38. // Fenced code block only.
  39. struct VCodeBlock
  40. {
  41. int m_startPos;
  42. int m_startBlock;
  43. int m_endBlock;
  44. QString m_lang;
  45. QString m_text;
  46. };
  47. // Highlight unit with global position and string style name.
  48. struct HLUnitPos
  49. {
  50. HLUnitPos() : m_position(-1), m_length(-1)
  51. {
  52. }
  53. HLUnitPos(int p_position, int p_length, const QString &p_style)
  54. : m_position(p_position), m_length(p_length), m_style(p_style)
  55. {
  56. }
  57. int m_position;
  58. int m_length;
  59. QString m_style;
  60. };
  61. // Denote the region of a certain Markdown element.
  62. struct VElementRegion
  63. {
  64. VElementRegion() : m_startPos(0), m_endPos(0) {}
  65. VElementRegion(int p_start, int p_end) : m_startPos(p_start), m_endPos(p_end) {}
  66. // The start position of the region in document.
  67. int m_startPos;
  68. // The end position of the region in document.
  69. int m_endPos;
  70. // Whether this region contains @p_pos.
  71. bool contains(int p_pos) const
  72. {
  73. return m_startPos <= p_pos && m_endPos >= p_pos;
  74. }
  75. bool operator==(const VElementRegion &p_other) const
  76. {
  77. return (m_startPos == p_other.m_startPos
  78. && m_endPos == p_other.m_endPos);
  79. }
  80. bool operator<(const VElementRegion &p_other) const
  81. {
  82. if (m_startPos < p_other.m_startPos) {
  83. return true;
  84. } else if (m_startPos == p_other.m_startPos) {
  85. // If a < b is true, then b < a must be false.
  86. return m_endPos < p_other.m_endPos;
  87. } else {
  88. return false;
  89. }
  90. }
  91. };
  92. class HGMarkdownHighlighter : public QSyntaxHighlighter
  93. {
  94. Q_OBJECT
  95. public:
  96. HGMarkdownHighlighter(const QVector<HighlightingStyle> &styles,
  97. const QHash<QString, QTextCharFormat> &codeBlockStyles,
  98. int waitInterval,
  99. QTextDocument *parent = 0);
  100. ~HGMarkdownHighlighter();
  101. // Request to update highlihgt (re-parse and re-highlight)
  102. void setCodeBlockHighlights(const QVector<HLUnitPos> &p_units);
  103. const QMap<int, bool> &getPotentialPreviewBlocks() const;
  104. const QVector<VElementRegion> &getHeaderRegions() const;
  105. const QSet<int> &getPossiblePreviewBlocks() const;
  106. void clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear);
  107. // Parse and only update the highlight results for rehighlight().
  108. void updateHighlightFast();
  109. QHash<QString, QTextCharFormat> &getCodeBlockStyles();
  110. QVector<HighlightingStyle> &getHighlightingStyles();
  111. signals:
  112. void highlightCompleted();
  113. // QVector is implicitly shared.
  114. void codeBlocksUpdated(const QVector<VCodeBlock> &p_codeBlocks);
  115. // Emitted when image regions have been fetched from a new parsing result.
  116. void imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions);
  117. // Emitted when header regions have been fetched from a new parsing result.
  118. void headersUpdated(const QVector<VElementRegion> &p_headerRegions);
  119. protected:
  120. void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
  121. public slots:
  122. // Parse and rehighlight immediately.
  123. void updateHighlight();
  124. private slots:
  125. void handleContentChange(int position, int charsRemoved, int charsAdded);
  126. // @p_fast: if true, just parse and update styles.
  127. void startParseAndHighlight(bool p_fast = false);
  128. private:
  129. struct HeaderBlockInfo
  130. {
  131. HeaderBlockInfo(int p_level = -1, int p_length = 0)
  132. : m_level(p_level), m_length(p_length)
  133. {
  134. }
  135. // Header level based on 0.
  136. int m_level;
  137. // Block length;
  138. int m_length;
  139. };
  140. QRegExp codeBlockStartExp;
  141. QRegExp codeBlockEndExp;
  142. QTextCharFormat codeBlockFormat;
  143. QTextCharFormat m_linkFormat;
  144. QTextCharFormat m_imageFormat;
  145. QTextCharFormat m_colorColumnFormat;
  146. QTextDocument *document;
  147. QVector<HighlightingStyle> highlightingStyles;
  148. QHash<QString, QTextCharFormat> m_codeBlockStyles;
  149. QVector<QVector<HLUnit> > m_blockHighlights;
  150. // Used for cache, [0, 6].
  151. QVector<QTextCharFormat> m_headerStyles;
  152. // Use another member to store the codeblocks highlights, because the highlight
  153. // sequence is blockHighlights, regular-expression-based highlihgts, and then
  154. // codeBlockHighlights.
  155. // Support fenced code block only.
  156. QVector<QVector<HLUnitStyle> > m_codeBlockHighlights;
  157. int m_numOfCodeBlockHighlightsToRecv;
  158. // All HTML comment regions.
  159. QVector<VElementRegion> m_commentRegions;
  160. // All image link regions.
  161. QVector<VElementRegion> m_imageRegions;
  162. // All header regions.
  163. // May contains illegal elements.
  164. // Sorted by start position.
  165. QVector<VElementRegion> m_headerRegions;
  166. // Indexed by block number.
  167. QHash<int, HeaderBlockInfo> m_headerBlocks;
  168. // Timer to signal highlightCompleted().
  169. QTimer *m_completeTimer;
  170. QAtomicInt parsing;
  171. // Whether highlight results for blocks are ready.
  172. bool m_blockHLResultReady;
  173. QTimer *timer;
  174. int waitInterval;
  175. // Block number of those blocks which possible contains previewed image.
  176. QSet<int> m_possiblePreviewBlocks;
  177. char *content;
  178. int capacity;
  179. pmh_element **result;
  180. static const int initCapacity;
  181. void resizeBuffer(int newCap);
  182. void highlightCodeBlock(const QString &text);
  183. // Highlight links using regular expression.
  184. // PEG Markdown Highlight treat URLs with spaces illegal. This function is
  185. // intended to complement this.
  186. void highlightLinkWithSpacesInURL(const QString &p_text);
  187. void parse(bool p_fast = false);
  188. void parseInternal();
  189. // Init highlight elements for all the blocks from parse results.
  190. void initBlockHighlightFromResult(int nrBlocks);
  191. // Init highlight elements for blocks from one parse result.
  192. void initBlockHighlihgtOne(unsigned long pos,
  193. unsigned long end,
  194. int styleIndex);
  195. // Return true if there are fenced code blocks and it will call rehighlight() later.
  196. // Return false if there is none.
  197. bool updateCodeBlocks();
  198. // Fetch all the HTML comment regions from parsing result.
  199. void initHtmlCommentRegionsFromResult();
  200. // Fetch all the image link regions from parsing result.
  201. void initImageRegionsFromResult();
  202. // Fetch all the header regions from parsing result.
  203. void initHeaderRegionsFromResult();
  204. // Whether @p_block is totally inside a HTML comment.
  205. bool isBlockInsideCommentRegion(const QTextBlock &p_block) const;
  206. // Highlights have been changed. Try to signal highlightCompleted().
  207. void highlightChanged();
  208. // Set the user data of currentBlock().
  209. void updateBlockUserData(int p_blockNum, const QString &p_text);
  210. // Highlight color column in code block.
  211. void highlightCodeBlockColorColumn(const QString &p_text);
  212. // Check if [p_pos, p_end) is a valid header.
  213. bool isValidHeader(unsigned long p_pos, unsigned long p_end);
  214. bool isValidHeader(const QString &p_text);
  215. VTextBlockData *currentBlockData() const;
  216. VTextBlockData *previousBlockData() const;
  217. // Highlight headers using regular expression first instead of waiting for
  218. // another parse.
  219. void highlightHeaderFast(int p_blockNumber, const QString &p_text);
  220. };
  221. inline const QVector<VElementRegion> &HGMarkdownHighlighter::getHeaderRegions() const
  222. {
  223. return m_headerRegions;
  224. }
  225. inline const QSet<int> &HGMarkdownHighlighter::getPossiblePreviewBlocks() const
  226. {
  227. return m_possiblePreviewBlocks;
  228. }
  229. inline void HGMarkdownHighlighter::clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear)
  230. {
  231. for (auto i : p_blocksToClear) {
  232. m_possiblePreviewBlocks.remove(i);
  233. }
  234. }
  235. inline VTextBlockData *HGMarkdownHighlighter::currentBlockData() const
  236. {
  237. return static_cast<VTextBlockData *>(currentBlockUserData());
  238. }
  239. inline VTextBlockData *HGMarkdownHighlighter::previousBlockData() const
  240. {
  241. QTextBlock block = currentBlock().previous();
  242. if (!block.isValid()) {
  243. return NULL;
  244. }
  245. return static_cast<VTextBlockData *>(block.userData());
  246. }
  247. inline QHash<QString, QTextCharFormat> &HGMarkdownHighlighter::getCodeBlockStyles()
  248. {
  249. return m_codeBlockStyles;
  250. }
  251. inline QVector<HighlightingStyle> &HGMarkdownHighlighter::getHighlightingStyles()
  252. {
  253. return highlightingStyles;
  254. }
  255. #endif