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 }
Container to describe a highlighting rule. Based on a regular expression, a relevant match # and the ...
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...