Skip to content

Commit a41af46

Browse files
committed
Moved syntax highlighter into widget
1 parent 982d559 commit a41af46

File tree

2 files changed

+255
-249
lines changed

2 files changed

+255
-249
lines changed

cq_editor/widgets/editor.py

Lines changed: 255 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
from modulefinder import ModuleFinder
55

66
from .code_editor import CodeEditor
7-
from .pyhighlight import PythonHighlighter
87

9-
from PyQt5.QtCore import pyqtSignal, QFileSystemWatcher, QTimer, Qt, QEvent
8+
from PyQt5.QtCore import pyqtSignal, QFileSystemWatcher, QTimer, Qt, QEvent, QRegExp
109
from PyQt5.QtWidgets import (
1110
QAction,
1211
QFileDialog,
@@ -15,7 +14,15 @@
1514
QListWidgetItem,
1615
QShortcut,
1716
)
18-
from PyQt5.QtGui import QFontDatabase, QTextCursor, QKeyEvent
17+
from PyQt5.QtGui import (
18+
QFontDatabase,
19+
QTextCursor,
20+
QKeyEvent,
21+
QColor,
22+
QTextCharFormat,
23+
QFont,
24+
QSyntaxHighlighter,
25+
)
1926
from path import Path
2027

2128
import sys
@@ -30,6 +37,251 @@
3037
from ..icons import icon
3138

3239

40+
def format(color, style=""):
41+
"""
42+
Return a QTextCharFormat with the given attributes.
43+
"""
44+
_color = QColor()
45+
_color.setNamedColor(color)
46+
47+
_format = QTextCharFormat()
48+
_format.setForeground(_color)
49+
if "bold" in style:
50+
_format.setFontWeight(QFont.Bold)
51+
if "italic" in style:
52+
_format.setFontItalic(True)
53+
54+
return _format
55+
56+
57+
# Syntax styles that can be shared by all languages
58+
STYLES = {
59+
"keyword": format("blue"),
60+
"operator": format("gray"),
61+
"brace": format("darkGray"),
62+
"defclass": format("gray", "bold"),
63+
"string": format("orange"),
64+
"string2": format("darkMagenta"),
65+
"comment": format("darkGreen", "italic"),
66+
"self": format("black", "italic"),
67+
"numbers": format("magenta"),
68+
}
69+
70+
71+
class PythonHighlighter(QSyntaxHighlighter):
72+
"""
73+
Syntax highlighter for the Python language.
74+
"""
75+
76+
# Python keywords
77+
keywords = [
78+
"and",
79+
"assert",
80+
"break",
81+
"class",
82+
"continue",
83+
"def",
84+
"del",
85+
"elif",
86+
"else",
87+
"except",
88+
"exec",
89+
"finally",
90+
"for",
91+
"from",
92+
"global",
93+
"if",
94+
"import",
95+
"in",
96+
"is",
97+
"lambda",
98+
"not",
99+
"or",
100+
"pass",
101+
"print",
102+
"raise",
103+
"return",
104+
"try",
105+
"while",
106+
"yield",
107+
"None",
108+
"True",
109+
"False",
110+
]
111+
112+
# Python operators
113+
operators = [
114+
"=",
115+
# Comparison
116+
"==",
117+
"!=",
118+
"<",
119+
"<=",
120+
">",
121+
">=",
122+
# Arithmetic
123+
"\+",
124+
"-",
125+
"\*",
126+
"/",
127+
"//",
128+
"\%",
129+
"\*\*",
130+
# In-place
131+
"\+=",
132+
"-=",
133+
"\*=",
134+
"/=",
135+
"\%=",
136+
# Bitwise
137+
"\^",
138+
"\|",
139+
"\&",
140+
"\~",
141+
">>",
142+
"<<",
143+
]
144+
145+
# Python braces
146+
braces = [
147+
"\{",
148+
"\}",
149+
"\(",
150+
"\)",
151+
"\[",
152+
"\]",
153+
]
154+
155+
def __init__(self, parent=None):
156+
super(PythonHighlighter, self).__init__(parent)
157+
158+
# Multi-line strings (expression, flag, style)
159+
self.tri_single = (QRegExp("'''"), 1, STYLES["string2"])
160+
self.tri_double = (QRegExp('"""'), 2, STYLES["string2"])
161+
162+
rules = []
163+
164+
# Keyword, operator, and brace rules
165+
rules += [
166+
(r"\b%s\b" % w, 0, STYLES["keyword"]) for w in PythonHighlighter.keywords
167+
]
168+
rules += [
169+
(r"%s" % o, 0, STYLES["operator"]) for o in PythonHighlighter.operators
170+
]
171+
rules += [(r"%s" % b, 0, STYLES["brace"]) for b in PythonHighlighter.braces]
172+
173+
# All other rules
174+
rules += [
175+
# 'self'
176+
(r"\bself\b", 0, STYLES["self"]),
177+
# 'def' followed by an identifier
178+
(r"\bdef\b\s*(\w+)", 1, STYLES["defclass"]),
179+
# 'class' followed by an identifier
180+
(r"\bclass\b\s*(\w+)", 1, STYLES["defclass"]),
181+
# Numeric literals
182+
(r"\b[+-]?[0-9]+[lL]?\b", 0, STYLES["numbers"]),
183+
(r"\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b", 0, STYLES["numbers"]),
184+
(r"\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b", 0, STYLES["numbers"]),
185+
# Double-quoted string, possibly containing escape sequences
186+
(r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES["string"]),
187+
# Single-quoted string, possibly containing escape sequences
188+
(r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES["string"]),
189+
# From '#' until a newline
190+
(r"#[^\n]*", 0, STYLES["comment"]),
191+
]
192+
193+
# Build a QRegExp for each pattern
194+
self.rules = [(QRegExp(pat), index, fmt) for (pat, index, fmt) in rules]
195+
196+
def highlightBlock(self, text):
197+
"""
198+
Apply syntax highlighting to the given block of text.
199+
"""
200+
self.tripleQuoutesWithinStrings = []
201+
# Do other syntax formatting
202+
for expression, nth, format in self.rules:
203+
index = expression.indexIn(text, 0)
204+
if index >= 0:
205+
# if there is a string we check
206+
# if there are some triple quotes within the string
207+
# they will be ignored if they are matched again
208+
if expression.pattern() in [
209+
r'"[^"\\]*(\\.[^"\\]*)*"',
210+
r"'[^'\\]*(\\.[^'\\]*)*'",
211+
]:
212+
innerIndex = self.tri_single[0].indexIn(text, index + 1)
213+
if innerIndex == -1:
214+
innerIndex = self.tri_double[0].indexIn(text, index + 1)
215+
216+
if innerIndex != -1:
217+
tripleQuoteIndexes = range(innerIndex, innerIndex + 3)
218+
self.tripleQuoutesWithinStrings.extend(tripleQuoteIndexes)
219+
220+
while index >= 0:
221+
# skipping triple quotes within strings
222+
if index in self.tripleQuoutesWithinStrings:
223+
index += 1
224+
expression.indexIn(text, index)
225+
continue
226+
227+
# We actually want the index of the nth match
228+
index = expression.pos(nth)
229+
length = len(expression.cap(nth))
230+
self.setFormat(index, length, format)
231+
index = expression.indexIn(text, index + length)
232+
233+
self.setCurrentBlockState(0)
234+
235+
# Do multi-line strings
236+
in_multiline = self.match_multiline(text, *self.tri_single)
237+
if not in_multiline:
238+
in_multiline = self.match_multiline(text, *self.tri_double)
239+
240+
def match_multiline(self, text, delimiter, in_state, style):
241+
"""
242+
Do highlighting of multi-line strings. ``delimiter`` should be a
243+
``QRegExp`` for triple-single-quotes or triple-double-quotes, and
244+
``in_state`` should be a unique integer to represent the corresponding
245+
state changes when inside those strings. Returns True if we're still
246+
inside a multi-line string when this function is finished.
247+
"""
248+
# If inside triple-single quotes, start at 0
249+
if self.previousBlockState() == in_state:
250+
start = 0
251+
add = 0
252+
# Otherwise, look for the delimiter on this line
253+
else:
254+
start = delimiter.indexIn(text)
255+
# skipping triple quotes within strings
256+
if start in self.tripleQuoutesWithinStrings:
257+
return False
258+
# Move past this match
259+
add = delimiter.matchedLength()
260+
261+
# As long as there's a delimiter match on this line...
262+
while start >= 0:
263+
# Look for the ending delimiter
264+
end = delimiter.indexIn(text, start + add)
265+
# Ending delimiter on this line?
266+
if end >= add:
267+
length = end - start + add + delimiter.matchedLength()
268+
self.setCurrentBlockState(0)
269+
# No; multi-line string
270+
else:
271+
self.setCurrentBlockState(in_state)
272+
length = len(text) - start + add
273+
# Apply formatting
274+
self.setFormat(start, length, style)
275+
# Look for the next match
276+
start = delimiter.indexIn(text, start + length)
277+
278+
# Return True if still inside a multi-line string, False otherwise
279+
if self.currentBlockState() == in_state:
280+
return True
281+
else:
282+
return False
283+
284+
33285
class EditorDebugger:
34286
def __init__(self):
35287
self.breakpoints = []

0 commit comments

Comments
 (0)