Skip to content

Commit c4a9c82

Browse files
committed
[ui] ScriptEditor: Added syntax colorization for the script editor
Python syntax within the script editor is now highlighted making it easier to understand and write smaller code in it.
1 parent 7a4d423 commit c4a9c82

File tree

3 files changed

+187
-2
lines changed

3 files changed

+187
-2
lines changed

meshroom/ui/components/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ def registerTypes():
66
from meshroom.ui.components.filepath import FilepathHelper
77
from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController, Transformations3DHelper
88
from meshroom.ui.components.csvData import CsvData
9+
from meshroom.ui.components.scriptEditor import PySyntaxHighlighter
910

1011
qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea")
1112
qmlRegisterType(ClipboardHelper, "Meshroom.Helpers", 1, 0, "ClipboardHelper") # TODO: uncreatable
@@ -14,3 +15,4 @@ def registerTypes():
1415
qmlRegisterType(Transformations3DHelper, "Meshroom.Helpers", 1, 0, "Transformations3DHelper") # TODO: uncreatable
1516
qmlRegisterType(TrackballController, "Meshroom.Helpers", 1, 0, "TrackballController")
1617
qmlRegisterType(CsvData, "DataObjects", 1, 0, "CsvData")
18+
qmlRegisterType(PySyntaxHighlighter, "ScriptEditor", 1, 0, "PySyntaxHighlighter")

meshroom/ui/components/scriptEditor.py

Lines changed: 176 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
from PySide6.QtCore import QObject, Slot, QSettings
2-
1+
""" Script Editor for Meshroom.
2+
"""
3+
# STD
34
from io import StringIO
45
from contextlib import redirect_stdout
56
import traceback
67

8+
# Qt
9+
from PySide6 import QtCore, QtGui, QtQuick
10+
from PySide6.QtCore import Property, QObject, Slot, Signal, QSettings
11+
12+
713
class ScriptEditorManager(QObject):
814
""" Manages the script editor history and logs.
915
"""
@@ -114,3 +120,171 @@ def saveScript(self, script):
114120
settings.beginGroup(self._GROUP)
115121
settings.setValue(self._KEY, script)
116122
settings.sync()
123+
124+
125+
class CharFormat(QtGui.QTextCharFormat):
126+
""" The Char format for the syntax.
127+
"""
128+
129+
def __init__(self, color, bold=False, italic=False):
130+
""" Constructor.
131+
"""
132+
super().__init__()
133+
134+
self._color = QtGui.QColor()
135+
self._color.setNamedColor(color)
136+
137+
# Update the Foreground color
138+
self.setForeground(self._color)
139+
140+
# The font characteristics
141+
if bold:
142+
self.setFontWeight(QtGui.QFont.Bold)
143+
if italic:
144+
self.setFontItalic(True)
145+
146+
147+
class PySyntaxHighlighter(QtGui.QSyntaxHighlighter):
148+
"""Syntax highlighter for the Python language.
149+
"""
150+
151+
# Syntax styles that can be shared by all languages
152+
STYLES = {
153+
"keyword" : CharFormat("#9e59b3"), # Purple
154+
"operator" : CharFormat("#2cb8a0"), # Teal
155+
"brace" : CharFormat("#2f807e"), # Dark Aqua
156+
"defclass" : CharFormat("#c9ba49", bold=True), # Yellow
157+
"deffunc" : CharFormat("#4996c9", bold=True), # Blue
158+
"string" : CharFormat("#7dbd39"), # Greeny
159+
"comment" : CharFormat("#8d8d8d", italic=True), # Dark Grayish
160+
"self" : CharFormat("#e6ba43", italic=True), # Yellow
161+
"numbers" : CharFormat("#d47713"), # Orangish
162+
}
163+
164+
# Python keywords
165+
keywords = (
166+
"and", "assert", "break", "class", "continue", "def",
167+
"del", "elif", "else", "except", "exec", "finally",
168+
"for", "from", "global", "if", "import", "in",
169+
"is", "lambda", "not", "or", "pass", "print",
170+
"raise", "return", "try", "while", "yield",
171+
"None", "True", "False",
172+
)
173+
174+
# Python operators
175+
operators = (
176+
"=",
177+
# Comparison
178+
"==", "!=", "<", "<=", ">", ">=",
179+
# Arithmetic
180+
r"\+", "-", r"\*", "/", "//", r"\%", r"\*\*",
181+
# In-place
182+
r"\+=", "-=", r"\*=", "/=", r"\%=",
183+
# Bitwise
184+
r"\^", r"\|", r"\&", r"\~", r">>", r"<<",
185+
)
186+
187+
# Python braces
188+
braces = (r"\{", r"\}", r"\(", r"\)", r"\[", r"\]")
189+
190+
def __init__(self, parent=None):
191+
""" Constructor.
192+
193+
Keyword Args:
194+
parent (QObject): The QObject parent from the QML side.
195+
"""
196+
super().__init__(parent)
197+
198+
# The Document to highlight
199+
self._document = None
200+
201+
# Build a QRegExp for each of the pattern
202+
self._rules = self.__rules()
203+
204+
# Private
205+
def __rules(self):
206+
""" Formatting rules.
207+
"""
208+
# Set of rules accordind to which the highlight should occur
209+
rules = []
210+
211+
# Keyword rules
212+
rules += [(QtCore.QRegExp(r"\b" + w + r"\s"), 0, PySyntaxHighlighter.STYLES["keyword"]) for w in PySyntaxHighlighter.keywords]
213+
# Operator rules
214+
rules += [(QtCore.QRegExp(o), 0, PySyntaxHighlighter.STYLES["operator"]) for o in PySyntaxHighlighter.operators]
215+
# Braces
216+
rules += [(QtCore.QRegExp(b), 0, PySyntaxHighlighter.STYLES["brace"]) for b in PySyntaxHighlighter.braces]
217+
218+
# All other rules
219+
rules += [
220+
# self
221+
(QtCore.QRegExp(r'\bself\b'), 0, PySyntaxHighlighter.STYLES["self"]),
222+
223+
# 'def' followed by an identifier
224+
(QtCore.QRegExp(r'\bdef\b\s*(\w+)'), 1, PySyntaxHighlighter.STYLES["deffunc"]),
225+
# 'class' followed by an identifier
226+
(QtCore.QRegExp(r'\bclass\b\s*(\w+)'), 1, PySyntaxHighlighter.STYLES["defclass"]),
227+
228+
# Numeric literals
229+
(QtCore.QRegExp(r'\b[+-]?[0-9]+[lL]?\b'), 0, PySyntaxHighlighter.STYLES["numbers"]),
230+
(QtCore.QRegExp(r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b'), 0, PySyntaxHighlighter.STYLES["numbers"]),
231+
(QtCore.QRegExp(r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b'), 0, PySyntaxHighlighter.STYLES["numbers"]),
232+
233+
# Double-quoted string, possibly containing escape sequences
234+
(QtCore.QRegExp(r'"[^"\\]*(\\.[^"\\]*)*"'), 0, PySyntaxHighlighter.STYLES["string"]),
235+
# Single-quoted string, possibly containing escape sequences
236+
(QtCore.QRegExp(r"'[^'\\]*(\\.[^'\\]*)*'"), 0, PySyntaxHighlighter.STYLES["string"]),
237+
238+
# From '#' until a newline
239+
(QtCore.QRegExp(r'#[^\n]*'), 0, PySyntaxHighlighter.STYLES['comment']),
240+
]
241+
242+
return rules
243+
244+
def highlightBlock(self, text):
245+
""" Applies syntax highlighting to the given block of text.
246+
247+
Args:
248+
text (str): The text to highlight.
249+
"""
250+
# Do other syntax formatting
251+
for expression, nth, _format in self._rules:
252+
# fetch the index of the expression in text
253+
index = expression.indexIn(text, 0)
254+
255+
while index >= 0:
256+
# We actually want the index of the nth match
257+
index = expression.pos(nth)
258+
length = len(expression.cap(nth))
259+
self.setFormat(index, length, _format)
260+
index = expression.indexIn(text, index + length)
261+
262+
def textDoc(self):
263+
""" Returns the document being highlighted.
264+
"""
265+
return self._document
266+
267+
def setTextDocument(self, document):
268+
""" Sets the document on the Highlighter.
269+
270+
Args:
271+
document (QtQuick.QQuickTextDocument): The document from the QML engine.
272+
"""
273+
# If the same document is provided again
274+
if document == self._document:
275+
return
276+
277+
# Update the class document
278+
self._document = document
279+
280+
# Set the document on the highlighter
281+
self.setDocument(self._document.textDocument())
282+
283+
# Emit that the document is now changed
284+
self.textDocumentChanged.emit()
285+
286+
# Signals
287+
textDocumentChanged = Signal()
288+
289+
# Property
290+
textDocument = Property(QtQuick.QQuickTextDocument, textDoc, setTextDocument, notify=textDocumentChanged)

meshroom/ui/qml/GraphEditor/ScriptEditor.qml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import Utils 1.0
99

1010
import Qt.labs.platform 1.0 as Platform
1111

12+
import ScriptEditor 1.0
13+
1214
Item {
1315
id: root
1416

@@ -339,6 +341,13 @@ Item {
339341
}
340342
}
341343
}
344+
345+
// Syntax Highlights for the Input Area for Python Based Syntax
346+
PySyntaxHighlighter {
347+
id: syntaxHighlighter
348+
// The document to highlight
349+
textDocument: input.textDocument
350+
}
342351
}
343352
}
344353
}

0 commit comments

Comments
 (0)