Skip to main content

flet-code-editor-dsl

A real Flutter code editor exposed as a Flet control, with a small Python DSL for defining syntax-highlighting rules and bracket/quote pair matching.

Python Flet Version License


Table of Contents

  1. Overview
  2. Features
  3. Requirements
  4. Installation
  5. Quick Start
  6. API Reference
  7. How the Highlighting Engine Works
  8. Controlling the Editor at Runtime
  9. Complete Example
  10. Building Your App
  11. Project Structure
  12. Known Limitations
  13. Troubleshooting
  14. Contributing
  15. License

Overview

flet-code-editor-dsl is a Flet extension that embeds a multi-line code editor built on Flutter's TextField. Instead of shipping a fixed set of language grammars, it provides a compact DSL (domain-specific language) in Python that lets you describe exactly how text should be colored:

  • Rules — regular expressions mapped to colors, with optional context conditions (before, after, ignore_if).
  • Pairs — matching delimiters (brackets, quotes, tags) with distinct colors for matched and unmatched occurrences.
  • Strict mode — any text not covered by a rule can be flagged as an error.

This makes it well suited for custom languages, configuration formats, templating syntaxes, and educational tools.

Features

  • Native Flutter rendering through the Flet extension mechanism.
  • Declarative highlighting rules defined entirely from Python.
  • Context-aware matching using look-behind (before), look-ahead (after) and exclusion (ignore_if) patterns.
  • Grouped rules: several patterns sharing a single color.
  • Bracket and quote pair matching, including nesting and unmatched-delimiter detection.
  • Strict mode that paints unrecognized text with an error color.
  • Configurable font family, font size and background color.
  • on_change event for reading the current text.
  • Inherits standard layout properties from ft.LayoutControl (width, height, expand, margin, etc.).

Requirements

Component Version
Python >= 3.10
Flet >= 0.80 (the Flutter side depends on flet: ^0.80.0)
Dart SDK >= 3.0.0 < 4.0.0
Flutter >= 3.10.0
Git Required for installing from GitHub

Note: Because this package contains a Flutter extension, it only takes effect when your app is packaged with flet build (or run with a Flet client that includes the extension). See Building Your App.


Installation

Option 1: Install directly from GitHub with pip

pip install "git+https://github.com/obgwew/flet-code-editor-dsl.git"

Or, using the explicit direct-reference form:

pip install "flet-code-editor-dsl @ git+https://github.com/obgwew/flet-code-editor-dsl.git"

Option 2: Declare it as a dependency in your Flet app

Add the package to the dependencies of your app's pyproject.toml:

[project]
name = "my-flet-app"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
  "flet",
  "flet-code-editor-dsl @ git+https://github.com/obgwew/flet-code-editor-dsl.git",
]

Then install and run as usual:

pip install -e .
flet run

If you use a requirements.txt instead:

flet
git+https://github.com/obgwew/flet-code-editor-dsl.git

Pinning a version

Append @<ref> to the URL, where <ref> is a branch, tag, or commit hash:

# Branch
pip install "git+https://github.com/obgwew/flet-code-editor-dsl.git@main"

# Tag
pip install "git+https://github.com/obgwew/flet-code-editor-dsl.git@v0.1.0"

# Commit
pip install "git+https://github.com/obgwew/flet-code-editor-dsl.git@<commit-sha>"

Pinning to a tag or commit is strongly recommended for reproducible builds.

Option 3: Local editable install

git clone https://github.com/obgwew/flet-code-editor-dsl.git
cd flet-code-editor-dsl
pip install -e .

Verifying the installation

python -c "import flet_code_editor_dsl as m; print(m.__all__)"

Expected output:

['CodeEditor', 'rule', 'group', 'pair', 'rules_json', 'pairs_json']

Quick Start

import flet as ft
import flet_code_editor_dsl as ce


def main(page: ft.Page):
    page.title = "Code Editor"

    editor = ce.CodeEditor(
        value="let x = (1 + 2)",
        rules=ce.rules_json([
            ce.group(["let", "if", "else"], color="#C586C0"),   # keywords
            ce.rule(r"\d+", color="#B5CEA8"),                    # numbers
        ]),
        pairs=ce.pairs_json([
            ce.pair("(", ")", color="#FFD700"),
        ]),
        default_color="#D4D4D4",
        background_color="#1E1E1E",
        expand=True,
    )

    page.add(editor)


ft.run(main)

On Flet versions that still use the older entry point, replace ft.run(main) with ft.app(main).


API Reference

All public names are importable from the top-level package:

from flet_code_editor_dsl import CodeEditor, rule, group, pair, rules_json, pairs_json

CodeEditor

@ft.control("flet_code_editor_dsl")
class CodeEditor(ft.LayoutControl): ...
Property Type Default Description
value str "" The text shown in the editor. Setting it from Python replaces the editor content.
rules str (JSON) "[]" Highlighting rules. Build it with rules_json([...]).
pairs str (JSON) "[]" Delimiter pairs. Build it with pairs_json([...]).
default_color str "#000000" Color for text not matched by any rule (when strict is False).
strict bool False If True, unmatched text is painted with error_color instead of default_color.
error_color str "#FF0000" Color used for unrecognized text in strict mode.
background_color ft.ColorValue | None None Editor background. When None, the Flutter side falls back to #202124.
font_family str "monospace" Font family used by the editor.
font_size float 14.0 Font size in logical pixels.
on_change ft.ControlEventHandler | None None Called whenever the text changes. The new text is available as e.data.

In addition, all ft.LayoutControl properties (such as width, height, expand, margin, visible, opacity) are supported.

Tip: The default default_color is black (#000000) while the default background is dark. Always set default_color explicitly (for example "#D4D4D4") when using a dark theme.

Color format

Colors are strings in one of these forms:

Format Example Meaning
#RRGGBB #FF8800 Opaque color (alpha FF is added automatically).
#AARRGGBB #80FF8800 Color with explicit alpha channel.

Invalid values fall back to a default color instead of raising an error.


rule()

Creates a single highlighting rule.

rule(
    form: str,
    color: str,
    before: Optional[str] = None,
    after: Optional[str] = None,
    ignore_if: Optional[str] = None,
) -> dict
Parameter Description
form Regular expression that selects the text to color.
color Color applied to every match.
before Regex that must match immediately before the match (it is anchored to the end of the preceding text). If it does not match, the occurrence is skipped.
after Regex that must match immediately after the match (anchored to the start of the following text).
ignore_if Regex tested at the start of the match. If it matches, the occurrence is skipped.
# Highlight identifiers that directly follow "def "
ce.rule(r"[A-Za-z_]\w*", color="#DCDCAA", before=r"def\s+")

# Highlight names followed by "(" as function calls
ce.rule(r"[A-Za-z_]\w*", color="#DCDCAA", after=r"\(")

# Highlight words, but skip the keyword "let"
ce.rule(r"[A-Za-z_]\w*", color="#9CDCFE", ignore_if=r"let\b")

group()

Creates a rule that applies one color to several patterns. The patterns are combined into a single alternation.

group(
    forms: list[str],
    color: str,
    before: Optional[str] = None,
    after: Optional[str] = None,
    ignore_if: Optional[str] = None,
) -> dict
ce.group([r"\bif\b", r"\belse\b", r"\bwhile\b", r"\breturn\b"], color="#C586C0")

pair()

Defines a pair of delimiters that are matched against each other.

pair(
    open: str,
    close: str,
    color: Optional[str] = None,
    unmatched_color: str = "#FF0000",
) -> dict
Parameter Description
open Opening delimiter (a literal string, not a regex).
close Closing delimiter (a literal string).
color Color for correctly matched delimiters. If None, matched delimiters keep the color assigned by rules.
unmatched_color Color for delimiters that have no partner. Defaults to red.

Behavior:

  • When open != close (e.g. ( and )), delimiters are matched using a stack, so nesting is supported. An unmatched closer, or an opener left on the stack, is painted with unmatched_color.
  • When open == close (e.g. " and "), occurrences are counted in order. If the total count is odd, the last one is treated as unmatched.
ce.pair("(", ")", color="#FFD700")
ce.pair("[", "]", color="#DA70D6")
ce.pair("{", "}", color="#179FFF")
ce.pair('"', '"', color="#CE9178", unmatched_color="#FF5555")

rules_json() and pairs_json()

Serialize a list of rules or pairs into the compact JSON string expected by CodeEditor.rules and CodeEditor.pairs.

rules_json(rules: Iterable[dict]) -> str
pairs_json(pairs: Iterable[dict]) -> str
editor.rules = ce.rules_json([ce.rule(r"\d+", "#B5CEA8")])
editor.pairs = ce.pairs_json([ce.pair("(", ")", "#FFD700")])

How the Highlighting Engine Works

Understanding the evaluation order helps you write predictable rules.

  1. Rule pass — Rules are applied in the order they appear in the list. Each match paints its characters with the rule's color. Later rules override earlier ones where matches overlap.
  2. Context checks — For every match, before, after and ignore_if are evaluated. If a check fails, that match is ignored.
  3. Fallback pass — Characters not covered by any rule receive default_color, or error_color if strict=True.
  4. Pair pass — Pair matching runs last and overrides the colors of delimiter characters. This ensures unmatched delimiters are always visible.

Practical consequences

  • Put general rules first (identifiers, words) and specific rules last (keywords, literals).
  • Regular expressions use the Dart RegExp engine (ECMAScript-style), not Python's re. Most common syntax is identical, but avoid Python-only constructs such as (?P<name>...) — use (?<name>...) instead.
  • Patterns that fail to compile are silently skipped, so test new patterns incrementally.
  • Use raw strings (r"...") in Python to avoid double escaping.

Controlling the Editor at Runtime

Reading the text

def on_change(e: ft.ControlEvent):
    print("Current text:", e.data)

editor = ce.CodeEditor(on_change=on_change)

Setting the text

editor.value = "new content"
editor.update()

Updating rules or pairs dynamically

editor.rules = ce.rules_json([
    ce.rule(r"\bTODO\b", "#FFCC00"),
])
editor.update()

Switching strict mode

editor.strict = True
editor.error_color = "#FF5555"
editor.update()

Changing appearance

editor.font_size = 16
editor.font_family = "Courier New"
editor.background_color = "#0D1117"
editor.update()

Layout

ce.CodeEditor(width=600, height=400)   # fixed size
ce.CodeEditor(expand=True)             # fill available space

Complete Example

A live-validating editor for a tiny expression language. Keywords, numbers, strings and comments are colored, brackets are matched, and anything unrecognized is flagged as an error.

import flet as ft
import flet_code_editor_dsl as ce

SAMPLE = '''\
# Example program
let total = (price * 3) + 12
if (total > 100) {
    print("expensive")
} else {
    print("cheap")
}
'''

RULES = ce.rules_json([
    # Identifiers (general rule first)
    ce.rule(r"[A-Za-z_]\w*", "#9CDCFE"),

    # Function calls: a name followed by "("
    ce.rule(r"[A-Za-z_]\w*", "#DCDCAA", after=r"\("),

    # Keywords
    ce.group([r"\blet\b", r"\bif\b", r"\belse\b"], "#C586C0"),

    # Numbers
    ce.rule(r"\b\d+(?:\.\d+)?\b", "#B5CEA8"),

    # Operators and whitespace (so strict mode does not flag them)
    ce.group([r"[+\-*/=<>!]+", r"\s+", r"[(){}\[\],;]"], "#D4D4D4"),

    # Strings
    ce.rule(r'"[^"\n]*"', "#CE9178"),

    # Comments (last, so they override everything on the line)
    ce.rule(r"#.*", "#6A9955"),
])

PAIRS = ce.pairs_json([
    ce.pair("(", ")", color="#FFD700"),
    ce.pair("{", "}", color="#179FFF"),
    ce.pair("[", "]", color="#DA70D6"),
])


def main(page: ft.Page):
    page.title = "flet-code-editor-dsl demo"
    page.theme_mode = ft.ThemeMode.DARK

    status = ft.Text("Characters: %d" % len(SAMPLE))

    def on_change(e: ft.ControlEvent):
        status.value = "Characters: %d" % len(e.data or "")
        status.update()

    def toggle_strict(e: ft.ControlEvent):
        editor.strict = e.control.value
        editor.update()

    editor = ce.CodeEditor(
        value=SAMPLE,
        rules=RULES,
        pairs=PAIRS,
        default_color="#D4D4D4",
        strict=False,
        error_color="#F44747",
        background_color="#1E1E1E",
        font_family="monospace",
        font_size=15,
        expand=True,
        on_change=on_change,
    )

    page.add(
        ft.Row([ft.Switch(label="Strict mode", on_change=toggle_strict), status]),
        editor,
    )


ft.run(main)

Building Your App

Flutter extensions are compiled into the Flet client, so you must build a custom client for your target platform. Make sure the package is listed in your app's pyproject.toml dependencies (see Installation), then run:

# Desktop
flet build windows
flet build macos
flet build linux

# Mobile
flet build apk
flet build ipa

# Web
flet build web

flet build detects the Flutter package bundled inside flutter/flet_code_editor_dsl/ and adds it to the generated Flutter project automatically. A working Flutter SDK is required; Flet will download one if it is missing.


Project Structure

flet-code-editor-dsl/
├── pyproject.toml
└── src/
    ├── flet_code_editor_dsl/
    │   └── __init__.py                     # Python API: CodeEditor + DSL helpers
    └── flutter/
        ├── __init__.py
        └── flet_code_editor_dsl/
            ├── __init__.py
            ├── pubspec.yaml                # Flutter package definition
            └── lib/
                ├── flet_code_editor_dsl.dart
                └── src/
                    ├── extension.dart      # Registers the control with Flet
                    └── code_editor.dart    # Editor widget + highlighting engine
File Responsibility
src/flet_code_editor_dsl/__init__.py Declares CodeEditor and the rule / group / pair helpers.
.../lib/src/extension.dart Maps the control type flet_code_editor_dsl to the Flutter widget.
.../lib/src/code_editor.dart Custom TextEditingController that builds colored TextSpans from rules and pairs.
pyproject.toml Packaging metadata; includes pubspec.yaml and lib/**/* as package data.

Known Limitations

  • No language awareness in pairs. Delimiters inside strings or comments are still matched. Use ignore_if or rule ordering to reduce false positives, or avoid pairing characters that appear inside literals.
  • Highlighting is recomputed on each rebuild. Very large documents with many rules may reduce typing performance.
  • Plain text editing only. Line numbers, code folding, auto-indent and autocompletion are not included.
  • Dart regex semantics. Patterns must be valid for Dart's RegExp; invalid patterns are ignored without an error message.
  • Client rebuild required. As with all Flet extensions, the control is not available in the stock Flet client.
  • Minimum height. The editor shows at least 12 lines.

Troubleshooting

Symptom Likely cause and fix
The editor does not appear, or an "unknown control" error is shown The app is running with a stock Flet client. Build with flet build, or ensure the extension is registered in your custom client.
Text is hard to see default_color defaults to black while the background is dark. Set default_color (for example #D4D4D4) or change background_color.
A rule has no effect Its regex may be invalid for Dart, or a later rule overrides it. Simplify the pattern and check rule order.
Everything is red strict=True is set and some text is not covered by any rule. Add rules for whitespace, operators and punctuation, or disable strict mode.
pip install from GitHub fails Confirm Git is installed, the URL is correct, and (for private repositories) that you are authenticated via SSH or a personal access token.
Changes made from Python are not visible Call editor.update() after modifying a property.

For private repositories, authenticate with a token:

pip install "git+https://<TOKEN>@github.com/obgwew/flet-code-editor-dsl.git"

Contributing

Contributions are welcome.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/my-feature.
  3. Commit your changes with clear messages.
  4. Push the branch and open a Pull Request describing the change and its motivation.

Please open an issue first for larger changes or new features so the design can be discussed.

License

This project is licensed under the Apache License, Version 2.0. See the LICENSE file for the full text.

Copyright 2026 obgwew

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

By contributing to this repository, you agree that your contributions will be licensed under the same Apache 2.0 license.

Metadata

Release files for flet-code-editor-dsl 0.1.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 flet-code-editor-dsl 0.1.0
File Size Uploaded
flet_code_editor_dsl-0.1.0.tar.gz 19.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for flet-code-editor-dsl 0.1.0
File Interpreter ABI Platform
flet_code_editor_dsl-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 33.6 kB

Release files / flet_code_editor_dsl-0.1.0.tar.gz

Download URL flet_code_editor_dsl-0.1.0.tar.gz
Size 19.6 kB
Tags Source
SHA-256 checksum
How to use checksums
308b133cc6bdd4ec6e9ba1b2a14d99171aae4419ebaf3591c8720b8dea7ddd08
BLAKE2b-256 checksum
How to use checksums
dbc0fbc2de119251c3d71550c5d9ad5d7712ef687cef75851277055c8754ae78
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release files / flet_code_editor_dsl-0.1.0-py3-none-any.whl

Download URL flet_code_editor_dsl-0.1.0-py3-none-any.whl
Size 13.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9fc063c19814c58d2b6d26e2a9f8885f9cb29192e554c27a5049f2e6583bba53
BLAKE2b-256 checksum
How to use checksums
c5af3ad164aff4dc0296f2bec53082245269ac18a607a033ff6eab7e8a9ff64f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release history Release notifications | RSS feed

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

This release

0.1.0 This release

2 release files

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