Developer Documentation

PythonSyntaxHighlighter.cc
1 /*
2 This is a C++ port of the following PyQt example
3 http://diotavelli.net/PyQtWiki/Python%20syntax%20highlighting
4 C++ port by Frankie Simon (www.kickdrive.de, www.fuh-edv.de)
5
6 The following free software license applies for this file ("X11 license"):
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy of this software
9 and associated documentation files (the "Software"), to deal in the Software without restriction,
10 including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
12 subject to the following conditions:
13
14 The above copyright notice and this permission notice shall be included in all copies or substantial
15 portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
18 LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21 USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 #include "PythonSyntaxHighlighter.hh"
25
26 PythonSyntaxHighlighter::PythonSyntaxHighlighter(QTextDocument *parent)
27 : QSyntaxHighlighter(parent)
28 {
29 keywords = QStringList() << "and" << "assert" << "break" << "class" << "continue" << "def" <<
30 "del" << "elif" << "else" << "except" << "exec" << "finally" <<
31 "for" << "from" << "global" << "if" << "import" << "in" <<
32 "is" << "lambda" << "not" << "or" << "pass" << "print" <<
33 "raise" << "return" << "try" << "while" << "yield" <<
34 "None" << "True" << "False";
35
36 operators = QStringList() << "=" <<
37 // Comparison
38 "==" << "!=" << "<" << "<=" << ">" << ">=" <<
39 // Arithmetic
40 "\\+" << "-" << "\\*" << "/" << "//" << "%" << "\\*\\*" <<
41 // In-place
42 "\\+=" << "-=" << "\\*=" << "/=" << "%=" <<
43 // Bitwise
44 "\\^" << "\\|" << "&" << "~" << ">>" << "<<";
45
46 braces = QStringList() << "{" << "}" << "\\(" << "\\)" << "\\[" << "]";
47
48 basicStyles.insert("keyword", getTextCharFormat("blue"));
49 basicStyles.insert("operator", getTextCharFormat("red"));
50 basicStyles.insert("brace", getTextCharFormat("darkGray"));
51 basicStyles.insert("defclass", getTextCharFormat("black", "bold"));
52 basicStyles.insert("brace", getTextCharFormat("darkGray"));
53 basicStyles.insert("string", getTextCharFormat("magenta"));
54 basicStyles.insert("string2", getTextCharFormat("darkMagenta"));
55 basicStyles.insert("comment", getTextCharFormat("darkGreen", "italic"));
56 basicStyles.insert("self", getTextCharFormat("black", "italic"));
57 basicStyles.insert("numbers", getTextCharFormat("brown"));
58
59 triSingleQuote.setPattern("'''");
60 triDoubleQuote.setPattern("\"\"\"");
61
62 initializeRules();
63 }
64
65 void PythonSyntaxHighlighter::initializeRules()
66 {
67 foreach (QString currKeyword, keywords)
68 {
69 rules.append(HighlightingRule(QString("\\b%1\\b").arg(currKeyword), 0, basicStyles.value("keyword")));
70 }
71 foreach (QString currOperator, operators)
72 {
73 rules.append(HighlightingRule(QString("%1").arg(currOperator), 0, basicStyles.value("operator")));
74 }
75 foreach (QString currBrace, braces)
76 {
77 rules.append(HighlightingRule(QString("%1").arg(currBrace), 0, basicStyles.value("brace")));
78 }
79 // 'self'
80 rules.append(HighlightingRule("\\bself\\b", 0, basicStyles.value("self")));
81
82 // Double-quoted string, possibly containing escape sequences
83 // FF: originally in python : r'"[^"\\]*(\\.[^"\\]*)*"'
84 rules.append(HighlightingRule("\"[^\"\\\\]*(\\\\.[^\"\\\\]*)*\"", 0, basicStyles.value("string")));
85 // Single-quoted string, possibly containing escape sequences
86 // FF: originally in python : r"'[^'\\]*(\\.[^'\\]*)*'"
87 rules.append(HighlightingRule("'[^'\\\\]*(\\\\.[^'\\\\]*)*'", 0, basicStyles.value("string")));
88
89 // 'def' followed by an identifier
90 // FF: originally: r'\bdef\b\s*(\w+)'
91 rules.append(HighlightingRule("\\bdef\\b\\s*(\\w+)", 1, basicStyles.value("defclass")));
92 // 'class' followed by an identifier
93 // FF: originally: r'\bclass\b\s*(\w+)'
94 rules.append(HighlightingRule("\\bclass\\b\\s*(\\w+)", 1, basicStyles.value("defclass")));
95
96 // From '#' until a newline
97 // FF: originally: r'#[^\\n]*'
98 rules.append(HighlightingRule("#[^\\n]*", 0, basicStyles.value("comment")));
99
100 // Numeric literals
101 rules.append(HighlightingRule("\\b[+-]?[0-9]+[lL]?\\b", 0, basicStyles.value("numbers"))); // r'\b[+-]?[0-9]+[lL]?\b'
102 rules.append(HighlightingRule("\\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\\b", 0, basicStyles.value("numbers"))); // r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b'
103 rules.append(HighlightingRule("\\b[+-]?[0-9]+(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b", 0, basicStyles.value("numbers"))); // r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b'
104 }
105
106 void PythonSyntaxHighlighter::highlightBlock(const QString &text)
107 {
108 foreach (HighlightingRule currRule, rules)
109 {
110 int idx = currRule.pattern.indexIn(text, 0);
111 while (idx >= 0)
112 {
113 // Get index of Nth match
114 idx = currRule.pattern.pos(currRule.nth);
115 int length = currRule.pattern.cap(currRule.nth).length();
116 setFormat(idx, length, currRule.format);
117 idx = currRule.pattern.indexIn(text, idx + length);
118 }
119 }
120
121 setCurrentBlockState(0);
122
123 // Do multi-line strings
124 bool isInMultilne = matchMultiline(text, triSingleQuote, 1, basicStyles.value("string2"));
125 if (!isInMultilne)
126 isInMultilne = matchMultiline(text, triDoubleQuote, 2, basicStyles.value("string2"));
127 }
128
129 bool PythonSyntaxHighlighter::matchMultiline(const QString &text, const QRegExp &delimiter, const int inState, const QTextCharFormat &style)
130 {
131 int start = -1;
132 int add = -1;
133 int length = 0;
134
135 // If inside triple-single quotes, start at 0
136 if (previousBlockState() == inState) {
137 start = 0;
138 add = 0;
139 }
140 // Otherwise, look for the delimiter on this line
141 else {
142 start = delimiter.indexIn(text);
143 // Move past this match
144 add = delimiter.matchedLength();
145 }
146
147 // As long as there's a delimiter match on this line...
148 while (start >= 0) {
149 // Look for the ending delimiter
150 int end = delimiter.indexIn(text, start + add);
151 // Ending delimiter on this line?
152 if (end >= add) {
153 length = end - start + add + delimiter.matchedLength();
154 setCurrentBlockState(0);
155 }
156 // No; multi-line string
157 else {
158 setCurrentBlockState(inState);
159 length = text.length() - start + add;
160 }
161 // Apply formatting and look for next
162 setFormat(start, length, style);
163 start = delimiter.indexIn(text, start + length);
164 }
165 // Return True if still inside a multi-line string, False otherwise
166 if (currentBlockState() == inState)
167 return true;
168 else
169 return false;
170 }
171
172 const QTextCharFormat PythonSyntaxHighlighter::getTextCharFormat(const QString &colorName, const QString &style)
173 {
174 QTextCharFormat charFormat;
175 QColor color(colorName);
176 charFormat.setForeground(color);
177 if (style.contains("bold", Qt::CaseInsensitive))
178 charFormat.setFontWeight(QFont::Bold);
179 if (style.contains("italic", Qt::CaseInsensitive))
180 charFormat.setFontItalic(true);
181 return charFormat;
182 }
PythonSyntaxHighlighter::matchMultiline
bool matchMultiline(const QString &text, const QRegExp &delimiter, const int inState, const QTextCharFormat &style)
Highlighst multi-line strings, returns true if after processing we are still within the multi-line se...
Definition: PythonSyntaxHighlighter.cc:129
HighlightingRule
Container to describe a highlighting rule. Based on a regular expression, a relevant match # and the ...
Definition: PythonSyntaxHighlighter.hh:33