If you need a code editor with syntax highlighting, but don't want something as heavyweight as [[http://www.riverbankcomputing.co.uk/static/Docs/QScintilla2/classQsciScintilla.html | QsciScintilla]], you can use the [[http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qsyntaxhighlighter.html | QSyntaxHighlighter]] class to apply highlighting to a [[http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qplaintextedit.html | QPlainTextEdit]] widget. This example was based on existing work by [[http://www.carsonfarmer.com/?p=333 | Carson Farmer]] and [[http://lateral.netmanagers.com.ar/weblog/2009/09/21.html#BB831 | Christophe Kibleur]], and an [[http://artis.inrialpes.fr/Membres/Xavier.Decoret/resources/scipres/wiki/index.php/Python.py | example on the SciPres wiki]]. One aspect not addressed by this prior work is handling of Python's triple-quoted strings, which may span multiple lines; the QSyntaxHighlighter documentation includes an example for C++ comments, but those have different beginning and ending delimiters `/* ... */`, whereas Python's triple-quoted strings have the same delimiter at the beginning and end. These are handled by the `match_multiline` method and by treating the triple-quotes within strings as an special case --there may be an easier way to do this, but it seems to work pretty well. I have placed this code under the [[http://directory.fsf.org/wiki/License:BSD_3Clause|Modified BSD License]] because I believe the author intended it to be freely used. However, I don't believe that I originally wrote this example, though I was responsible for migrating it to the Python Wiki. -- DavidBoddie <<DateTime(2017-01-19T20:34:17Z)>> I modified this code to fix a problem when triple-quotes were embedded inside strings. -- [[Artemio Garza Reyna]] <<DateTime(2021-08-08T20:26:00Z)>> {{{ #!python # syntax.py import sys from PySide2 import QtCore, QtGui, QtWidgets def format(color, style=''): """Return a QTextCharFormat with the given attributes. """ _color = QtGui.QColor() _color.setNamedColor(color) _format = QtGui.QTextCharFormat() _format.setForeground(_color) if 'bold' in style: _format.setFontWeight(QtGui.QFont.Bold) if 'italic' in style: _format.setFontItalic(True) return _format # Syntax styles that can be shared by all languages STYLES = { 'keyword': format('blue'), 'operator': format('red'), 'brace': format('darkGray'), 'defclass': format('black', 'bold'), 'string': format('magenta'), 'string2': format('darkMagenta'), 'comment': format('darkGreen', 'italic'), 'self': format('black', 'italic'), 'numbers': format('brown'), } class PythonHighlighter (QtGui.QSyntaxHighlighter): """Syntax highlighter for the Python language. """ # 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 '\+', '-', '\*', '/', '//', '\%', '\*\*', # In-place '\+=', '-=', '\*=', '/=', '\%=', # Bitwise '\^', '\|', '\&', '\~', '>>', '<<', ] # Python braces braces = [ '\{', '\}', '\(', '\)', '\[', '\]', ] def __init__(self, parent: QtGui.QTextDocument) -> None: super().__init__(parent) # Multi-line strings (expression, flag, style) self.tri_single = (QtCore.QRegExp("'''"), 1, STYLES['string2']) self.tri_double = (QtCore.QRegExp('"""'), 2, STYLES['string2']) rules = [] # Keyword, operator, and brace rules rules += [(r'\b%s\b' % w, 0, STYLES['keyword']) for w in PythonHighlighter.keywords] rules += [(r'%s' % o, 0, STYLES['operator']) for o in PythonHighlighter.operators] rules += [(r'%s' % b, 0, STYLES['brace']) for b in PythonHighlighter.braces] # All other rules rules += [ # 'self' (r'\bself\b', 0, STYLES['self']), # 'def' followed by an identifier (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']), # 'class' followed by an identifier (r'\bclass\b\s*(\w+)', 1, STYLES['defclass']), # Numeric literals (r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']), (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']), (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']), # Double-quoted string, possibly containing escape sequences (r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']), # Single-quoted string, possibly containing escape sequences (r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']), # From '#' until a newline (r'#[^\n]*', 0, STYLES['comment']), ] # Build a QRegExp for each pattern self.rules = [(QtCore.QRegExp(pat), index, fmt) for (pat, index, fmt) in rules] def highlightBlock(self, text): """Apply syntax highlighting to the given block of text. """ self.tripleQuoutesWithinStrings = [] # Do other syntax formatting for expression, nth, format in self.rules: index = expression.indexIn(text, 0) if index >= 0: # if there is a string we check # if there are some triple quotes within the string # they will be ignored if they are matched again if expression.pattern() in [r'"[^"\\]*(\\.[^"\\]*)*"', r"'[^'\\]*(\\.[^'\\]*)*'"]: innerIndex = self.tri_single[0].indexIn(text, index + 1) if innerIndex == -1: innerIndex = self.tri_double[0].indexIn(text, index + 1) if innerIndex != -1: tripleQuoteIndexes = range(innerIndex, innerIndex + 3) self.tripleQuoutesWithinStrings.extend(tripleQuoteIndexes) while index >= 0: # skipping triple quotes within strings if index in self.tripleQuoutesWithinStrings: index += 1 expression.indexIn(text, index) continue # 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) self.setCurrentBlockState(0) # Do multi-line strings in_multiline = self.match_multiline(text, *self.tri_single) if not in_multiline: in_multiline = self.match_multiline(text, *self.tri_double) def match_multiline(self, text, delimiter, in_state, style): """Do highlighting of multi-line strings. ``delimiter`` should be a ``QRegExp`` for triple-single-quotes or triple-double-quotes, and ``in_state`` should be a unique integer to represent the corresponding state changes when inside those strings. Returns True if we're still inside a multi-line string when this function is finished. """ # If inside triple-single quotes, start at 0 if self.previousBlockState() == in_state: start = 0 add = 0 # Otherwise, look for the delimiter on this line else: start = delimiter.indexIn(text) # skipping triple quotes within strings if start in self.tripleQuoutesWithinStrings: return False # Move past this match add = delimiter.matchedLength() # As long as there's a delimiter match on this line... while start >= 0: # Look for the ending delimiter end = delimiter.indexIn(text, start + add) # Ending delimiter on this line? if end >= add: length = end - start + add + delimiter.matchedLength() self.setCurrentBlockState(0) # No; multi-line string else: self.setCurrentBlockState(in_state) length = len(text) - start + add # Apply formatting self.setFormat(start, length, style) # Look for the next match start = delimiter.indexIn(text, start + length) # Return True if still inside a multi-line string, False otherwise if self.currentBlockState() == in_state: return True else: return False }}} Here's a simple editor application that demonstrates it (not including save/load features). Really all you need to do is instantiate the `syntax.PythonHighlighter` class, passing the `QPlainTextEdit` widget's document to the constructor: {{{ #!python # editor.py from PyQt4 import QtGui import syntax app = QtGui.QApplication([]) editor = QtGui.QPlainTextEdit() highlight = syntax.PythonHighlighter(editor.document()) editor.show() # Load syntax.py into the editor for demo purposes infile = open('syntax.py', 'r') editor.setPlainText(infile.read()) app.exec_() }}}