If you need a code editor with syntax highlighting, but don't want something as heavyweight as QsciScintilla, you can use the QSyntaxHighlighter class to apply highlighting to a QPlainTextEdit widget.

This example was based on existing work by Carson Farmer and Christophe Kibleur, and an 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--there may be an easier way to do this, but it seems to work pretty well, although it still has trouble with triple-quotes embedded inside another string (as you'll see if you run the example editor.py below).

I have placed this code under the 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 2017-01-19 20:34:17

   1 # syntax.py
   2 
   3 import sys
   4 
   5 from PyQt4.QtCore import QRegExp
   6 from PyQt4.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter
   7 
   8 def format(color, style=''):
   9     """Return a QTextCharFormat with the given attributes.
  10     """
  11     _color = QColor()
  12     _color.setNamedColor(color)
  13 
  14     _format = QTextCharFormat()
  15     _format.setForeground(_color)
  16     if 'bold' in style:
  17         _format.setFontWeight(QFont.Bold)
  18     if 'italic' in style:
  19         _format.setFontItalic(True)
  20 
  21     return _format
  22 
  23 
  24 # Syntax styles that can be shared by all languages
  25 STYLES = {
  26     'keyword': format('blue'),
  27     'operator': format('red'),
  28     'brace': format('darkGray'),
  29     'defclass': format('black', 'bold'),
  30     'string': format('magenta'),
  31     'string2': format('darkMagenta'),
  32     'comment': format('darkGreen', 'italic'),
  33     'self': format('black', 'italic'),
  34     'numbers': format('brown'),
  35 }
  36 
  37 
  38 class PythonHighlighter (QSyntaxHighlighter):
  39     """Syntax highlighter for the Python language.
  40     """
  41     # Python keywords
  42     keywords = [
  43         'and', 'assert', 'break', 'class', 'continue', 'def',
  44         'del', 'elif', 'else', 'except', 'exec', 'finally',
  45         'for', 'from', 'global', 'if', 'import', 'in',
  46         'is', 'lambda', 'not', 'or', 'pass', 'print',
  47         'raise', 'return', 'try', 'while', 'yield',
  48         'None', 'True', 'False',
  49     ]
  50 
  51     # Python operators
  52     operators = [
  53         '=',
  54         # Comparison
  55         '==', '!=', '<', '<=', '>', '>=',
  56         # Arithmetic
  57         '\+', '-', '\*', '/', '//', '\%', '\*\*',
  58         # In-place
  59         '\+=', '-=', '\*=', '/=', '\%=',
  60         # Bitwise
  61         '\^', '\|', '\&', '\~', '>>', '<<',
  62     ]
  63 
  64     # Python braces
  65     braces = [
  66         '\{', '\}', '\(', '\)', '\[', '\]',
  67     ]
  68     def __init__(self, document):
  69         QSyntaxHighlighter.__init__(self, document)
  70 
  71         # Multi-line strings (expression, flag, style)
  72         # FIXME: The triple-quotes in these two lines will mess up the
  73         # syntax highlighting from this point onward
  74         self.tri_single = (QRegExp("'''"), 1, STYLES['string2'])
  75         self.tri_double = (QRegExp('"""'), 2, STYLES['string2'])
  76 
  77         rules = []
  78 
  79         # Keyword, operator, and brace rules
  80         rules += [(r'\b%s\b' % w, 0, STYLES['keyword'])
  81             for w in PythonHighlighter.keywords]
  82         rules += [(r'%s' % o, 0, STYLES['operator'])
  83             for o in PythonHighlighter.operators]
  84         rules += [(r'%s' % b, 0, STYLES['brace'])
  85             for b in PythonHighlighter.braces]
  86 
  87         # All other rules
  88         rules += [
  89             # 'self'
  90             (r'\bself\b', 0, STYLES['self']),
  91 
  92             # Double-quoted string, possibly containing escape sequences
  93             (r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
  94             # Single-quoted string, possibly containing escape sequences
  95             (r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']),
  96 
  97             # 'def' followed by an identifier
  98             (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
  99             # 'class' followed by an identifier
 100             (r'\bclass\b\s*(\w+)', 1, STYLES['defclass']),
 101 
 102             # From '#' until a newline
 103             (r'#[^\n]*', 0, STYLES['comment']),
 104 
 105             # Numeric literals
 106             (r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']),
 107             (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
 108             (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']),
 109         ]
 110 
 111         # Build a QRegExp for each pattern
 112         self.rules = [(QRegExp(pat), index, fmt)
 113             for (pat, index, fmt) in rules]
 114 
 115 
 116     def highlightBlock(self, text):
 117         """Apply syntax highlighting to the given block of text.
 118         """
 119         # Do other syntax formatting
 120         for expression, nth, format in self.rules:
 121             index = expression.indexIn(text, 0)
 122 
 123             while index >= 0:
 124                 # We actually want the index of the nth match
 125                 index = expression.pos(nth)
 126                 length = expression.cap(nth).length()
 127                 self.setFormat(index, length, format)
 128                 index = expression.indexIn(text, index + length)
 129 
 130         self.setCurrentBlockState(0)
 131 
 132         # Do multi-line strings
 133         in_multiline = self.match_multiline(text, *self.tri_single)
 134         if not in_multiline:
 135             in_multiline = self.match_multiline(text, *self.tri_double)
 136 
 137 
 138     def match_multiline(self, text, delimiter, in_state, style):
 139         """Do highlighting of multi-line strings. ``delimiter`` should be a
 140         ``QRegExp`` for triple-single-quotes or triple-double-quotes, and
 141         ``in_state`` should be a unique integer to represent the corresponding
 142         state changes when inside those strings. Returns True if we're still
 143         inside a multi-line string when this function is finished.
 144         """
 145         # If inside triple-single quotes, start at 0
 146         if self.previousBlockState() == in_state:
 147             start = 0
 148             add = 0
 149         # Otherwise, look for the delimiter on this line
 150         else:
 151             start = delimiter.indexIn(text)
 152             # Move past this match
 153             add = delimiter.matchedLength()
 154 
 155         # As long as there's a delimiter match on this line...
 156         while start >= 0:
 157             # Look for the ending delimiter
 158             end = delimiter.indexIn(text, start + add)
 159             # Ending delimiter on this line?
 160             if end >= add:
 161                 length = end - start + add + delimiter.matchedLength()
 162                 self.setCurrentBlockState(0)
 163             # No; multi-line string
 164             else:
 165                 self.setCurrentBlockState(in_state)
 166                 length = text.length() - start + add
 167             # Apply formatting
 168             self.setFormat(start, length, style)
 169             # Look for the next match
 170             start = delimiter.indexIn(text, start + length)
 171 
 172         # Return True if still inside a multi-line string, False otherwise
 173         if self.currentBlockState() == in_state:
 174             return True
 175         else:
 176             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:

   1 # editor.py
   2 
   3 from PyQt4 import QtGui
   4 import syntax
   5 
   6 app = QtGui.QApplication([])
   7 editor = QtGui.QPlainTextEdit()
   8 highlight = syntax.PythonHighlighter(editor.document())
   9 editor.show()
  10 
  11 # Load syntax.py into the editor for demo purposes
  12 infile = open('syntax.py', 'r')
  13 editor.setPlainText(infile.read())
  14 
  15 app.exec_()

PyQt/Python syntax highlighting (last edited 2017-01-19 20:34:17 by DavidBoddie)

Unable to edit the page? See the FrontPage for instructions.