[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.
This commit is contained in:
waaake 2024-10-28 16:03:08 +05:30
parent 4464cdf799
commit 2e577274e6
3 changed files with 187 additions and 2 deletions

View file

@ -7,6 +7,7 @@ def registerTypes():
from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController, Transformations3DHelper from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController, Transformations3DHelper
from meshroom.ui.components.csvData import CsvData from meshroom.ui.components.csvData import CsvData
from meshroom.ui.components.geom2D import Geom2D from meshroom.ui.components.geom2D import Geom2D
from meshroom.ui.components.scriptEditor import PySyntaxHighlighter
qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea") qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea")
qmlRegisterType(ClipboardHelper, "Meshroom.Helpers", 1, 0, "ClipboardHelper") # TODO: uncreatable qmlRegisterType(ClipboardHelper, "Meshroom.Helpers", 1, 0, "ClipboardHelper") # TODO: uncreatable
@ -15,5 +16,6 @@ def registerTypes():
qmlRegisterType(Transformations3DHelper, "Meshroom.Helpers", 1, 0, "Transformations3DHelper") # TODO: uncreatable qmlRegisterType(Transformations3DHelper, "Meshroom.Helpers", 1, 0, "Transformations3DHelper") # TODO: uncreatable
qmlRegisterType(TrackballController, "Meshroom.Helpers", 1, 0, "TrackballController") qmlRegisterType(TrackballController, "Meshroom.Helpers", 1, 0, "TrackballController")
qmlRegisterType(CsvData, "DataObjects", 1, 0, "CsvData") qmlRegisterType(CsvData, "DataObjects", 1, 0, "CsvData")
qmlRegisterType(PySyntaxHighlighter, "ScriptEditor", 1, 0, "PySyntaxHighlighter")
qmlRegisterSingletonType(Geom2D, "Meshroom.Helpers", 1, 0, "Geom2D") qmlRegisterSingletonType(Geom2D, "Meshroom.Helpers", 1, 0, "Geom2D")

View file

@ -1,9 +1,15 @@
from PySide6.QtCore import QObject, Slot, QSettings """ Script Editor for Meshroom.
"""
# STD
from io import StringIO from io import StringIO
from contextlib import redirect_stdout from contextlib import redirect_stdout
import traceback import traceback
# Qt
from PySide6 import QtCore, QtGui, QtQuick
from PySide6.QtCore import Property, QObject, Slot, Signal, QSettings
class ScriptEditorManager(QObject): class ScriptEditorManager(QObject):
""" Manages the script editor history and logs. """ Manages the script editor history and logs.
""" """
@ -114,3 +120,171 @@ class ScriptEditorManager(QObject):
settings.beginGroup(self._GROUP) settings.beginGroup(self._GROUP)
settings.setValue(self._KEY, script) settings.setValue(self._KEY, script)
settings.sync() settings.sync()
class CharFormat(QtGui.QTextCharFormat):
""" The Char format for the syntax.
"""
def __init__(self, color, bold=False, italic=False):
""" Constructor.
"""
super().__init__()
self._color = QtGui.QColor()
self._color.setNamedColor(color)
# Update the Foreground color
self.setForeground(self._color)
# The font characteristics
if bold:
self.setFontWeight(QtGui.QFont.Bold)
if italic:
self.setFontItalic(True)
class PySyntaxHighlighter(QtGui.QSyntaxHighlighter):
"""Syntax highlighter for the Python language.
"""
# Syntax styles that can be shared by all languages
STYLES = {
"keyword" : CharFormat("#9e59b3"), # Purple
"operator" : CharFormat("#2cb8a0"), # Teal
"brace" : CharFormat("#2f807e"), # Dark Aqua
"defclass" : CharFormat("#c9ba49", bold=True), # Yellow
"deffunc" : CharFormat("#4996c9", bold=True), # Blue
"string" : CharFormat("#7dbd39"), # Greeny
"comment" : CharFormat("#8d8d8d", italic=True), # Dark Grayish
"self" : CharFormat("#e6ba43", italic=True), # Yellow
"numbers" : CharFormat("#d47713"), # Orangish
}
# Python keywords
keywords = (
"and", "assert", "break", "class", "continue", "def",
"del", "elif", "else", "except", "exec", "finally",
"for", "from", "global", "if", "import", "in",
"is", "lambda", "not", "or", "pass", "print",
"raise", "return", "try", "while", "yield",
"None", "True", "False",
)
# Python operators
operators = (
"=",
# Comparison
"==", "!=", "<", "<=", ">", ">=",
# Arithmetic
r"\+", "-", r"\*", "/", "//", r"\%", r"\*\*",
# In-place
r"\+=", "-=", r"\*=", "/=", r"\%=",
# Bitwise
r"\^", r"\|", r"\&", r"\~", r">>", r"<<",
)
# Python braces
braces = (r"\{", r"\}", r"\(", r"\)", r"\[", r"\]")
def __init__(self, parent=None):
""" Constructor.
Keyword Args:
parent (QObject): The QObject parent from the QML side.
"""
super().__init__(parent)
# The Document to highlight
self._document = None
# Build a QRegExp for each of the pattern
self._rules = self.__rules()
# Private
def __rules(self):
""" Formatting rules.
"""
# Set of rules accordind to which the highlight should occur
rules = []
# Keyword rules
rules += [(QtCore.QRegExp(r"\b" + w + r"\s"), 0, PySyntaxHighlighter.STYLES["keyword"]) for w in PySyntaxHighlighter.keywords]
# Operator rules
rules += [(QtCore.QRegExp(o), 0, PySyntaxHighlighter.STYLES["operator"]) for o in PySyntaxHighlighter.operators]
# Braces
rules += [(QtCore.QRegExp(b), 0, PySyntaxHighlighter.STYLES["brace"]) for b in PySyntaxHighlighter.braces]
# All other rules
rules += [
# self
(QtCore.QRegExp(r'\bself\b'), 0, PySyntaxHighlighter.STYLES["self"]),
# 'def' followed by an identifier
(QtCore.QRegExp(r'\bdef\b\s*(\w+)'), 1, PySyntaxHighlighter.STYLES["deffunc"]),
# 'class' followed by an identifier
(QtCore.QRegExp(r'\bclass\b\s*(\w+)'), 1, PySyntaxHighlighter.STYLES["defclass"]),
# Numeric literals
(QtCore.QRegExp(r'\b[+-]?[0-9]+[lL]?\b'), 0, PySyntaxHighlighter.STYLES["numbers"]),
(QtCore.QRegExp(r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b'), 0, PySyntaxHighlighter.STYLES["numbers"]),
(QtCore.QRegExp(r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b'), 0, PySyntaxHighlighter.STYLES["numbers"]),
# Double-quoted string, possibly containing escape sequences
(QtCore.QRegExp(r'"[^"\\]*(\\.[^"\\]*)*"'), 0, PySyntaxHighlighter.STYLES["string"]),
# Single-quoted string, possibly containing escape sequences
(QtCore.QRegExp(r"'[^'\\]*(\\.[^'\\]*)*'"), 0, PySyntaxHighlighter.STYLES["string"]),
# From '#' until a newline
(QtCore.QRegExp(r'#[^\n]*'), 0, PySyntaxHighlighter.STYLES['comment']),
]
return rules
def highlightBlock(self, text):
""" Applies syntax highlighting to the given block of text.
Args:
text (str): The text to highlight.
"""
# Do other syntax formatting
for expression, nth, _format in self._rules:
# fetch the index of the expression in text
index = expression.indexIn(text, 0)
while index >= 0:
# We actually want the index of the nth match
index = expression.pos(nth)
length = len(expression.cap(nth))
self.setFormat(index, length, _format)
index = expression.indexIn(text, index + length)
def textDoc(self):
""" Returns the document being highlighted.
"""
return self._document
def setTextDocument(self, document):
""" Sets the document on the Highlighter.
Args:
document (QtQuick.QQuickTextDocument): The document from the QML engine.
"""
# If the same document is provided again
if document == self._document:
return
# Update the class document
self._document = document
# Set the document on the highlighter
self.setDocument(self._document.textDocument())
# Emit that the document is now changed
self.textDocumentChanged.emit()
# Signals
textDocumentChanged = Signal()
# Property
textDocument = Property(QtQuick.QQuickTextDocument, textDoc, setTextDocument, notify=textDocumentChanged)

View file

@ -9,6 +9,8 @@ import Utils 1.0
import Qt.labs.platform 1.0 as Platform import Qt.labs.platform 1.0 as Platform
import ScriptEditor 1.0
Item { Item {
id: root id: root
@ -339,6 +341,13 @@ Item {
} }
} }
} }
// Syntax Highlights for the Input Area for Python Based Syntax
PySyntaxHighlighter {
id: syntaxHighlighter
// The document to highlight
textDocument: input.textDocument
}
} }
} }
} }