Skip to main content

QCodeSitter

A reusable, tree-sitter-powered code editor widget for Qt applications. QCodeSitter provides a QPlainTextEdit subclass with syntax highlighting, multi-cursor editing, smart indentation, code folding, and more -- all driven by a modular plugin system that makes it easy to add, remove, or customize features.

This is not an IDE. It's a drop-in editor widget you can embed in your own Qt application.

Quick Start

The quickest way to a full editor is the default setup:

from Qt import QtWidgets

from QCodeSitter import CodeEditor, default_options, install_default_behaviors

app = QtWidgets.QApplication([])
editor = CodeEditor(default_options())
install_default_behaviors(editor)
editor.show()
app.exec()

install_default_behaviors(editor, optional=True) also adds the behaviors that are off by default, like AutoBracket.

Or pick the options and behaviors yourself:

from tree_sitter import Language
import tree_sitter_python as tspython
from Qt import QtGui

from QCodeSitter import CodeEditor, EditorOptions
from QCodeSitter.themes import DARK_THEME, read_theme
from QCodeSitter.behaviors.syntax_highlighting import SyntaxHighlighting
from QCodeSitter.behaviors.smart_indent import SmartIndent
from QCodeSitter.behaviors.line_numbers import LineNumbers
from QCodeSitter.behaviors.auto_bracket import AutoBracket

language = Language(tspython.language())
colors, syntax = read_theme(DARK_THEME)
options = EditorOptions({
    "space_indent_width": 4,
    "indent_using_tabs": False,
    "language": language,
    "syntax": syntax,
    "colors": colors,
    "font": QtGui.QFont("Consolas", pointSize=11),
})

editor = CodeEditor(options)
editor.add_behavior(SyntaxHighlighting)
editor.add_behavior(SmartIndent)
editor.add_behavior(LineNumbers)
editor.add_behavior(AutoBracket)

You only pay for what you use. If you don't need code folding, don't add it. If you want custom behavior, write your own.

Every option is listed in OptionsDict in editor_options.py, and the options you don't give take their value from OPTION_DEFAULTS. Setting an option that isn't listed raises a KeyError, and a value of the wrong type raises a TypeError, so mistakes don't silently do nothing. The highlight query comes from the language, so changing "language" is all it takes to highlight another language. "syntax" only holds the format of each capture.

The "colors" option holds the gui colors, listed with their defaults in gui_colors.py.

Setting an option to the value it already has does nothing, so change a list option by setting a new list rather than editing it in place. A behavior from outside the package adds its own options with register_option("my_option", int, default=3).

The Behavior System

Every editor feature beyond basic text editing is implemented as a Behavior. Behaviors are essentially just plugins. They are modular, self-contained classes that plug into the editor through a well-defined interface.

Base Class

All behaviors inherit from Behavior:

from QCodeSitter.behaviors import Behavior

class MyBehavior(Behavior):
    def __init__(self, editor):
        super().__init__(editor)
        # self.editor  - the CodeEditor instance
        # self.options  - the EditorOptions dict-like object
        # Signals connected with self.connect are disconnected when it's removed
        self.connect(editor.textChanged, self.on_text_changed)

    def on_text_changed(self):
        ...

    def on_remove(self):
        # Undo anything else done in __init__. This runs after the
        # signals connected with self.connect are disconnected

Behaviors are added and removed at runtime:

behavior = editor.add_behavior(MyBehavior)  # Replaces any existing MyBehavior
editor.remove_behavior(MyBehavior)          # Returns the removed instance
editor.get_behavior(MyBehavior)             # Returns the instance or None

Reacting to Option Changes

Every option except "language" and "font" always has a value, so a behavior can read a setting from self.options when it needs it. For a setting that needs work when it changes, register a handler. Handlers are called with the option's value right away, and again whenever it changes. The handlers of "language" and "font" wait until they are set:

class MyBehavior(Behavior):
    def __init__(self, editor):
        super().__init__(editor)
        self.font = QtGui.QFont()  # The default until the option is set
        self.watch_options({"font": self.set_font})

    def set_font(self, font):
        self.font = font

Parsing and highlighting are the expensive part of an editor, so the editor only sets its language when it is first shown. Call editor.load() to set it for an editor you don't show.

Keys, Painting and Signals

  • Override handle_key_press(event) to get key presses before the editor's own key handling, and return True from it to consume the key. Behaviors with a higher KEY_PRIORITY get the keys first, like TabCompletion, so Tab accepts a completion before SmartIndent indents.
  • Return actions from get_hotkeys to add keyboard shortcuts to the editor.
  • Connect to editor.behaviorPaint(event, painter) to paint over the text.
  • Connect to editor.cursors.changed, editor.tree_manager.reparsed, or editor.undoRedoFinished to react to the editor.
  • Subclass GutterWidget (in gutters.py) for a column beside the text, like line numbers.

Languages

Language-specific knowledge (comment prefix, indent rules, foldable node types, and the highlight and identifier queries) lives in a LanguageSpec, looked up from the editor's tree-sitter language. Add a language by registering a spec for its grammar name:

from QCodeSitter.languages import LanguageSpec, register_language

register_language(LanguageSpec("lua", comment_prefix="-- "))

Built-in Behaviors

Behavior Description
SyntaxHighlighting Tree-sitter-based syntax highlighting with incremental updates
SmartIndent Language-aware auto-indentation on Enter, Tab, Backspace
LineNumbers Line number gutter
AutoBracket Auto-close brackets, quotes, and parens; selection wrapping
TabCompletion Autocomplete popup with pluggable providers
CodeFolding Tree-sitter-based code folding with gutter indicators
CommentToggle Toggle line comments (Ctrl+/) with language detection
HighlightMatchingBrackets Highlight matching bracket/paren/brace pairs
HighlightMatchingSelection Highlight all occurrences of selected text
MultiCursorPaint Render primary and secondary cursors
Overscroll Allow scrolling past end of document

Multi-Cursor Editing

The editor has built-in multi-cursor support. All text operations (insert, delete, indent, comment, etc.) apply to every cursor simultaneously.

Default bindings:

  • Ctrl+D -- Select next occurrence of current selection
  • Ctrl+Alt+Up/Down -- Add cursor above/below
  • Ctrl+Shift+L -- Add cursors to all line ends in selection
  • Alt+Click -- Add cursor at mouse position
  • Escape -- Exit multi-cursor mode

Behaviors that modify text across multiple cursors use the editor's CursorIterator. It moves one cursor to each cursor in turn, and shifts the later cursors by however much each edit changed the document:

def my_multi_cursor_operation(self):
    citer = self.editor.citer
    with self.editor.edit_session():  # One undo step, and one reparse
        for cursor in citer.iterate_cursors(complete_all=True):
            cursor.insertText("hello")

complete_all=True marks every cursor completed, which stops later key handlers in the same edit session from editing them. A pass that handles only some cursors calls citer.mark_cursor_completed() inside the loop for each one it handled instead.

Tree-Sitter Integration

QCodeSitter uses tree-sitter for parsing, with UTF-16 encoding throughout to match Qt's internal string representation. The TreeManager provides:

  • Incremental parsing (only re-parses changed regions)
  • A reparsed signal for loose coupling between components
  • Pause/unpause to batch edits (e.g., during multi-cursor operations)

The TrackedDocument (a QTextDocument subclass) and TrackedCursor (a QTextCursor subclass) automatically track UTF-16 byte offsets and emit change signals that keep the parse tree in sync.

Dependencies

  • Python 3.10+

  • Qt.py, and a Qt binding for it. The tests run on PySide6 (tox) and PyQt6 (tox -e pyqt6); PySide2 and PyQt5 are untested. pip install QCodeSitter[pyside6] installs PySide6

  • tree-sitter

  • A tree-sitter language grammar (e.g., tree-sitter-python)

Running Tests

Tests are set up to be run with tox, and tox -e fuzz runs the slower Hypothesis fuzz tests. python examples/demo.py opens a window with the default editor.

Metadata

Release files for QCodeSitter 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for QCodeSitter 0.2.0
File Size Uploaded Provenance Actions
qcodesitter-0.2.0.tar.gz 117.2 kB With provenance Details for qcodesitter-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for QCodeSitter 0.2.0
File Interpreter ABI Platform Actions
qcodesitter-0.2.0-py3-none-any.whl Python 3 none any Details for qcodesitter-0.2.0-py3-none-any.whl

Total release size: 205.5 kB

Release files / qcodesitter-0.2.0.tar.gz

Download URL qcodesitter-0.2.0.tar.gz
Size 117.2 kB
Tags Source
SHA-256 checksum
How to use checksums
887c0747af504b2026aeeafdd605451fa466106c356d1b7209b400c74d3d1158
BLAKE2b-256 checksum
How to use checksums
d4f2a8115322d52bade3806f711a5d5aeb7de34266886325607acb58c2b61592
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Release files / qcodesitter-0.2.0-py3-none-any.whl

Download URL qcodesitter-0.2.0-py3-none-any.whl
Size 88.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
456c33078cbecfea6a405a42f20243ab90bac38126117c106a58965693f34189
BLAKE2b-256 checksum
How to use checksums
86d720339ca30f049f90be5a422960745deb5b10d8c6508ad9467987b916481e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Security for QCodeSitter 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for qcodesitter-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page