Tags Python , Qt

QScintilla is (a port to Qt of the Scintilla source code editor control) comes packaged with PyQt. Unfortunately, its documentation is quite scarce and it's hard to find examples online.

So I developed a small sample editor with PyQt using QScintilla, which can serve as a starting point for more complex uses.

Here's a screenshot:

The sample demonstrates some interesting features of QScintilla:

  • Syntax highlighting, and customizing how some of its styles look
  • Using the margin to show line numbers
  • Clickable markers on the margin (can be used for breakpoints in a debugger, code bookmarks, error marks, or any other purpose)
  • Enabling brace matching
  • Setting a background color for the current line

Here's the complete code:

#-------------------------------------------------------------------------
# qsci_simple_pythoneditor.pyw
#
# QScintilla sample with PyQt
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.Qsci import QsciScintilla, QsciLexerPython


class SimplePythonEditor(QsciScintilla):
    ARROW_MARKER_NUM = 8

    def __init__(self, parent=None):
        super(SimplePythonEditor, self).__init__(parent)

        # Set the default font
        font = QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.setMarginsFont(font)

        # Margin 0 is used for line numbers
        fontmetrics = QFontMetrics(font)
        self.setMarginsFont(font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 6)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QColor("#cccccc"))

        # Clickable margin 1 for showing markers
        self.setMarginSensitivity(1, True)
        self.connect(self,
            SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
            self.on_margin_clicked)
        self.markerDefine(QsciScintilla.RightArrow,
            self.ARROW_MARKER_NUM)
        self.setMarkerBackgroundColor(QColor("#ee1111"),
            self.ARROW_MARKER_NUM)

        # Brace matching: enable for a brace immediately before or after
        # the current position
        #
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Current line visible with special background color
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QColor("#ffe4e4"))

        # Set Python lexer
        # Set style for Python comments (style number 1) to a fixed-width
        # courier.
        #
        lexer = QsciLexerPython()
        lexer.setDefaultFont(font)
        self.setLexer(lexer)
        self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')

        # Don't want to see the horizontal scrollbar at all
        # Use raw message to Scintilla here (all messages are documented
        # here: http://www.scintilla.org/ScintillaDoc.html)
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)

        # not too small
        self.setMinimumSize(600, 450)

    def on_margin_clicked(self, nmargin, nline, modifiers):
        # Toggle marker for the line the margin was clicked on
        if self.markersAtLine(nline) != 0:
            self.markerDelete(nline, self.ARROW_MARKER_NUM)
        else:
            self.markerAdd(nline, self.ARROW_MARKER_NUM)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    editor = SimplePythonEditor()
    editor.show()
    editor.setText(open(sys.argv[0]).read())
    app.exec_()

As I said, the documentation for QScintilla is far from being abundant. I used the following sources to develop the sample:

  • QScintilla class hierarchy - this auto-generated documentation is the closest you get to a reference for the module
  • Scintilla itself is a control that communicates with the outer world via messages. The QsciScintilla.SendScintilla method allows to send the messages, which are quite well documented in the low-level Scintilla Documentation.

Note that I'm using the QsciLexerPython lexer for Python source code highlighting. Scintilla supports lexers for many languages (and others can be easily supported by playing with the settings of existing lexers for similar languages). For a complete list of lexers supported, run:

import PyQt4.Qsci

for sym in dir(PyQt4.Qsci):
    if sym.startswith('QsciLexer'):
        print sym

Update (15-May-2017): A new documentation site for the use of QScintilla with Python is available here.