CodeEditor#

A full-featured code editor with syntax highlighting, line numbers, bracket matching, smart indent, and built-in search/replace.

CodeEditor — light theme CodeEditor — dark theme

Usage#

Basic usage#

bs.CodeEditor()

Syntax highlighting#

Pass language= to enable Pygments syntax highlighting. Any Pygments lexer name is accepted.

bs.CodeEditor(language="python")
bs.CodeEditor(language="sql")
bs.CodeEditor(language="javascript")
bs.CodeEditor(language="html")
CodeEditor languages — light theme CodeEditor languages — dark theme

Color themes#

By default (theme='auto'), the editor switches between light_theme and dark_theme to match the active bootstack theme. Pass an explicit Pygments style name to pin the scheme.

bs.CodeEditor(language="python", theme="auto")           # follows app theme
bs.CodeEditor(language="python", theme="monokai")        # always dark
bs.CodeEditor(language="python", theme="default")        # always light
bs.CodeEditor(language="python",
              light_theme="friendly",
              dark_theme="dracula")                      # custom pair

Read-only state#

bs.CodeEditor(value=code, language="python", read_only=True)
CodeEditor states — light theme CodeEditor states — dark theme

Editor options#

bs.CodeEditor(
    language="python",
    tab_width=4,           # spaces per tab stop (default 4)
    insert_spaces=True,    # Tab inserts spaces (default True)
    auto_indent=True,      # match indentation on Return (default True)
    show_line_numbers=True,  # line-number gutter (default True)
    show_indent_guides=False,  # vertical guide marks (default False)
    wrap=False,            # horizontal scroll (default; True to wrap)
)

Handling changes#

on_change() fires on every edit. Use on_input() for keystroke-level feedback.

editor = bs.CodeEditor(language="python")

editor.on_change(lambda e: autosave(editor.value))

# Debounced Stream — save 500ms after the last keystroke
editor.on_change().debounce(500).listen(lambda e: autosave(editor.value))

Cursor position#

on_cursor_move() fires after any key press or mouse click that moves the insertion cursor.

editor = bs.CodeEditor(language="python")

def show_pos(e):
    idx = editor._internal._core.text.index("insert")
    line, col = idx.split(".")
    status.text = f"Ln {line}, Col {int(col) + 1}"

editor.on_cursor_move(show_pos)

Undo and redo#

The editor maintains a built-in undo/redo stack. Use undo_block() to group multiple programmatic edits into a single undo step.

editor = bs.CodeEditor(language="python")
editor.undo()
editor.redo()

# Group edits into one undo step
with editor.undo_block():
    editor.insert("1.0", "# Auto-generated\\n")
    editor.value = reformatted

Dirty tracking#

is_dirty is True after any edit since the last mark_saved() call.

editor = bs.CodeEditor(language="python")
editor.on_modified(lambda e: update_title(editor.is_dirty))

# After saving
editor.mark_saved()

Search and replace#

editor.show_search()   # open find bar
editor.show_replace()  # open find/replace bar
editor.hide_search()   # close the bar

Widget sizing#

All widgets accept self-placement kwargs via **kwargs. The parent container determines which options apply — stack-based parents use stack kwargs, grid-based parents use grid kwargs. Unrecognised keys are silently ignored.

Stack#

Used inside VStack, HStack, App, and other stack containers.

fill

Fill direction: 'x', 'y', 'both', or 'none'.

expand

Grow to consume extra space in the parent. True or False.

anchor

Alignment when the widget does not fill the available slot: 'n', 's', 'e', 'w', 'center', 'nw', etc.

margin

External spacing in pixels. Accepts an integer (equal on all sides), a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).

margin_x

Horizontal external spacing (left and right). Accepts an integer or a 2-tuple (left, right) for asymmetric spacing. Overrides the horizontal component of margin=.

margin_y

Vertical external spacing (top and bottom). Accepts an integer or a 2-tuple (top, bottom) for asymmetric spacing. Overrides the vertical component of margin=.

Grid#

Used inside a Grid container.

row / column

Zero-based row and column indices.

rowspan / columnspan

Number of rows or columns to span.

sticky

Alignment and fill within the grid cell. Any combination of 'n', 's', 'e', 'w' — e.g. 'ew' stretches horizontally, 'nsew' fills the entire cell.

margin

External spacing in pixels. Accepts an integer, a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).

margin_x

Horizontal external spacing. Accepts an integer or (left, right).

margin_y

Vertical external spacing. Accepts an integer or (top, bottom).

See also#

  • TextArea — plain multi-line text input for form fields

API#

The complete reference for CodeEditor lives on the Widgets API page. At a glance:

CodeEditor

A full-featured code editor with line numbers, bracket matching, and syntax highlighting.

Full Example#

 1
 2SAMPLE_PY = """\
 3import bootstack as bs
 4
 5with bs.App(title="My App", padding=16, gap=12) as app:
 6    bs.Label("Hello, bootstack!", font="heading-lg")
 7    name = bs.TextField(label="Your name", placeholder="Enter name…")
 8    accent = bs.Select(["primary", "secondary", "success"], label="Accent")
 9    with bs.HStack(gap=8):
10        bs.Button("Submit", accent="primary")
11        bs.Button("Cancel", variant="outline")
12app.run()
13"""
14
15SAMPLE_SQL = """\
16SELECT
17    u.name,
18    u.email,
19    COUNT(o.id)   AS order_count,
20    SUM(o.total)  AS lifetime_value
21FROM users u
22LEFT JOIN orders o
23       ON o.user_id = u.id
24      AND o.status  = 'completed'
25WHERE u.created_at >= '2024-01-01'
26GROUP BY u.id, u.name, u.email
27ORDER BY lifetime_value DESC
28LIMIT 50;
29"""
30
31with bs.App(title="CodeEditor Demo", size=(800, 700)) as app:
32    with bs.ScrollView(fill="both", expand=True):
33        with bs.VStack(padding=20, gap=16, fill="x"):
34
35            # Python syntax highlighting
36            bs.Label("Python", font="heading-sm")
37            bs.CodeEditor(value=SAMPLE_PY, language="python", height=7, fill="x")
38
39            # SQL syntax highlighting
40            bs.Label("SQL", font="heading-sm")
41            bs.CodeEditor(value=SAMPLE_SQL, language="sql", height=7, fill="x")
42
43            # No highlighting
44            bs.Label("Plain text (no language)", font="heading-sm")
45            bs.CodeEditor(value="Hello, World!\nNo syntax highlighting.", height=3, fill="x")
46
47            # Read only
48            bs.Label("Read Only", font="heading-sm")
49            bs.CodeEditor(value=SAMPLE_PY, language="python", height=6, fill="x", read_only=True)
50
51            # Undo / redo + dirty tracking
52            bs.Label("Undo / Redo + Dirty Tracking", font="heading-sm")
53            dirty_sig = bs.Signal("not modified")
54            editor = bs.CodeEditor(value=SAMPLE_PY, language="python", height=6, fill="x")
55            editor.on_modified(lambda e: dirty_sig.set("modified" if editor.is_dirty else "not modified"))
56            with bs.HStack(gap=8):
57                bs.Button("Undo",       on_click=lambda: editor.undo())
58                bs.Button("Redo",       on_click=lambda: editor.redo())
59                bs.Button("Mark saved", on_click=lambda: editor.mark_saved())
60                bs.Label(textsignal=dirty_sig, accent="secondary")
61
62            # Search / replace
63            bs.Label("Search / Replace", font="heading-sm")
64            ed2 = bs.CodeEditor(value=SAMPLE_PY, language="python", height=7, fill="x")
65            with bs.HStack(gap=8):
66                bs.Button("Find",    on_click=lambda: ed2.show_search())
67                bs.Button("Replace", on_click=lambda: ed2.show_replace())
68
69            # Cursor position
70            bs.Label("Cursor Move Event", font="heading-sm")
71            pos_sig = bs.Signal("line ?, col ?")
72            ed3 = bs.CodeEditor(value=SAMPLE_PY, language="python", height=6, fill="x")
73
74            def _update_pos(e):
75                idx = ed3._internal._core.text.index("insert")
76                line, col = idx.split(".")
77                pos_sig.set(f"line {line}, col {int(col) + 1}")
78
79            ed3.on_cursor_move(_update_pos)
80            bs.Label(textsignal=pos_sig, accent="secondary")
81
82app.run()