CodeEditor#
A full-featured code editor with syntax highlighting, line numbers, bracket matching, smart indent, and built-in search/replace.
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")
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)
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 direction: |
|
Grow to consume extra space in the parent. |
|
Alignment when the widget does not fill the available slot:
|
|
External spacing in pixels. Accepts an integer (equal on all
sides), a 2-tuple |
|
Horizontal external spacing (left and right). Accepts an integer
or a 2-tuple |
|
Vertical external spacing (top and bottom). Accepts an integer
or a 2-tuple |
Grid#
Used inside a Grid container.
|
Zero-based row and column indices. |
|
Number of rows or columns to span. |
|
Alignment and fill within the grid cell. Any combination of
|
|
External spacing in pixels. Accepts an integer, a 2-tuple
|
|
Horizontal external spacing. Accepts an integer or |
|
Vertical external spacing. Accepts an integer or |
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:
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()