File: text_area.md

package info (click to toggle)
textual 2.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 55,056 kB
  • sloc: python: 85,423; lisp: 1,669; makefile: 101
file content (567 lines) | stat: -rw-r--r-- 24,983 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# TextArea

!!! tip

    Added in version 0.38.0. Soft wrapping added in version 0.48.0.

A widget for editing text which may span multiple lines.
Supports text selection, soft wrapping, optional syntax highlighting with tree-sitter
and a variety of keybindings.

- [x] Focusable
- [ ] Container

## Guide

### Code editing vs plain text editing

By default, the `TextArea` widget is a standard multi-line input box with soft-wrapping enabled.

If you're interested in editing code, you may wish to use the [`TextArea.code_editor`][textual.widgets._text_area.TextArea.code_editor] convenience constructor.
This is a method which, by default, returns a new `TextArea` with soft-wrapping disabled, line numbers enabled, and the tab key behavior configured to insert `\t`.

### Syntax highlighting dependencies

To enable syntax highlighting, you'll need to install the `syntax` extra dependencies:

=== "pip"

    ```
    pip install "textual[syntax]"
    ```

=== "poetry"

    ```
    poetry add "textual[syntax]"
    ```

This will install `tree-sitter` and `tree-sitter-languages`.
These packages are distributed as binary wheels, so it may limit your applications ability to run in environments where these wheels are not available.
After installing, you can set the [`language`][textual.widgets._text_area.TextArea.language] reactive attribute on the `TextArea` to enable highlighting.

### Loading text

In this example we load some initial text into the `TextArea`, and set the language to `"python"` to enable syntax highlighting.

=== "Output"

    ```{.textual path="docs/examples/widgets/text_area_example.py" columns="42" lines="8"}
    ```

=== "text_area_example.py"

    ```python
    --8<-- "docs/examples/widgets/text_area_example.py"
    ```

To update the content programmatically, set the [`text`][textual.widgets._text_area.TextArea.text] property to a string value.

To update the parser used for syntax highlighting, set the [`language`][textual.widgets._text_area.TextArea.language] reactive attribute:

```python
# Set the language to Markdown
text_area.language = "markdown"
```

!!! note
    More built-in languages will be added in the future. For now, you can [add your own](#adding-support-for-custom-languages).

### Reading content from `TextArea`

There are a number of ways to retrieve content from the `TextArea`:

- The [`TextArea.text`][textual.widgets._text_area.TextArea.text] property returns all content in the text area as a string.
- The [`TextArea.selected_text`][textual.widgets._text_area.TextArea.selected_text] property returns the text corresponding to the current selection.
- The [`TextArea.get_text_range`][textual.widgets._text_area.TextArea.get_text_range] method returns the text between two locations.

In all cases, when multiple lines of text are retrieved, the [document line separator](#line-separators) will be used.

### Editing content inside `TextArea`

The content of the `TextArea` can be updated using the [`replace`][textual.widgets._text_area.TextArea.replace] method.
This method is the programmatic equivalent of selecting some text and then pasting.

Some other convenient methods are available, such as [`insert`][textual.widgets._text_area.TextArea.insert], [`delete`][textual.widgets._text_area.TextArea.delete], and [`clear`][textual.widgets._text_area.TextArea.clear].

!!! tip
    The `TextArea.document.end` property returns the location at the end of the
    document, which might be convenient when editing programmatically.

### Working with the cursor

#### Moving the cursor

The cursor location is available via the [`cursor_location`][textual.widgets._text_area.TextArea.cursor_location] property, which represents
the location of the cursor as a tuple `(row_index, column_index)`. These indices are zero-based and represent the position of the cursor in the content.
Writing a new value to `cursor_location` will immediately update the location of the cursor.

```python
>>> text_area = TextArea()
>>> text_area.cursor_location
(0, 0)
>>> text_area.cursor_location = (0, 4)
>>> text_area.cursor_location
(0, 4)
```

`cursor_location` is a simple way to move the cursor programmatically, but it doesn't let us select text.

#### Selecting text

To select text, we can use the `selection` reactive attribute.
Let's select the first two lines of text in a document by adding `text_area.selection = Selection(start=(0, 0), end=(2, 0))` to our code:

=== "Output"

    ```{.textual path="docs/examples/widgets/text_area_selection.py" columns="42" lines="8"}
    ```

=== "text_area_selection.py"

    ```python hl_lines="17"
    --8<-- "docs/examples/widgets/text_area_selection.py"
    ```

    1. Selects the first two lines of text.

Note that selections can happen in both directions, so `Selection((2, 0), (0, 0))` is also valid.

!!! tip

    The `end` attribute of the `selection` is always equal to `TextArea.cursor_location`. In other words,
    the `cursor_location` attribute is simply a convenience for accessing `text_area.selection.end`.

#### More cursor utilities

There are a number of additional utility methods available for interacting with the cursor.

##### Location information

Many properties exist on `TextArea` which give information about the current cursor location.
These properties begin with `cursor_at_`, and return booleans.
For example, [`cursor_at_start_of_line`][textual.widgets._text_area.TextArea.cursor_at_start_of_line] tells us if the cursor is at a start of line.

We can also check the location the cursor _would_ arrive at if we were to move it.
For example, [`get_cursor_right_location`][textual.widgets._text_area.TextArea.get_cursor_right_location] returns the location
the cursor would move to if it were to move right.
A number of similar methods exist, with names like `get_cursor_*_location`.

##### Cursor movement methods

The [`move_cursor`][textual.widgets._text_area.TextArea.move_cursor] method allows you to move the cursor to a new location while selecting
text, or move the cursor and scroll to keep it centered.

```python
# Move the cursor from its current location to row index 4,
# column index 8, while selecting all the text between.
text_area.move_cursor((4, 8), select=True)
```

The [`move_cursor_relative`][textual.widgets._text_area.TextArea.move_cursor_relative] method offers a very similar interface, but moves the cursor relative
to its current location.

##### Common selections

There are some methods available which make common selections easier:

- [`select_line`][textual.widgets._text_area.TextArea.select_line] selects a line by index. Bound to ++f6++ by default.
- [`select_all`][textual.widgets._text_area.TextArea.select_all] selects all text. Bound to ++f7++ by default.

### Themes

`TextArea` ships with some builtin themes, and you can easily add your own.

Themes give you control over the look and feel, including syntax highlighting,
the cursor, selection, gutter, and more.

#### Default theme

The default `TextArea` theme is called `css`, which takes its values entirely from CSS.
This means that the default appearance of the widget fits nicely into a standard Textual application,
and looks right on both dark and light mode.

When using the `css` theme, you can make use of [component classes][textual.widgets.TextArea.COMPONENT_CLASSES] to style elements of the `TextArea`.
For example, the CSS code `TextArea .text-area--cursor { background: green; }` will make the cursor `green`.

More complex applications such as code editors may want to use pre-defined themes such as `monokai`.
This involves using a `TextAreaTheme` object, which we cover in detail below.
This allows full customization of the `TextArea`, including syntax highlighting, at the code level.

#### Using builtin themes

The initial theme of the `TextArea` is determined by the `theme` parameter.

```python
# Create a TextArea with the 'dracula' theme.
yield TextArea.code_editor("print(123)", language="python", theme="dracula")
```

You can check which themes are available using the [`available_themes`][textual.widgets._text_area.TextArea.available_themes] property.

```python
>>> text_area = TextArea()
>>> print(text_area.available_themes)
{'css', 'dracula', 'github_light', 'monokai', 'vscode_dark'}
```

After creating a `TextArea`, you can change the theme by setting the [`theme`][textual.widgets._text_area.TextArea.theme]
attribute to one of the available themes.

```python
text_area.theme = "vscode_dark"
```

On setting this attribute the `TextArea` will immediately refresh to display the updated theme.

#### Custom themes

!!! note

    Custom themes are only relevant for people who are looking to customize syntax highlighting.
    If you're only editing plain text, and wish to recolor aspects of the `TextArea`, you should use the [provided component classes][textual.widgets.TextArea.COMPONENT_CLASSES].

Using custom (non-builtin) themes is a two-step process:

1. Create an instance of [`TextAreaTheme`][textual.widgets.text_area.TextAreaTheme].
2. Register it using [`TextArea.register_theme`][textual.widgets._text_area.TextArea.register_theme].

##### 1. Creating a theme

Let's create a simple theme, `"my_cool_theme"`, which colors the cursor <span style="background-color: dodgerblue; color: white; padding: 0 2px;">blue</span>, and the cursor line <span style="background-color: yellow; color: black; padding: 0 2px;">yellow</span>.
Our theme will also syntax highlight strings as <span style="background-color: red; color: white; padding: 0 2px;">red</span>, and comments as <span style="background-color: magenta; color: black; padding: 0 2px;">magenta</span>.

```python
from rich.style import Style
from textual.widgets.text_area import TextAreaTheme
# ...
my_theme = TextAreaTheme(
    # This name will be used to refer to the theme...
    name="my_cool_theme",
    # Basic styles such as background, cursor, selection, gutter, etc...
    cursor_style=Style(color="white", bgcolor="blue"),
    cursor_line_style=Style(bgcolor="yellow"),
    # `syntax_styles` is for syntax highlighting.
    # It maps tokens parsed from the document to Rich styles.
    syntax_styles={
        "string": Style(color="red"),
        "comment": Style(color="magenta"),
    }
)
```

Attributes like `cursor_style` and `cursor_line_style` apply general language-agnostic
styling to the widget.
If you choose not to supply a value for one of these attributes, it will be taken from the CSS component styles.

The `syntax_styles` attribute of `TextAreaTheme` is used for syntax highlighting and
depends on the `language` currently in use.
For more details, see [syntax highlighting](#syntax-highlighting).

If you wish to build on an existing theme, you can obtain a reference to it using the [`TextAreaTheme.get_builtin_theme`][textual.widgets.text_area.TextAreaTheme.get_builtin_theme] classmethod:

```python
from textual.widgets.text_area import TextAreaTheme

monokai = TextAreaTheme.get_builtin_theme("monokai")
```

##### 2. Registering a theme

Our theme can now be registered with the `TextArea` instance.

```python
text_area.register_theme(my_theme)
```

After registering a theme, it'll appear in the `available_themes`:

```python
>>> print(text_area.available_themes)
{'dracula', 'github_light', 'monokai', 'vscode_dark', 'my_cool_theme'}
```

We can now switch to it:

```python
text_area.theme = "my_cool_theme"
```

This immediately updates the appearance of the `TextArea`:

```{.textual path="docs/examples/widgets/text_area_custom_theme.py" columns="42" lines="8"}
```

### Tab and Escape behavior

Pressing the ++tab++ key will shift focus to the next widget in your application by default.
This matches how other widgets work in Textual.

To have ++tab++ insert a `\t` character, set the `tab_behavior` attribute to the string value `"indent"`.
While in this mode, you can shift focus by pressing the ++escape++ key.

### Indentation

The character(s) inserted when you press tab is controlled by setting the `indent_type` attribute to either `tabs` or `spaces`.

If `indent_type == "spaces"`, pressing ++tab++ will insert up to `indent_width` spaces in order to align with the next tab stop.

### Undo and redo

`TextArea` offers `undo` and `redo` methods.
By default, `undo` is bound to <kbd>Ctrl</kbd>+<kbd>Z</kbd> and `redo` to <kbd>Ctrl</kbd>+<kbd>Y</kbd>.

The `TextArea` uses a heuristic to place _checkpoints_ after certain types of edit.
When you call `undo`, all of the edits between now and the most recent checkpoint are reverted.
You can manually add a checkpoint by calling the [`TextArea.history.checkpoint()`][textual.widgets.text_area.EditHistory.checkpoint] instance method.

The undo and redo history uses a stack-based system, where a single item on the stack represents a single checkpoint.
In memory-constrained environments, you may wish to reduce the maximum number of checkpoints that can exist.
You can do this by passing the `max_checkpoints` argument to the `TextArea` constructor.

### Read-only mode

`TextArea.read_only` is a boolean reactive attribute which, if `True`, will prevent users from modifying content in the `TextArea`.

While `read_only=True`, you can still modify the content programmatically.

While this mode is active, the `TextArea` receives the `-read-only` CSS class, which you can use to supply custom styles for read-only mode.

### Line separators

When content is loaded into `TextArea`, the content is scanned from beginning to end
and the first occurrence of a line separator is recorded.

This separator will then be used when content is later read from the `TextArea` via
the `text` property. The `TextArea` widget does not support exporting text which
contains mixed line endings.

Similarly, newline characters pasted into the `TextArea` will be converted.

You can check the line separator of the current document by inspecting `TextArea.document.newline`:

```python
>>> text_area = TextArea()
>>> text_area.document.newline
'\n'
```

### Line numbers

The gutter (column on the left containing line numbers) can be toggled by setting
the `show_line_numbers` attribute to `True` or `False`.

Setting this attribute will immediately repaint the `TextArea` to reflect the new value.

You can also change the start line number (the topmost line number in the gutter) by setting the `line_number_start` reactive attribute.

### Extending `TextArea`

Sometimes, you may wish to subclass `TextArea` to add some extra functionality.
In this section, we'll briefly explore how we can extend the widget to achieve common goals.

#### Hooking into key presses

You may wish to hook into certain key presses to inject some functionality.
This can be done by over-riding `_on_key` and adding the required functionality.

##### Example - closing parentheses automatically

Let's extend `TextArea` to add a feature which automatically closes parentheses and moves the cursor to a sensible location.

```python
--8<-- "docs/examples/widgets/text_area_extended.py"
```

This intercepts the key handler when `"("` is pressed, and inserts `"()"` instead.
It then moves the cursor so that it lands between the open and closing parentheses.

Typing "`def hello(`" into the `TextArea` now results in the bracket automatically being closed:

```{.textual path="docs/examples/widgets/text_area_extended.py" columns="36" lines="4" press="d,e,f,space,h,e,l,l,o,left_parenthesis"}
```

### Advanced concepts

#### Syntax highlighting

Syntax highlighting inside the `TextArea` is powered by a library called [`tree-sitter`](https://tree-sitter.github.io/tree-sitter/).

Each time you update the document in a `TextArea`, an internal syntax tree is updated.
This tree is frequently _queried_ to find location ranges relevant to syntax highlighting.
We give these ranges _names_, and ultimately map them to Rich styles inside `TextAreaTheme.syntax_styles`.

To illustrate how this works, lets look at how the "Monokai" `TextAreaTheme` highlights Markdown files.

When the `language` attribute is set to `"markdown"`, a highlight query similar to the one below is used (trimmed for brevity).

```scheme
(heading_content) @heading
(link) @link
```

This highlight query maps `heading_content` nodes returned by the Markdown parser to the name `@heading`,
and `link` nodes to the name `@link`.

Inside our `TextAreaTheme.syntax_styles` dict, we can map the name `@heading` to a Rich style.
Here's a snippet from the "Monokai" theme which does just that:

```python
TextAreaTheme(
    name="monokai",
    base_style=Style(color="#f8f8f2", bgcolor="#272822"),
    gutter_style=Style(color="#90908a", bgcolor="#272822"),
    # ...
    syntax_styles={
        # Colorise @heading and make them bold
        "heading": Style(color="#F92672", bold=True),
        # Colorise and underline @link
        "link": Style(color="#66D9EF", underline=True),
        # ...
    },
)
```

To understand which names can be mapped inside `syntax_styles`, we recommend looking at the existing
themes and highlighting queries (`.scm` files) in the Textual repository.

!!! tip

    You may also wish to take a look at the contents of `TextArea._highlights` on an
    active `TextArea` instance to see which highlights have been generated for the
    open document.

#### Adding support for custom languages

To add support for a language to a `TextArea`, use the [`register_language`][textual.widgets._text_area.TextArea.register_language] method.

To register a language, we require two things:

1. A tree-sitter `Language` object which contains the grammar for the language.
2. A highlight query which is used for [syntax highlighting](#syntax-highlighting).

##### Example - adding Java support

The easiest way to obtain a `Language` object is using the [`py-tree-sitter-languages`](https://github.com/grantjenks/py-tree-sitter-languages) package. Here's how we can use this package to obtain a reference to a `Language` object representing Java:

```python
from tree_sitter_languages import get_language
java_language = get_language("java")
```

The exact version of the parser used when you call `get_language` can be checked via
the [`repos.txt` file](https://github.com/grantjenks/py-tree-sitter-languages/blob/a6d4f7c903bf647be1bdcfa504df967d13e40427/repos.txt) in
the version of `py-tree-sitter-languages` you're using. This file contains links to the GitHub
repos and commit hashes of the tree-sitter parsers. In these repos you can often find pre-made highlight queries at `queries/highlights.scm`,
and a file showing all the available node types which can be used in highlight queries at `src/node-types.json`.

Since we're adding support for Java, lets grab the Java highlight query from the repo by following these steps:

1. Open [`repos.txt` file](https://github.com/grantjenks/py-tree-sitter-languages/blob/a6d4f7c903bf647be1bdcfa504df967d13e40427/repos.txt) from the `py-tree-sitter-languages` repo.
2. Find the link corresponding to `tree-sitter-java` and go to the repo on GitHub (you may also need to go to the specific commit referenced in `repos.txt`).
3. Go to [`queries/highlights.scm`](https://github.com/tree-sitter/tree-sitter-java/blob/ac14b4b1884102839455d32543ab6d53ae089ab7/queries/highlights.scm) to see the example highlight query for Java.

Be sure to check the license in the repo to ensure it can be freely copied.

!!! warning

    It's important to use a highlight query which is compatible with the parser in use, so
    pay attention to the commit hash when visiting the repo via `repos.txt`.

We now have our `Language` and our highlight query, so we can register Java as a language.

```python
--8<-- "docs/examples/widgets/text_area_custom_language.py"
```

Running our app, we can see that the Java code is highlighted.
We can freely edit the text, and the syntax highlighting will update immediately.

```{.textual path="docs/examples/widgets/text_area_custom_language.py" columns="52" lines="8"}
```

Recall that we map names (like `@heading`) from the tree-sitter highlight query to Rich style objects inside the `TextAreaTheme.syntax_styles` dictionary.
If you notice some highlights are missing after registering a language, the issue may be:

1. The current `TextAreaTheme` doesn't contain a mapping for the name in the highlight query. Adding a new key-value pair to `syntax_styles` should resolve the issue.
2. The highlight query doesn't assign a name to the pattern you expect to be highlighted. In this case you'll need to update the highlight query to assign to the name.

!!! tip

    The names assigned in tree-sitter highlight queries are often reused across multiple languages.
    For example, `@string` is used in many languages to highlight strings.

#### Navigation and wrapping information

If you're building functionality on top of `TextArea`, it may be useful to inspect the `navigator` and `wrapped_document` attributes.

- `navigator` is a [`DocumentNavigator`][textual.widgets.text_area.DocumentNavigator] instance which can give us general information about the cursor's location within a document, as well as where the cursor will move to when certain actions are performed.
- `wrapped_document` is a [`WrappedDocument`][textual.widgets.text_area.WrappedDocument] instance which can be used to convert document locations to visual locations, taking wrapping into account. It also offers a variety of other convenience methods and properties.

A detailed view of these classes is out of scope, but do note that a lot of the functionality of `TextArea` exists within them, so inspecting them could be worthwhile.

## Reactive attributes

| Name                   | Type                     | Default       | Description                                                      |
|------------------------|--------------------------|---------------|------------------------------------------------------------------|
| `language`             | `str | None`             | `None`        | The language to use for syntax highlighting.                     |
| `theme`                | `str`                    | `"css"`       | The theme to use.                                                |
| `selection`            | `Selection`              | `Selection()` | The current selection.                                           |
| `show_line_numbers`    | `bool`                   | `False`       | Show or hide line numbers.                                       |
| `line_number_start`    | `int`                    | `1`           | The start line number in the gutter.                            |
| `indent_width`         | `int`                    | `4`           | The number of spaces to indent and width of tabs.                |
| `match_cursor_bracket` | `bool`                   | `True`        | Enable/disable highlighting matching brackets under cursor.      |
| `cursor_blink`         | `bool`                   | `True`        | Enable/disable blinking of the cursor when the widget has focus. |
| `soft_wrap`            | `bool`                   | `True`        | Enable/disable soft wrapping.                                    |
| `read_only`            | `bool`                   | `False`       | Enable/disable read-only mode.                                   |

## Messages

- [TextArea.Changed][textual.widgets._text_area.TextArea.Changed]
- [TextArea.SelectionChanged][textual.widgets._text_area.TextArea.SelectionChanged]

## Bindings

The `TextArea` widget defines the following bindings:

::: textual.widgets._text_area.TextArea.BINDINGS
    options:
      show_root_heading: false
      show_root_toc_entry: false

## Component classes

The `TextArea` defines component classes that can style various aspects of the widget.
Styles from the `theme` attribute take priority.

::: textual.widgets.TextArea.COMPONENT_CLASSES
    options:
      show_root_heading: false
      show_root_toc_entry: false

## See also

- [`Input`][textual.widgets.Input] - single-line text input widget
- [`TextAreaTheme`][textual.widgets.text_area.TextAreaTheme] - theming the `TextArea`
- [`DocumentNavigator`][textual.widgets.text_area.DocumentNavigator] - guides cursor movement 
- [`WrappedDocument`][textual.widgets.text_area.WrappedDocument] - manages wrapping the document 
- [`EditHistory`][textual.widgets.text_area.EditHistory] - manages the undo stack
- The tree-sitter documentation [website](https://tree-sitter.github.io/tree-sitter/).
- The tree-sitter Python bindings [repository](https://github.com/tree-sitter/py-tree-sitter).
- `py-tree-sitter-languages` [repository](https://github.com/grantjenks/py-tree-sitter-languages) (provides binary wheels for a large variety of tree-sitter languages).

## Additional notes

- To remove the outline effect when the `TextArea` is focused, you can set `border: none; padding: 0;` in your CSS.

---

::: textual.widgets._text_area.TextArea
    options:
      heading_level: 2

---

::: textual.widgets.text_area
    options:
      heading_level: 2