361 lines
11 KiB
C++
361 lines
11 KiB
C++
/*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2014-2025 Patrizio Bekerle -- <patrizio@bekerle.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
* QPlainTextEdit Markdown highlighter
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <QRegularExpression>
|
|
#include <QSyntaxHighlighter>
|
|
#include <QTextCharFormat>
|
|
|
|
#ifdef QT_QUICK_LIB
|
|
#include <QQuickTextDocument>
|
|
#endif
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
class QTextDocument;
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
class MarkdownHighlighter : public QSyntaxHighlighter {
|
|
Q_OBJECT
|
|
|
|
#ifdef QT_QUICK_LIB
|
|
Q_PROPERTY(QQuickTextDocument *textDocument READ textDocument WRITE
|
|
setTextDocument NOTIFY textDocumentChanged)
|
|
|
|
QQuickTextDocument *m_quickDocument = nullptr;
|
|
|
|
signals:
|
|
void textDocumentChanged();
|
|
|
|
public:
|
|
inline QQuickTextDocument *textDocument() const { return m_quickDocument; };
|
|
void setTextDocument(QQuickTextDocument *textDocument) {
|
|
if (!textDocument) return;
|
|
m_quickDocument = textDocument;
|
|
setDocument(m_quickDocument->textDocument());
|
|
Q_EMIT textDocumentChanged();
|
|
};
|
|
#endif
|
|
|
|
public:
|
|
enum HighlightingOption {
|
|
None = 0,
|
|
FullyHighlightedBlockQuote = 0x01,
|
|
Underline = 0x02
|
|
};
|
|
Q_DECLARE_FLAGS(HighlightingOptions, HighlightingOption)
|
|
|
|
MarkdownHighlighter(
|
|
QTextDocument *parent = nullptr,
|
|
HighlightingOptions highlightingOptions = HighlightingOption::None);
|
|
|
|
static inline QColor codeBlockBackgroundColor() {
|
|
const QBrush brush = _formats[CodeBlock].background();
|
|
|
|
if (!brush.isOpaque()) {
|
|
return QColor(Qt::transparent);
|
|
}
|
|
|
|
return brush.color();
|
|
}
|
|
|
|
static constexpr inline bool isOctal(const char c) {
|
|
return (c >= '0' && c <= '7');
|
|
}
|
|
static constexpr inline bool isHex(const char c) {
|
|
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
|
|
(c >= 'A' && c <= 'F');
|
|
}
|
|
static constexpr inline bool isCodeBlock(const int state) {
|
|
return state == MarkdownHighlighter::CodeBlock ||
|
|
state == MarkdownHighlighter::CodeBlockTilde ||
|
|
state == MarkdownHighlighter::CodeBlockComment ||
|
|
state == MarkdownHighlighter::CodeBlockTildeComment ||
|
|
state >= MarkdownHighlighter::CodeCpp;
|
|
}
|
|
static constexpr inline bool isCodeBlockEnd(const int state) {
|
|
return state == MarkdownHighlighter::CodeBlockEnd ||
|
|
state == MarkdownHighlighter::CodeBlockTildeEnd;
|
|
}
|
|
static constexpr inline bool isHeading(const int state) {
|
|
return state >= H1 && state <= H6;
|
|
}
|
|
|
|
enum class RangeType { CodeSpan, Emphasis, Link };
|
|
|
|
QPair<int, int> findPositionInRanges(MarkdownHighlighter::RangeType type,
|
|
int blockNum, int pos) const;
|
|
bool isPosInACodeSpan(int blockNumber, int position) const;
|
|
bool isPosInALink(int blockNumber, int position) const;
|
|
QPair<int, int> getSpanRange(RangeType rangeType, int blockNumber,
|
|
int position) const;
|
|
|
|
// we used some predefined numbers here to be compatible with
|
|
// the peg-Markdown parser
|
|
enum HighlighterState {
|
|
NoState = -1,
|
|
Link = 0,
|
|
Image = 3,
|
|
CodeBlock,
|
|
CodeBlockComment,
|
|
Italic = 7,
|
|
Bold,
|
|
List,
|
|
Comment = 11,
|
|
H1,
|
|
H2,
|
|
H3,
|
|
H4,
|
|
H5,
|
|
H6,
|
|
BlockQuote,
|
|
HorizontalRuler = 21,
|
|
Table,
|
|
InlineCodeBlock,
|
|
MaskedSyntax,
|
|
CurrentLineBackgroundColor,
|
|
BrokenLink,
|
|
FrontmatterBlock,
|
|
TrailingSpace,
|
|
CheckBoxUnChecked,
|
|
CheckBoxChecked,
|
|
StUnderline,
|
|
|
|
// code highlighting
|
|
CodeKeyWord = 1000,
|
|
CodeString = 1001,
|
|
CodeComment = 1002,
|
|
CodeType = 1003,
|
|
CodeOther = 1004,
|
|
CodeNumLiteral = 1005,
|
|
CodeBuiltIn = 1006,
|
|
|
|
// internal
|
|
CodeBlockIndented = 96,
|
|
CodeBlockTildeEnd = 97,
|
|
CodeBlockTilde = 98,
|
|
CodeBlockTildeComment,
|
|
CodeBlockEnd = 100,
|
|
HeadlineEnd,
|
|
FrontmatterBlockEnd,
|
|
|
|
// languages
|
|
/*********
|
|
* When adding a language make sure that its value is a multiple of 2
|
|
* This is because we use the next number as comment for that language
|
|
* In case the language doesn't support multiline comments in the
|
|
* traditional C++ sense, leave the next value empty. Otherwise mark the
|
|
* next value as comment for that language. e.g CodeCpp = 200
|
|
* CodeCppComment = 201
|
|
*/
|
|
CodeCpp = 200,
|
|
CodeCppComment = 201,
|
|
CodeJs = 202,
|
|
CodeJsComment = 203,
|
|
CodeC = 204,
|
|
CodeCComment = 205,
|
|
CodeBash = 206,
|
|
CodePHP = 208,
|
|
CodePHPComment = 209,
|
|
CodeQML = 210,
|
|
CodeQMLComment = 211,
|
|
CodePython = 212,
|
|
CodeRust = 214,
|
|
CodeRustComment = 215,
|
|
CodeJava = 216,
|
|
CodeJavaComment = 217,
|
|
CodeCSharp = 218,
|
|
CodeCSharpComment = 219,
|
|
CodeGo = 220,
|
|
CodeGoComment = 221,
|
|
CodeV = 222,
|
|
CodeVComment = 223,
|
|
CodeSQL = 224,
|
|
CodeSQLComment = 225,
|
|
CodeJSON = 226,
|
|
CodeXML = 228,
|
|
CodeCSS = 230,
|
|
CodeCSSComment = 231,
|
|
CodeTypeScript = 232,
|
|
CodeTypeScriptComment = 233,
|
|
CodeYAML = 234,
|
|
CodeINI = 236,
|
|
CodeTaggerScript = 238,
|
|
CodeVex = 240,
|
|
CodeVexComment = 241,
|
|
CodeCMake = 242,
|
|
CodeMake = 244,
|
|
CodeNix = 246,
|
|
CodeForth = 248,
|
|
CodeForthComment = 249,
|
|
CodeSystemVerilog = 250,
|
|
CodeSystemVerilogComment = 251,
|
|
CodeGDScript = 252,
|
|
CodeTOML = 254,
|
|
CodeTOMLString = 255
|
|
};
|
|
Q_ENUM(HighlighterState)
|
|
|
|
static void setTextFormats(
|
|
QHash<HighlighterState, QTextCharFormat> formats);
|
|
static void setTextFormat(HighlighterState state, QTextCharFormat format);
|
|
void clearDirtyBlocks();
|
|
void setHighlightingOptions(const HighlightingOptions options);
|
|
void initHighlightingRules();
|
|
|
|
Q_SIGNALS:
|
|
void highlightingFinished();
|
|
|
|
protected Q_SLOTS:
|
|
void timerTick();
|
|
|
|
protected:
|
|
struct HighlightingRule {
|
|
explicit HighlightingRule(const HighlighterState state_)
|
|
: state(state_) {}
|
|
HighlightingRule() = default;
|
|
|
|
QRegularExpression pattern;
|
|
QString shouldContain;
|
|
HighlighterState state = NoState;
|
|
uint8_t capturingGroup = 0;
|
|
uint8_t maskedGroup = 0;
|
|
};
|
|
struct InlineRange {
|
|
int begin;
|
|
int end;
|
|
RangeType type;
|
|
InlineRange() = default;
|
|
InlineRange(int begin_, int end_, RangeType type_)
|
|
: begin{begin_}, end{end_}, type{type_} {}
|
|
};
|
|
|
|
void highlightBlock(const QString &text) override;
|
|
|
|
static void initTextFormats(int defaultFontSize = 12);
|
|
|
|
static void initCodeLangs();
|
|
|
|
void highlightMarkdown(const QString &text);
|
|
|
|
void formatAndMaskRemaining(int formatBegin, int formatLength,
|
|
int beginningText, int endText,
|
|
const QTextCharFormat &format);
|
|
|
|
/******************************
|
|
* BLOCK LEVEL FUNCTIONS
|
|
******************************/
|
|
|
|
void highlightHeadline(const QString &text);
|
|
|
|
void highlightSubHeadline(const QString &text, HighlighterState state);
|
|
|
|
void highlightAdditionalRules(const QVector<HighlightingRule> &rules,
|
|
const QString &text);
|
|
|
|
void highlightFrontmatterBlock(const QString &text);
|
|
|
|
void highlightCommentBlock(const QString &text);
|
|
|
|
void highlightThematicBreak(const QString &text);
|
|
|
|
void highlightLists(const QString &text);
|
|
|
|
void highlightCheckbox(const QString &text, int curPos);
|
|
|
|
/******************************
|
|
* INLINE FUNCTIONS
|
|
******************************/
|
|
|
|
void highlightInlineRules(const QString &text);
|
|
|
|
int highlightInlineSpans(const QString &text, int currentPos,
|
|
const QChar c);
|
|
|
|
void highlightEmAndStrong(const QString &text, const int pos);
|
|
|
|
Q_REQUIRED_RESULT int highlightInlineComment(const QString &text, int pos);
|
|
|
|
int highlightLinkOrImage(const QString &text, int startIndex);
|
|
|
|
void setHeadingStyles(MarkdownHighlighter::HighlighterState rule,
|
|
const QRegularExpressionMatch &match,
|
|
const int capturedGroup);
|
|
|
|
/******************************
|
|
* CODE HIGHLIGHTING FUNCTIONS
|
|
******************************/
|
|
|
|
void highlightIndentedCodeBlock(const QString &text);
|
|
|
|
void highlightCodeFence(const QString &text);
|
|
|
|
void highlightCodeBlock(const QString &text,
|
|
const QString &opener = QStringLiteral("```"));
|
|
|
|
void highlightSyntax(const QString &text);
|
|
|
|
Q_REQUIRED_RESULT int highlightNumericLiterals(const QString &text, int i);
|
|
|
|
Q_REQUIRED_RESULT int highlightStringLiterals(QChar strType,
|
|
const QString &text, int i);
|
|
|
|
void ymlHighlighter(const QString &text);
|
|
|
|
void iniHighlighter(const QString &text);
|
|
|
|
void cssHighlighter(const QString &text);
|
|
|
|
void xmlHighlighter(const QString &text);
|
|
|
|
void makeHighlighter(const QString &text);
|
|
|
|
void forthHighlighter(const QString &text);
|
|
void taggerScriptHighlighter(const QString &text);
|
|
|
|
void gdscriptHighlighter(const QString &text);
|
|
void sqlHighlighter(const QString &text);
|
|
void tomlHighlighter(const QString &text);
|
|
|
|
void addDirtyBlock(const QTextBlock &block);
|
|
|
|
void reHighlightDirtyBlocks();
|
|
|
|
bool _highlightingFinished;
|
|
HighlightingOptions _highlightingOptions;
|
|
QTimer *_timer;
|
|
QVector<QTextBlock> _dirtyTextBlocks;
|
|
QVector<QPair<int, int>> _linkRanges;
|
|
|
|
QHash<int, QVector<InlineRange>> _ranges;
|
|
|
|
static QVector<HighlightingRule> _highlightingRules;
|
|
static QHash<HighlighterState, QTextCharFormat> _formats;
|
|
static QHash<QString, HighlighterState> _langStringToEnum;
|
|
static constexpr int tildeOffset = 300;
|
|
};
|