ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
PythonHighlighter.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "PythonHighlighter.h"
9 #include "ColorScheme.h"
10 
11 // Python keywords
12 static const QStringList s_Keywords = {
13  "and", "assert", "break", "class", "continue", "def", "del", "elif",
14  "else", "except", "exec", "finally", "for", "from", "global", "if",
15  "import", "in", "is", "lambda", "not", "or", "pass", "print",
16  "raise", "return", "try", "while", "yield", "None", "True", "False"};
17 
18 // Python operators
19 static const QStringList s_Operators = {"=",
20  // Comparison
21  "==",
22  "!=",
23  "<",
24  "<=",
25  ">",
26  ">=",
27  // Arithmetic
28  "\\+",
29  "-",
30  "\\*",
31  "/",
32  "//",
33  "\\%",
34  "\\*\\*",
35  // In-place
36  "\\+=",
37  "-=",
38  "\\*=",
39  "/=",
40  "\\%=",
41  // Bitwise
42  "\\^",
43  "\\|",
44  "\\&",
45  "\\~",
46  ">>",
47  "<<"};
48 
49 // Python braces
50 static const QStringList s_Braces = {"\\{", "\\}", "\\(", "\\)", R"([)", R"(])"};
51 
53 {
54  switch (e)
55  {
57  return {"Keyword"};
59  return {"Operator"};
60  case CodeElement::Brace:
61  return {"Brace"};
63  return {"Definition"};
65  return {"String"};
67  return {"Doc String"};
69  return {"Comment"};
70  case CodeElement::Self:
71  return {"Self"};
73  return {"Numbers"};
74  case CodeElement::End:
75  Q_FALLTHROUGH();
76  default:
77  Q_ASSERT(false);
78  return {};
79  }
80 }
81 
83 {
84  for (HighlightingRule &rule : m_highlightingRules)
85  {
86  rule.format = colorScheme[rule.element];
87  }
88 }
89 
90 PythonHighlighter::PythonHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent)
91 {
92  initialize();
93 }
94 
95 void PythonHighlighter::highlightBlock(const QString &text)
96 {
97  highlightPythonBlock(text);
98 }
99 
100 void PythonHighlighter::initialize()
101 {
102  // Multi-line strings (expression, flag, style)
103  // FIXME: The triple-quotes in these two lines will mess up the
104  // syntax highlighting from this point onward
105  m_triSingle = HighlightingRule(CodeElement::DocString, "'''", 1);
106 
107  m_triDouble = HighlightingRule(CodeElement::DocString, R"(""")", 2);
108 
109  // Keyword, operator, and brace rules
110  for (const QString &keyword : s_Keywords)
111  {
112  QString pattern = QString("\\b%1\\b").arg(keyword);
113  m_highlightingRules += HighlightingRule(CodeElement::Keyword, pattern, 0);
114  }
115 
116  for (const QString &pattern : s_Operators)
117  m_highlightingRules += HighlightingRule(CodeElement::Operator, pattern, 0);
118 
119  for (const QString &pattern : s_Braces)
120  m_highlightingRules += HighlightingRule(CodeElement::Brace, pattern, 0);
121 
122  // All other rules
123 
124  // 'self'
125  m_highlightingRules += HighlightingRule(CodeElement::Self, "\\bself\\b", 0);
126 
127  // Double-quoted string, possibly containing escape sequences
128  m_highlightingRules += HighlightingRule(CodeElement::String, R"("[^"\\]*(\\.[^"\\]*)*")", 0);
129  // Single-quoted string, possibly containing escape sequences
130  m_highlightingRules += HighlightingRule(CodeElement::String, R"("[^'\\]*(\\.[^'\\]*)*")", 0);
131 
132  // 'def' followed by an identifier
133  m_highlightingRules += HighlightingRule(CodeElement::Definition, R"(\bdef\b\s*(\w+))", 1);
134  // 'class' followed by an identifier
135  m_highlightingRules += HighlightingRule(CodeElement::Definition, R"(\bclass\b\s*(\w+))", 1);
136 
137  // From '#' until a newline
138  m_highlightingRules += HighlightingRule(CodeElement::Comment, "#[^\\n]*", 0);
139 
140  // Numeric literals
141  m_highlightingRules += HighlightingRule(CodeElement::Numbers, "\\b[+-]?[0-9]+[lL]?\\b", 0);
142  m_highlightingRules +=
143  HighlightingRule(CodeElement::Numbers, "\\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\\b", 0);
144  m_highlightingRules += HighlightingRule(
145  CodeElement::Numbers, R"(\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b)", 0);
146 }
147 
148 void PythonHighlighter::highlightPythonBlock(const QString &text)
149 {
150  if (text.isEmpty())
151  return;
152 
153  int index = -1;
154 
155  // Do other syntax formatting
156  for (const auto &rule : m_highlightingRules)
157  {
158  // Use QtCompatRegExpWrapper for unified QRegExp-like API (works in both Qt5 and Qt6)
159  QtCompatRegExpWrapper wrapper(rule.pattern);
160  index = wrapper.indexIn(text, 0);
161 
162  // We actually want the index of the nth match
163  while (index >= 0)
164  {
165  index = wrapper.pos(rule.matchIndex);
166  int length = wrapper.cap(rule.matchIndex).length();
167  if (length > 0)
168  {
169  setFormat(index, length, rule.format);
170  index = wrapper.indexIn(text, index + length);
171  }
172  else
173  {
174  index = -1;
175  }
176  }
177  }
178 
179  setCurrentBlockState(0);
180 
181  // Do multi-line strings
182  bool in_multiline = matchMultiLine(text, m_triSingle);
183  if (!in_multiline)
184  in_multiline = matchMultiLine(text, m_triDouble);
185 }
186 
187 /*Do highlighting of multi-line strings. ``delimiter`` should be a
188 ``QRegExp`` for triple-single-quotes or triple-double-quotes, and
189 ``in_state`` should be a unique integer to represent the corresponding
190 state changes when inside those strings. Returns True if we're still
191 inside a multi-line string when this function is finished.
192 */
193 bool PythonHighlighter::matchMultiLine(const QString &text, const HighlightingRule &rule)
194 {
195  int start, add, end, length;
196 
197  // Use QtCompatRegExpWrapper for unified QRegExp-like API (works in both Qt5 and Qt6)
198  QtCompatRegExpWrapper wrapper(rule.pattern);
199 
200  // If inside triple-single quotes, start at 0
201  if (previousBlockState() == rule.matchIndex)
202  {
203  start = 0;
204  add = 0;
205  }
206  // Otherwise, look for the delimiter on this line
207  else
208  {
209  start = wrapper.indexIn(text);
210  // Move past this match
211  add = wrapper.matchedLength();
212  }
213 
214  // As long as there's a delimiter match on this line...
215  while (start >= 0)
216  {
217  // Look for the ending delimiter
218  end = wrapper.indexIn(text, start + add);
219  // Ending delimiter on this line?
220  if (end >= add)
221  {
222  length = end - start + add + wrapper.matchedLength();
223  setCurrentBlockState(0);
224  }
225  // No; multi-line string
226  else
227  {
228  setCurrentBlockState(rule.matchIndex);
229  length = text.length() - start + add;
230  }
231 
232  // Apply formatting
233  setFormat(start, length, rule.format);
234 
235  // Look for the next match
236  start = wrapper.indexIn(text, start + length);
237  }
238 
239  // Return True if still inside a multi-line string, False otherwise
240  return currentBlockState() == rule.matchIndex;
241 }
static const QStringList s_Keywords
static const QStringList s_Operators
static const QStringList s_Braces
ColorScheme to be used by the PythonHighlighter & Editor.
Definition: ColorScheme.h:14
static QString CodeElementName(PythonHighlighter::CodeElement e)
void highlightBlock(const QString &text) override
PythonHighlighter(QTextDocument *parent=nullptr)
void useColorScheme(const ColorScheme &colorScheme)
__host__ __device__ float length(float2 v)
Definition: cutil_math.h:1162