Skip to content

Commit

Permalink
[ui] ScriptEditor: Added syntax colorization for the script editor
Browse files Browse the repository at this point in the history
Python syntax within the script editor is now highlighted making it easier to understand and write smaller code in it.
  • Loading branch information
waaake committed Oct 28, 2024
1 parent 22eb380 commit 2bd93ff
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 2 deletions.
2 changes: 2 additions & 0 deletions meshroom/ui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def registerTypes():
from meshroom.ui.components.filepath import FilepathHelper
from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController, Transformations3DHelper
from meshroom.ui.components.csvData import CsvData
from meshroom.ui.components.scriptEditor import PySyntaxHighlighter

qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea")
qmlRegisterType(ClipboardHelper, "Meshroom.Helpers", 1, 0, "ClipboardHelper") # TODO: uncreatable
Expand All @@ -14,3 +15,4 @@ def registerTypes():
qmlRegisterType(Transformations3DHelper, "Meshroom.Helpers", 1, 0, "Transformations3DHelper") # TODO: uncreatable
qmlRegisterType(TrackballController, "Meshroom.Helpers", 1, 0, "TrackballController")
qmlRegisterType(CsvData, "DataObjects", 1, 0, "CsvData")
qmlRegisterType(PySyntaxHighlighter, "ScriptEditor", 1, 0, "PySyntaxHighlighter")
178 changes: 176 additions & 2 deletions meshroom/ui/components/scriptEditor.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
from PySide2.QtCore import QObject, Slot, QSettings

""" Script Editor for Meshroom.
"""
# STD
from io import StringIO
from contextlib import redirect_stdout
import traceback

# Qt
from PySide2 import QtCore, QtGui, QtQuick
from PySide2.QtCore import Property, QObject, Slot, Signal, QSettings


class ScriptEditorManager(QObject):
""" Manages the script editor history and logs.
"""
Expand Down Expand Up @@ -114,3 +120,171 @@ def saveScript(self, script):
settings.beginGroup(self._GROUP)
settings.setValue(self._KEY, script)
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)
9 changes: 9 additions & 0 deletions meshroom/ui/qml/GraphEditor/ScriptEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import MaterialIcons 2.2
import Qt.labs.platform 1.0 as Platform
import QtQuick.Dialogs 1.3

import ScriptEditor 1.0

Item {
id: root

Expand Down Expand Up @@ -338,6 +340,13 @@ Item {
}
}
}

// Syntax Highlights for the Input Area for Python Based Syntax
PySyntaxHighlighter {
id: syntaxHighlighter
// The document to highlight
textDocument: input.textDocument
}
}
}
}

0 comments on commit 2bd93ff

Please sign in to comment.