Skip to content

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.

  • 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 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 install "textual[syntax]"
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 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.

TextAreaExample ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 1  defhello(name):                    2  print("hello"+ name)  3   4  defgoodbye(name):  5  print("goodbye"+ name)  6   ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

from textual.app import App, ComposeResult
from textual.widgets import TextArea

TEXT = """\
def hello(name):
    print("hello" + name)

def goodbye(name):
    print("goodbye" + name)
"""


class TextAreaExample(App):
    def compose(self) -> ComposeResult:
        yield TextArea.code_editor(TEXT, language="python")


app = TextAreaExample()
if __name__ == "__main__":
    app.run()

To update the content programmatically, set the text property to a string value.

To update the parser used for syntax highlighting, set the language reactive attribute:

# 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.

Reading content from TextArea

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

In all cases, when multiple lines of text are retrieved, the document line separator will be used.

Editing content inside TextArea

The content of the TextArea can be updated using the replace method. This method is the programmatic equivalent of selecting some text and then pasting.

Some other convenient methods are available, such as insert, delete, and 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 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.

>>> 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:

TextAreaSelection ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 1  defhello(name): 2  print("hello"+ name) 3   4  defgoodbye(name):  5  print("goodbye"+ name)  6   ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

from textual.app import App, ComposeResult
from textual.widgets import TextArea
from textual.widgets.text_area import Selection

TEXT = """\
def hello(name):
    print("hello" + name)

def goodbye(name):
    print("goodbye" + name)
"""


class TextAreaSelection(App):
    def compose(self) -> ComposeResult:
        text_area = TextArea.code_editor(TEXT, language="python")
        text_area.selection = Selection(start=(0, 0), end=(2, 0))  # (1)!
        yield text_area


app = TextAreaSelection()
if __name__ == "__main__":
    app.run()
  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 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 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 method allows you to move the cursor to a new location while selecting text, or move the cursor and scroll to keep it centered.

# 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 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 selects a line by index. Bound to F6 by default.
  • 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 it's 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 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.

# 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 property.

>>> 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 attribute to one of the available themes.

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.

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

  1. Create an instance of TextAreaTheme.
  2. Register it using TextArea.register_theme.
1. Creating a theme

Let's create a simple theme, "my_cool_theme", which colors the cursor blue, and the cursor line yellow. Our theme will also syntax highlight strings as red, and comments as magenta.

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.

If you wish to build on an existing theme, you can obtain a reference to it using the TextAreaTheme.get_builtin_theme classmethod:

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.

text_area.register_theme(my_theme)

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

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

We can now switch to it:

text_area.theme = "my_cool_theme"

This immediately updates the appearance of the TextArea:

TextAreaCustomThemes ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ # says hello def hello(name):      print("hello" + name)  # says goodbye▄▄ def goodbye(name):  ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

Tab and Escape behaviour

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 Esc 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 Ctrl+Z and redo to Ctrl+Y.

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() 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:

>>> 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.

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.

from textual import events
from textual.app import App, ComposeResult
from textual.widgets import TextArea


class ExtendedTextArea(TextArea):
    """A subclass of TextArea with parenthesis-closing functionality."""

    def _on_key(self, event: events.Key) -> None:
        if event.character == "(":
            self.insert("()")
            self.move_cursor_relative(columns=-1)
            event.prevent_default()


class TextAreaKeyPressHook(App):
    def compose(self) -> ComposeResult:
        yield ExtendedTextArea.code_editor(language="python")


app = TextAreaKeyPressHook()
if __name__ == "__main__":
    app.run()

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:

TextAreaKeyPressHook ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 1  def hello() ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

Advanced concepts

Syntax highlighting

Syntax highlighting inside the TextArea is powered by a library called 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).

(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:

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 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.
Example - adding Java support

The easiest way to obtain a Language object is using the py-tree-sitter-languages package. Here's how we can use this package to obtain a reference to a Language object representing Java:

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 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 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 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.

from pathlib import Path

from tree_sitter_languages import get_language

from textual.app import App, ComposeResult
from textual.widgets import TextArea

java_language = get_language("java")
java_highlight_query = (Path(__file__).parent / "java_highlights.scm").read_text()
java_code = """\
class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
"""


class TextAreaCustomLanguage(App):
    def compose(self) -> ComposeResult:
        text_area = TextArea.code_editor(text=java_code)
        text_area.cursor_blink = False

        # Register the Java language and highlight query
        text_area.register_language(java_language, java_highlight_query)

        # Switch to Java
        text_area.language = "java"
        yield text_area


app = TextAreaCustomLanguage()
if __name__ == "__main__":
    app.run()

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.

TextAreaCustomLanguage ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 1  class HelloWorld {                            2  publicstatic void main(String[] args) {  3          System.out.println("Hello, World!");  4      }  5   6   ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

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 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.

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 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 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.
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

Bindings

The TextArea widget defines the following bindings:

Key(s) Description
up Move the cursor up.
down Move the cursor down.
left Move the cursor left.
ctrl+left Move the cursor to the start of the word.
ctrl+shift+left Move the cursor to the start of the word and select.
right Move the cursor right.
ctrl+right Move the cursor to the end of the word.
ctrl+shift+right Move the cursor to the end of the word and select.
home,ctrl+a Move the cursor to the start of the line.
end,ctrl+e Move the cursor to the end of the line.
shift+home Move the cursor to the start of the line and select.
shift+end Move the cursor to the end of the line and select.
pageup Move the cursor one page up.
pagedown Move the cursor one page down.
shift+up Select while moving the cursor up.
shift+down Select while moving the cursor down.
shift+left Select while moving the cursor left.
shift+right Select while moving the cursor right.
backspace Delete character to the left of cursor.
ctrl+w Delete from cursor to start of the word.
delete,ctrl+d Delete character to the right of cursor.
ctrl+f Delete from cursor to end of the word.
ctrl+x Delete the current line.
ctrl+u Delete from cursor to the start of the line.
ctrl+k Delete from cursor to the end of the line.
f6 Select the current line.
f7 Select all text in the document.
ctrl+z Undo.
ctrl+y Redo.

Component classes

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

TextArea offers some component classes which can be used to style aspects of the widget.

Note that any attributes provided in the chosen TextAreaTheme will take priority here.

Class Description
text-area--cursor Target the cursor.
text-area--gutter Target the gutter (line number column).
text-area--cursor-gutter Target the gutter area of the line the cursor is on.
text-area--cursor-line Target the line the cursor is on.
text-area--selection Target the current selection.
text-area--matching-bracket Target matching brackets.

See also

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 class

def __init__(
    self,
    text="",
    *,
    language=None,
    theme="css",
    soft_wrap=True,
    tab_behavior="focus",
    read_only=False,
    show_line_numbers=False,
    max_checkpoints=50,
    name=None,
    id=None,
    classes=None,
    disabled=False
):

Bases: ScrollView

Parameters
Parameter Default Description
text
str
''

The initial text to load into the TextArea.

language
str | None
None

The language to use.

theme
str
'css'

The theme to use.

soft_wrap
bool
True

Enable soft wrapping.

tab_behavior
Literal['focus', 'indent']
'focus'

If 'focus', pressing tab will switch focus. If 'indent', pressing tab will insert a tab.

read_only
bool
False

Enable read-only mode. This prevents edits using the keyboard.

show_line_numbers
bool
False

Show line numbers on the left edge.

max_checkpoints
int
50

The maximum number of undo history checkpoints to retain.

name
str | None
None

The name of the TextArea widget.

id
str | None
None

The ID of the widget, used to refer to it from Textual CSS.

classes
str | None
None

One or more Textual CSS compatible class names separated by spaces.

disabled
bool
False

True if the widget is disabled.

BINDINGS instance-attribute class-attribute

BINDINGS = [
    Binding("up", "cursor_up", "cursor up", show=False),
    Binding(
        "down", "cursor_down", "cursor down", show=False
    ),
    Binding(
        "left", "cursor_left", "cursor left", show=False
    ),
    Binding(
        "right", "cursor_right", "cursor right", show=False
    ),
    Binding(
        "ctrl+left",
        "cursor_word_left",
        "cursor word left",
        show=False,
    ),
    Binding(
        "ctrl+right",
        "cursor_word_right",
        "cursor word right",
        show=False,
    ),
    Binding(
        "home,ctrl+a",
        "cursor_line_start",
        "cursor line start",
        show=False,
    ),
    Binding(
        "end,ctrl+e",
        "cursor_line_end",
        "cursor line end",
        show=False,
    ),
    Binding(
        "pageup",
        "cursor_page_up",
        "cursor page up",
        show=False,
    ),
    Binding(
        "pagedown",
        "cursor_page_down",
        "cursor page down",
        show=False,
    ),
    Binding(
        "ctrl+shift+left",
        "cursor_word_left(True)",
        "cursor left word select",
        show=False,
    ),
    Binding(
        "ctrl+shift+right",
        "cursor_word_right(True)",
        "cursor right word select",
        show=False,
    ),
    Binding(
        "shift+home",
        "cursor_line_start(True)",
        "cursor line start select",
        show=False,
    ),
    Binding(
        "shift+end",
        "cursor_line_end(True)",
        "cursor line end select",
        show=False,
    ),
    Binding(
        "shift+up",
        "cursor_up(True)",
        "cursor up select",
        show=False,
    ),
    Binding(
        "shift+down",
        "cursor_down(True)",
        "cursor down select",
        show=False,
    ),
    Binding(
        "shift+left",
        "cursor_left(True)",
        "cursor left select",
        show=False,
    ),
    Binding(
        "shift+right",
        "cursor_right(True)",
        "cursor right select",
        show=False,
    ),
    Binding("f6", "select_line", "select line", show=False),
    Binding("f7", "select_all", "select all", show=False),
    Binding(
        "backspace",
        "delete_left",
        "delete left",
        show=False,
    ),
    Binding(
        "ctrl+w",
        "delete_word_left",
        "delete left to start of word",
        show=False,
    ),
    Binding(
        "delete,ctrl+d",
        "delete_right",
        "delete right",
        show=False,
    ),
    Binding(
        "ctrl+f",
        "delete_word_right",
        "delete right to start of word",
        show=False,
    ),
    Binding(
        "ctrl+x", "delete_line", "delete line", show=False
    ),
    Binding(
        "ctrl+u",
        "delete_to_start_of_line",
        "delete to line start",
        show=False,
    ),
    Binding(
        "ctrl+k",
        "delete_to_end_of_line_or_delete_line",
        "delete to line end",
        show=False,
    ),
    Binding("ctrl+z", "undo", "Undo", show=False),
    Binding("ctrl+y", "redo", "Redo", show=False),
]
Key(s) Description
up Move the cursor up.
down Move the cursor down.
left Move the cursor left.
ctrl+left Move the cursor to the start of the word.
ctrl+shift+left Move the cursor to the start of the word and select.
right Move the cursor right.
ctrl+right Move the cursor to the end of the word.
ctrl+shift+right Move the cursor to the end of the word and select.
home,ctrl+a Move the cursor to the start of the line.
end,ctrl+e Move the cursor to the end of the line.
shift+home Move the cursor to the start of the line and select.
shift+end Move the cursor to the end of the line and select.
pageup Move the cursor one page up.
pagedown Move the cursor one page down.
shift+up Select while moving the cursor up.
shift+down Select while moving the cursor down.
shift+left Select while moving the cursor left.
shift+right Select while moving the cursor right.
backspace Delete character to the left of cursor.
ctrl+w Delete from cursor to start of the word.
delete,ctrl+d Delete character to the right of cursor.
ctrl+f Delete from cursor to end of the word.
ctrl+x Delete the current line.
ctrl+u Delete from cursor to the start of the line.
ctrl+k Delete from cursor to the end of the line.
f6 Select the current line.
f7 Select all text in the document.
ctrl+z Undo.
ctrl+y Redo.

COMPONENT_CLASSES class-attribute

COMPONENT_CLASSES: set[str] = {
    "text-area--cursor",
    "text-area--gutter",
    "text-area--cursor-gutter",
    "text-area--cursor-line",
    "text-area--selection",
    "text-area--matching-bracket",
}

TextArea offers some component classes which can be used to style aspects of the widget.

Note that any attributes provided in the chosen TextAreaTheme will take priority here.

Class Description
text-area--cursor Target the cursor.
text-area--gutter Target the gutter (line number column).
text-area--cursor-gutter Target the gutter area of the line the cursor is on.
text-area--cursor-line Target the line the cursor is on.
text-area--selection Target the current selection.
text-area--matching-bracket Target matching brackets.

available_languages property

available_languages: set[str]

A list of the names of languages available to the TextArea.

The values in this list can be assigned to the language reactive attribute of TextArea.

The returned list contains the builtin languages plus those registered via the register_language method. Builtin languages will be listed before user-registered languages, but there are no other ordering guarantees.

available_themes property

available_themes: set[str]

A list of the names of the themes available to the TextArea.

The values in this list can be assigned theme reactive attribute of TextArea.

You can retrieve the full specification for a theme by passing one of the strings from this list into TextAreaTheme.get_by_name(theme_name: str).

Alternatively, you can directly retrieve a list of TextAreaTheme objects (which contain the full theme specification) by calling TextAreaTheme.builtin_themes().

cursor_at_end_of_line property

cursor_at_end_of_line: bool

True if and only if the cursor is at the end of a row.

cursor_at_end_of_text property

cursor_at_end_of_text: bool

True if and only if the cursor is at the very end of the document.

cursor_at_first_line property

cursor_at_first_line: bool

True if and only if the cursor is on the first line.

cursor_at_last_line property

cursor_at_last_line: bool

True if and only if the cursor is on the last line.

cursor_at_start_of_line property

cursor_at_start_of_line: bool

True if and only if the cursor is at column 0.

cursor_at_start_of_text property

cursor_at_start_of_text: bool

True if and only if the cursor is at location (0, 0)

cursor_blink: Reactive[bool] = reactive(True, init=False)

True if the cursor should blink.

cursor_location writable property

cursor_location: Location

The current location of the cursor in the document.

This is a utility for accessing the end of TextArea.selection.

cursor_screen_offset property

cursor_screen_offset: Offset

The offset of the cursor relative to the screen.

document instance-attribute

document: DocumentBase = Document(text)

The document this widget is currently editing.

gutter_width property

gutter_width: int

The width of the gutter (the left column containing line numbers).

Returns
Type Description
int

The cell-width of the line number column. If show_line_numbers is False returns 0.

history instance-attribute

history: EditHistory = EditHistory(
    max_checkpoints=max_checkpoints,
    checkpoint_timer=2.0,
    checkpoint_max_characters=100,
)

A stack (the end of the list is the top of the stack) for tracking edits.

indent_type instance-attribute

indent_type: Literal['tabs', 'spaces'] = 'spaces'

Whether to indent using tabs or spaces.

indent_width instance-attribute class-attribute

indent_width: Reactive[int] = reactive(4, init=False)

The width of tabs or the multiple of spaces to align to on pressing the tab key.

If the document currently open contains tabs that are currently visible on screen, altering this value will immediately change the display width of the visible tabs.

is_syntax_aware property

is_syntax_aware: bool

True if the TextArea is currently syntax aware - i.e. it's parsing document content.

language instance-attribute class-attribute

language: Reactive[str | None] = language

The language to use.

This must be set to a valid, non-None value for syntax highlighting to work.

If the value is a string, a built-in language parser will be used if available.

If you wish to use an unsupported language, you'll have to register it first using TextArea.register_language.

match_cursor_bracket instance-attribute class-attribute

match_cursor_bracket: Reactive[bool] = reactive(
    True, init=False
)

If the cursor is at a bracket, highlight the matching bracket (if found).

navigator instance-attribute

navigator: DocumentNavigator = DocumentNavigator(
    self.wrapped_document
)

Queried to determine where the cursor should move given a navigation action, accounting for wrapping etc.

read_only instance-attribute class-attribute

read_only: Reactive[bool] = reactive(False)

True if the content is read-only.

Read-only means end users cannot insert, delete or replace content.

The document can still be edited programmatically via the API.

selected_text property

selected_text: str

The text between the start and end points of the current selection.

selection instance-attribute class-attribute

selection: Reactive[Selection] = reactive(
    Selection(), init=False, always_update=True
)

The selection start and end locations (zero-based line_index, offset).

This represents the cursor location and the current selection.

The Selection.end always refers to the cursor location.

If no text is selected, then Selection.end == Selection.start is True.

The text selected in the document is available via the TextArea.selected_text property.

show_line_numbers instance-attribute class-attribute

show_line_numbers: Reactive[bool] = reactive(
    False, init=False
)

True to show the line number column on the left edge, otherwise False.

Changing this value will immediately re-render the TextArea.

soft_wrap instance-attribute class-attribute

soft_wrap: Reactive[bool] = reactive(True, init=False)

True if text should soft wrap.

text writable property

text: str

The entire text content of the document.

theme instance-attribute class-attribute

theme: Reactive[str] = theme

The name of the theme to use.

Themes must be registered using TextArea.register_theme before they can be used.

Syntax highlighting is only possible when the language attribute is set.

wrap_width property

wrap_width: int

The width which gets used when the document wraps.

Accounts for gutter, scrollbars, etc.

wrapped_document instance-attribute

wrapped_document: WrappedDocument = WrappedDocument(
    self.document
)

The wrapped view of the document.

Changed class

Bases: Message

Posted when the content inside the TextArea changes.

Handle this message using the on decorator - @on(TextArea.Changed) or a method named on_text_area_changed.

control property

control: TextArea

The TextArea that sent this message.

text_area instance-attribute

text_area: TextArea

The text_area that sent this message.

SelectionChanged class

Bases: Message

Posted when the selection changes.

This includes when the cursor moves or when text is selected.

selection instance-attribute

selection: Selection

The new selection.

text_area instance-attribute

text_area: TextArea

The text_area that sent this message.

action_cursor_down method

def action_cursor_down(self, select=False):

Move the cursor down one cell.

Parameters
Parameter Default Description
select
bool
False

If True, select the text while moving.

action_cursor_left method

def action_cursor_left(self, select=False):

Move the cursor one location to the left.

If the cursor is at the left edge of the document, try to move it to the end of the previous line.

Parameters
Parameter Default Description
select
bool
False

If True, select the text while moving.

action_cursor_line_end method

def action_cursor_line_end(self, select=False):

Move the cursor to the end of the line.

action_cursor_line_start method

def action_cursor_line_start(self, select=False):

Move the cursor to the start of the line.

action_cursor_page_down method

def action_cursor_page_down(self):

Move the cursor and scroll down one page.

action_cursor_page_up method

def action_cursor_page_up(self):

Move the cursor and scroll up one page.

action_cursor_right method

def action_cursor_right(self, select=False):

Move the cursor one location to the right.

If the cursor is at the end of a line, attempt to go to the start of the next line.

Parameters
Parameter Default Description
select
bool
False

If True, select the text while moving.

action_cursor_up method

def action_cursor_up(self, select=False):

Move the cursor up one cell.

Parameters
Parameter Default Description
select
bool
False

If True, select the text while moving.

action_cursor_word_left method

def action_cursor_word_left(self, select=False):

Move the cursor left by a single word, skipping trailing whitespace.

Parameters
Parameter Default Description
select
bool
False

Whether to select while moving the cursor.

action_cursor_word_right method

def action_cursor_word_right(self, select=False):

Move the cursor right by a single word, skipping leading whitespace.

action_delete_left method

def action_delete_left(self):

Deletes the character to the left of the cursor and updates the cursor location.

If there's a selection, then the selected range is deleted.

action_delete_line method

def action_delete_line(self):

Deletes the lines which intersect with the selection.

action_delete_right method

def action_delete_right(self):

Deletes the character to the right of the cursor and keeps the cursor at the same location.

If there's a selection, then the selected range is deleted.

action_delete_to_end_of_line method

def action_delete_to_end_of_line(self):

Deletes from the cursor location to the end of the line.

action_delete_to_end_of_line_or_delete_line async

def action_delete_to_end_of_line_or_delete_line(self):

Deletes from the cursor location to the end of the line, or deletes the line.

The line will be deleted if the line is empty.

action_delete_to_start_of_line method

def action_delete_to_start_of_line(self):

Deletes from the cursor location to the start of the line.

action_delete_word_left method

def action_delete_word_left(self):

Deletes the word to the left of the cursor and updates the cursor location.

action_delete_word_right method

def action_delete_word_right(self):

Deletes the word to the right of the cursor and keeps the cursor at the same location.

Note that the location that we delete to using this action is not the same as the location we move to when we move the cursor one word to the right. This action does not skip leading whitespace, whereas cursor movement does.

action_redo method

def action_redo(self):

Redo the most recently undone batch of edits.

action_select_all method

def action_select_all(self):

Select all the text in the document.

action_select_line method

def action_select_line(self):

Select all the text on the current line.

action_undo method

def action_undo(self):

Undo the edits since the last checkpoint (the most recent batch of edits).

cell_width_to_column_index method

def cell_width_to_column_index(self, cell_width, row_index):

Return the column that the cell width corresponds to on the given row.

Parameters
Parameter Default Description
cell_width
int
required

The cell width to convert.

row_index
int
required

The index of the row to examine.

Returns
Type Description
int

The column corresponding to the cell width on that row.

clamp_visitable method

def clamp_visitable(self, location):

Clamp the given location to the nearest visitable location.

Parameters
Parameter Default Description
location
Location
required

The location to clamp.

Returns
Type Description
Location

The nearest location that we could conceivably navigate to using the cursor.

clear method

def clear(self):

Delete all text from the document.

Returns
Type Description
EditResult

An EditResult relating to the deletion of all content.

code_editor classmethod

def code_editor(
    cls,
    text="",
    *,
    language=None,
    theme="monokai",
    soft_wrap=False,
    tab_behavior="indent",
    read_only=False,
    show_line_numbers=True,
    max_checkpoints=50,
    name=None,
    id=None,
    classes=None,
    disabled=False
):

Construct a new TextArea with sensible defaults for editing code.

This instantiates a TextArea with line numbers enabled, soft wrapping disabled, "indent" tab behavior, and the "monokai" theme.

Parameters
Parameter Default Description
text
str
''

The initial text to load into the TextArea.

language
str | None
None

The language to use.

theme
str
'monokai'

The theme to use.

soft_wrap
bool
False

Enable soft wrapping.

tab_behavior
Literal['focus', 'indent']
'indent'

If 'focus', pressing tab will switch focus. If 'indent', pressing tab will insert a tab.

show_line_numbers
bool
True

Show line numbers on the left edge.

name
str | None
None

The name of the TextArea widget.

id
str | None
None

The ID of the widget, used to refer to it from Textual CSS.

classes
str | None
None

One or more Textual CSS compatible class names separated by spaces.

disabled
bool
False

True if the widget is disabled.

delete method

def delete(self, start, end, *, maintain_selection_offset=True):

Delete the text between two locations in the document.

Parameters
Parameter Default Description
start
Location
required

The start location.

end
Location
required

The end location.

maintain_selection_offset
bool
True

If True, the active Selection will be updated such that the same text is selected before and after the selection, if possible. Otherwise, the cursor will jump to the end point of the edit.

Returns
Type Description
EditResult

An EditResult containing information about the edit.

edit method

def edit(self, edit):

Perform an Edit.

Parameters
Parameter Default Description
edit
Edit
required

The Edit to perform.

Returns
Type Description
EditResult

Data relating to the edit that may be useful. The data returned

EditResult

may be different depending on the edit performed.

find_matching_bracket method

def find_matching_bracket(self, bracket, search_from):

If the character is a bracket, find the matching bracket.

Parameters
Parameter Default Description
bracket
str
required

The character we're searching for the matching bracket of.

search_from
Location
required

The location to start the search.

Returns
Type Description
Location | None

The Location of the matching bracket, or None if it's not found.

Location | None

If the character is not available for bracket matching, None is returned.

get_column_width method

def get_column_width(self, row, column):

Get the cell offset of the column from the start of the row.

Parameters
Parameter Default Description
row
int
required

The row index.

column
int
required

The column index (codepoint offset from start of row).

Returns
Type Description
int

The cell width of the column relative to the start of the row.

get_cursor_down_location method

def get_cursor_down_location(self):

Get the location the cursor will move to if it moves down.

Returns
Type Description
Location

The location the cursor will move to if it moves down.

get_cursor_left_location method

def get_cursor_left_location(self):

Get the location the cursor will move to if it moves left.

Returns
Type Description
Location

The location of the cursor if it moves left.

get_cursor_line_end_location method

def get_cursor_line_end_location(self):

Get the location of the end of the current line.

Returns
Type Description
Location

The (row, column) location of the end of the cursors current line.

get_cursor_line_start_location method

def get_cursor_line_start_location(self, smart_home=False):

Get the location of the start of the current line.

Parameters
Parameter Default Description
smart_home
bool
False

If True, use "smart home key" behavior - go to the first non-whitespace character on the line, and if already there, go to offset 0. Smart home only works when wrapping is disabled.

Returns
Type Description
Location

The (row, column) location of the start of the cursors current line.

get_cursor_right_location method

def get_cursor_right_location(self):

Get the location the cursor will move to if it moves right.

Returns
Type Description
Location

the location the cursor will move to if it moves right.

get_cursor_up_location method

def get_cursor_up_location(self):

Get the location the cursor will move to if it moves up.

Returns
Type Description
Location

The location the cursor will move to if it moves up.

get_cursor_word_left_location method

def get_cursor_word_left_location(self):

Get the location the cursor will jump to if it goes 1 word left.

Returns
Type Description
Location

The location the cursor will jump on "jump word left".

get_cursor_word_right_location method

def get_cursor_word_right_location(self):

Get the location the cursor will jump to if it goes 1 word right.

Returns
Type Description
Location

The location the cursor will jump on "jump word right".

get_line method

def get_line(self, line_index):

Retrieve the line at the given line index.

You can stylize the Text object returned here to apply additional styling to TextArea content.

Parameters
Parameter Default Description
line_index
int
required

The index of the line.

Returns
Type Description
Text

A rich.Text object containing the requested line.

get_target_document_location method

def get_target_document_location(self, event):

Given a MouseEvent, return the row and column offset of the event in document-space.

Parameters
Parameter Default Description
event
MouseEvent
required

The MouseEvent.

Returns
Type Description
Location

The location of the mouse event within the document.

get_text_range method

def get_text_range(self, start, end):

Get the text between a start and end location.

Parameters
Parameter Default Description
start
Location
required

The start location.

end
Location
required

The end location.

Returns
Type Description
str

The text between start and end.

insert method

def insert(
    self,
    text,
    location=None,
    *,
    maintain_selection_offset=True
):

Insert text into the document.

Parameters
Parameter Default Description
text
str
required

The text to insert.

location
Location | None
None

The location to insert text, or None to use the cursor location.

maintain_selection_offset
bool
True

If True, the active Selection will be updated such that the same text is selected before and after the selection, if possible. Otherwise, the cursor will jump to the end point of the edit.

Returns
Type Description
EditResult

An EditResult containing information about the edit.

load_text method

def load_text(self, text):

Load text into the TextArea.

This will replace the text currently in the TextArea and clear the edit history.

Parameters
Parameter Default Description
text
str
required

The text to load into the TextArea.

move_cursor method

def move_cursor(
    self,
    location,
    select=False,
    center=False,
    record_width=True,
):

Move the cursor to a location.

Parameters
Parameter Default Description
location
Location
required

The location to move the cursor to.

select
bool
False

If True, select text between the old and new location.

center
bool
False

If True, scroll such that the cursor is centered.

record_width
bool
True

If True, record the cursor column cell width after navigating so that we jump back to the same width the next time we move to a row that is wide enough.

move_cursor_relative method

def move_cursor_relative(
    self,
    rows=0,
    columns=0,
    select=False,
    center=False,
    record_width=True,
):

Move the cursor relative to its current location in document-space.

Parameters
Parameter Default Description
rows
int
0

The number of rows to move down by (negative to move up)

columns
int
0

The number of columns to move right by (negative to move left)

select
bool
False

If True, select text between the old and new location.

center
bool
False

If True, scroll such that the cursor is centered.

record_width
bool
True

If True, record the cursor column cell width after navigating so that we jump back to the same width the next time we move to a row that is wide enough.

record_cursor_width method

def record_cursor_width(self):

Record the current cell width of the cursor.

This is used where we navigate up and down through rows. If we're in the middle of a row, and go down to a row with no content, then we go down to another row, we want our cursor to jump back to the same offset that we were originally at.

redo method

def redo(self):

Redo the most recently undone batch of edits.

register_language method

def register_language(self, language, highlight_query):

Register a language and corresponding highlight query.

Calling this method does not change the language of the TextArea. On switching to this language (via the language reactive attribute), syntax highlighting will be performed using the given highlight query.

If a string name is supplied for a builtin supported language, then this method will update the default highlight query for that language.

Registering a language only registers it to this instance of TextArea.

Parameters
Parameter Default Description
language
'str | Language'
required

A string referring to a builtin language or a tree-sitter Language object.

highlight_query
str
required

The highlight query to use for syntax highlighting this language.

register_theme method

def register_theme(self, theme):

Register a theme for use by the TextArea.

After registering a theme, you can set themes by assigning the theme name to the TextArea.theme reactive attribute. For example text_area.theme = "my_custom_theme" where "my_custom_theme" is the name of the theme you registered.

If you supply a theme with a name that already exists that theme will be overwritten.

replace method

def replace(
    self,
    insert,
    start,
    end,
    *,
    maintain_selection_offset=True
):

Replace text in the document with new text.

Parameters
Parameter Default Description
insert
str
required

The text to insert.

start
Location
required

The start location

end
Location
required

The end location.

maintain_selection_offset
bool
True

If True, the active Selection will be updated such that the same text is selected before and after the selection, if possible. Otherwise, the cursor will jump to the end point of the edit.

Returns
Type Description
EditResult

An EditResult containing information about the edit.

scroll_cursor_visible method

def scroll_cursor_visible(self, center=False, animate=False):

Scroll the TextArea such that the cursor is visible on screen.

Parameters
Parameter Default Description
center
bool
False

True if the cursor should be scrolled to the center.

animate
bool
False

True if we should animate while scrolling.

Returns
Type Description
Offset

The offset that was scrolled to bring the cursor into view.

select_all method

def select_all(self):

Select all of the text in the TextArea.

select_line method

def select_line(self, index):

Select all the text in the specified line.

Parameters
Parameter Default Description
index
int
required

The index of the line to select (starting from 0).

undo method

def undo(self):

Undo the edits since the last checkpoint (the most recent batch of edits).


Highlight module-attribute

Highlight = Tuple[StartColumn, EndColumn, HighlightName]

A tuple representing a syntax highlight within one line.

Location module-attribute

Location = Tuple[int, int]

A location (row, column) within the document. Indexing starts at 0.

Document class

def __init__(self, text):

Bases: DocumentBase

A document which can be opened in a TextArea.

end property

end: Location

Returns the location of the end of the document.

line_count property

line_count: int

Returns the number of lines in the document.

lines property

lines: list[str]

Get the document as a list of strings, where each string represents a line.

Newline characters are not included in at the end of the strings.

The newline character used in this document can be found via the Document.newline property.

newline property

newline: Newline

Get the Newline used in this document (e.g. ' ', ' '. etc.)

start property

start: Location

Returns the location of the start of the document (0, 0).

text property

text: str

Get the text from the document.

get_index_from_location method

def get_index_from_location(self, location):

Given a location, returns the index from the document's text.

Parameters
Parameter Default Description
location
Location
required

The location in the document.

Returns
Type Description
int

The index in the document's text.

get_line method

def get_line(self, index):

Returns the line with the given index from the document.

Parameters
Parameter Default Description
index
int
required

The index of the line in the document.

Returns
Type Description
str

The string representing the line.

get_location_from_index method

def get_location_from_index(self, index):

Given an index in the document's text, returns the corresponding location.

Parameters
Parameter Default Description
index
int
required

The index in the document's text.

Returns
Type Description
Location

The corresponding location.

get_size method

def get_size(self, tab_width):

The Size of the document, taking into account the tab rendering width.

Parameters
Parameter Default Description
tab_width
int
required

The width to use for tab indents.

Returns
Type Description
Size

The size (width, height) of the document.

get_text_range method

def get_text_range(self, start, end):

Get the text that falls between the start and end locations.

Returns the text between start and end, including the appropriate line separator character as specified by Document._newline. Note that _newline is set automatically to the first line separator character found in the document.

Parameters
Parameter Default Description
start
Location
required

The start location of the selection.

end
Location
required

The end location of the selection.

Returns
Type Description
str

The text between start (inclusive) and end (exclusive).

replace_range method

def replace_range(self, start, end, text):

Replace text at the given range.

This is the only method by which a document may be updated.

Parameters
Parameter Default Description
start
Location
required

A tuple (row, column) where the edit starts.

end
Location
required

A tuple (row, column) where the edit ends.

text
str
required

The text to insert between start and end.

Returns
Type Description
EditResult

The EditResult containing information about the completed replace operation.

DocumentBase class

Bases: ABC

Describes the minimum functionality a Document implementation must provide in order to be used by the TextArea widget.

end property abstractmethod

end: Location

Returns the location of the end of the document.

line_count property abstractmethod

line_count: int

Returns the number of lines in the document.

lines property abstractmethod

lines: list[str]

Get the lines of the document as a list of strings.

The strings should not include newline characters. The newline character used for the document can be retrieved via the newline property.

newline property abstractmethod

newline: Newline

Return the line separator used in the document.

start property abstractmethod

start: Location

Returns the location of the start of the document (0, 0).

text property abstractmethod

text: str

The text from the document as a string.

get_line abstractmethod

def get_line(self, index):

Returns the line with the given index from the document.

This is used in rendering lines, and will be called by the TextArea for each line that is rendered.

Parameters
Parameter Default Description
index
int
required

The index of the line in the document.

Returns
Type Description
str

The str instance representing the line.

get_size abstractmethod

def get_size(self, indent_width):

Get the size of the document.

The height is generally the number of lines, and the width is generally the maximum cell length of all the lines.

Parameters
Parameter Default Description
indent_width
int
required

The width to use for tab characters.

Returns
Type Description
Size

The Size of the document bounding box.

get_text_range abstractmethod

def get_text_range(self, start, end):

Get the text that falls between the start and end locations.

Parameters
Parameter Default Description
start
Location
required

The start location of the selection.

end
Location
required

The end location of the selection.

Returns
Type Description
str

The text between start (inclusive) and end (exclusive).

query_syntax_tree method

def query_syntax_tree(
    self, query, start_point=None, end_point=None
):

Query the tree-sitter syntax tree.

The default implementation always returns an empty list.

To support querying in a subclass, this must be implemented.

Parameters
Parameter Default Description
query
Query
required

The tree-sitter Query to perform.

start_point
tuple[int, int] | None
None

The (row, column byte) to start the query at.

end_point
tuple[int, int] | None
None

The (row, column byte) to end the query at.

Returns
Type Description
list[tuple[Node, str]]

A tuple containing the nodes and text captured by the query.

replace_range abstractmethod

def replace_range(self, start, end, text):

Replace the text at the given range.

Parameters
Parameter Default Description
start
Location
required

A tuple (row, column) where the edit starts.

end
Location
required

A tuple (row, column) where the edit ends.

text
str
required

The text to insert between start and end.

Returns
Type Description
EditResult

The new end location after the edit is complete.

DocumentNavigator class

def __init__(self, wrapped_document):

Cursor navigation in the TextArea is "wrapping-aware".

Although the cursor location (the selection) is represented as a location in the raw document, when you actually move the cursor, it must take wrapping into account (otherwise things start to look really confusing to the user where wrapping is involved).

Your cursor visually moves through the wrapped version of the document, rather than the raw document. So, for example, pressing down on the keyboard may move your cursor to a position further along the current raw document line, rather than on to the next line in the raw document.

The DocumentNavigator class manages that behaviour.

Given a cursor location in the unwrapped document, and a cursor movement action, this class can inform us of the destination the cursor will move to considering the current wrapping width and document content. It can also translate between document-space (a location/(row,col) in the raw document), and visual-space (x and y offsets) as the user will see them on screen after the document has been wrapped.

For this to work correctly, the wrapped_document and document must be synchronised. This means that if you make an edit to the document, you must then update the wrapped document, and then you may query the document navigator.

Naming conventions:

A "location" refers to a location, in document-space (in the raw document). It is entirely unrelated to visually positioning. A location in a document can appear in any visual position, as it is influenced by scrolling, wrapping, gutter settings, and the cell width of characters to its left.

A "wrapped section" refers to a portion of the line accounting for wrapping. For example the line "ABCDEF" when wrapped at width 3 will result in 2 sections: "ABC" and "DEF". In this case, we call "ABC" is the first section/wrapped section.

A "wrap offset" is an integer representing the index at which wrapping occurs in a document-space line. This is a codepoint index, rather than a visual offset. In "ABCDEF" with wrapping at width 3, there is a single wrap offset of 3.

"Smart home" refers to a modification of the "home" key behaviour. If smart home is enabled, the first non-whitespace character is considered to be the home location. If the cursor is currently at this position, then the normal home behaviour applies. This is designed to make cursor movement more useful to end users.

Parameters
Parameter Default Description
wrapped_document
WrappedDocument
required

The WrappedDocument to be used when making navigation decisions.

last_x_offset instance-attribute

last_x_offset = 0

Remembers the last x offset (cell width) the cursor was moved horizontally to, so that it can be restored on vertical movement where possible.

clamp_reachable method

def clamp_reachable(self, location):

Given a location, return the nearest location that corresponds to a reachable location in the document.

Parameters
Parameter Default Description
location
Location
required

A location.

Returns
Type Description
Location

The nearest reachable location in the document.

get_location_above method

def get_location_above(self, location):

Get the location visually aligned with the cell above the given location.

Parameters
Parameter Default Description
location
Location
required

The location to start from.

Returns
Type Description
Location

The cell above the given location.

get_location_at_y_offset method

def get_location_at_y_offset(self, location, vertical_offset):

Apply a visual vertical offset to a location and check the resulting location.

Parameters
Parameter Default Description
location
Location
required

The location to start from.

vertical_offset
int
required

The vertical offset to move (negative=up, positive=down).

Returns
Type Description
Location

The location after the offset has been applied.

get_location_below method

def get_location_below(self, location):

Given a location in the raw document, return the raw document location corresponding to moving down in the wrapped representation of the document.

Parameters
Parameter Default Description
location
Location
required

The location in the raw document.

Returns
Type Description
Location

The location which is visually below the given location.

get_location_end method

def get_location_end(self, location):

Get the location corresponding to the end of the current section.

Parameters
Parameter Default Description
location
Location
required

The current location.

Returns
Type Description
Location

The location corresponding to the end of the wrapped line.

get_location_home method

def get_location_home(self, location, smart_home=False):

Get the "home location" corresponding to the given location.

Parameters
Parameter Default Description
location
Location
required

The location to consider.

smart_home
bool
False

Enable/disable 'smart home' behaviour.

Returns
Type Description
Location

The home location, relative to the given location.

get_location_left method

def get_location_left(self, location):

Get the location to the left of the given location.

Note that if the given location is at the start of the line, then this will return the end of the preceding line, since that's where you would expect the cursor to move.

Parameters
Parameter Default Description
location
Location
required

The location to start from.

Returns
Type Description
Location

The location to the right.

get_location_right method

def get_location_right(self, location):

Get the location to the right of the given location.

Note that if the given location is at the end of the line, then this will return the start of the following line, since that's where you would expect the cursor to move.

Parameters
Parameter Default Description
location
Location
required

The location to start from.

Returns
Type Description
Location

The location to the right.

is_end_of_document method

def is_end_of_document(self, location):

Check if a location is at the end of the document.

Parameters
Parameter Default Description
location
Location
required

The location to examine.

Returns
Type Description
bool

True if and only if the cursor is at the end of the document.

is_end_of_document_line method

def is_end_of_document_line(self, location):

True if the location is at the end of a line in the document.

Note that the "end" of a line is equal to its length (one greater than the final index), since there is a space at the end of the line for the cursor to rest.

Parameters
Parameter Default Description
location
Location
required

The location to examine.

Returns
Type Description
bool

True if and only if the document is at the end of a line in the document.

is_end_of_wrapped_line method

def is_end_of_wrapped_line(self, location):

True if the location is at the end of a wrapped line.

Parameters
Parameter Default Description
location
Location
required

The location to examine.

Returns
Type Description
bool

True if and only if the cursor is on the last wrapped section of any line.

is_first_document_line method

def is_first_document_line(self, location):

Check if the given location is on the first line in the document.

Parameters
Parameter Default Description
location
Location
required

The location to examine.

Returns
Type Description
bool

True if and only if the cursor is on the first line of the document.

is_first_wrapped_line method

def is_first_wrapped_line(self, location):

Check if the given location is on the first wrapped section of the first line in the document.

Parameters
Parameter Default Description
location
Location
required

The location to examine.

Returns
Type Description
bool

True if and only if the cursor is on the first wrapped section of the first line.

is_last_document_line method

def is_last_document_line(self, location):

Check if the given location is on the last line of the document.

Parameters
Parameter Default Description
location
Location
required

The location to examine.

Returns
Type Description
bool

True when the location is on the last line of the document.

is_last_wrapped_line method

def is_last_wrapped_line(self, location):

Check if the given location is on the last wrapped section of the last line.

That is, the cursor is visually on the last rendered row.

Parameters
Parameter Default Description
location
Location
required

The location to examine.

Returns
Type Description
bool

True if and only if the cursor is on the last section of the last line.

is_start_of_document method

def is_start_of_document(self, location):

Check if a location is at the start of the document.

Parameters
Parameter Default Description
location
Location
required

The location to examine.

Returns
Type Description
bool

True if and only if the cursor is at document location (0, 0)

is_start_of_document_line method

def is_start_of_document_line(self, location):

True when the location is at the start of the first document line.

Parameters
Parameter Default Description
location
Location
required

The location to check.

Returns
Type Description
bool

True if the location is at column index 0.

is_start_of_wrapped_line method

def is_start_of_wrapped_line(self, location):

True when the location is at the start of the first wrapped line.

Parameters
Parameter Default Description
location
Location
required

The location to check.

Returns
Type Description
bool

True if the location is at column index 0.

Edit class

Implements the Undoable protocol to replace text at some range within a document.

bottom property

bottom: Location

The Location impacted by this edit that is nearest the end of the document.

from_location instance-attribute

from_location: Location

The start location of the insert.

maintain_selection_offset instance-attribute

maintain_selection_offset: bool

If True, the selection will maintain its offset to the replacement range.

text instance-attribute

text: str

The text to insert. An empty string is equivalent to deletion.

to_location instance-attribute

to_location: Location

The end location of the insert

top property

top: Location

The Location impacted by this edit that is nearest the start of the document.

after method

def after(self, text_area):

Hook for running code after an Edit has been performed via Edit.do and side effects such as re-wrapping the document and refreshing the display have completed.

For example, we can't record cursor visual offset until we know where the cursor will land after wrapping has been performed, so we must wait until here to do it.

Parameters
Parameter Default Description
text_area
TextArea
required

The TextArea this operation was performed on.

do method

def do(self, text_area, record_selection=True):

Perform the edit operation.

Parameters
Parameter Default Description
text_area
TextArea
required

The TextArea to perform the edit on.

record_selection
bool
True

If True, record the current selection in the TextArea so that it may be restored if this Edit is undone in the future.

Returns
Type Description
EditResult

An EditResult containing information about the replace operation.

undo method

def undo(self, text_area):

Undo the edit operation.

Looks at the data stored in the edit, and performs the inverse operation of Edit.do.

Parameters
Parameter Default Description
text_area
TextArea
required

The TextArea to undo the insert operation on.

Returns
Type Description
EditResult

An EditResult containing information about the replace operation.

EditHistory class

Manages batching/checkpointing of Edits into groups that can be undone/redone in the TextArea.

checkpoint_max_characters instance-attribute

checkpoint_max_characters: int

Maximum number of characters that can appear in a batch before a new batch is formed.

checkpoint_timer instance-attribute

checkpoint_timer: float

Maximum number of seconds since last edit until a new batch is created.

redo_stack property

redo_stack: list[list[Edit]]

A copy of the redo stack, with references to the original Edits.

undo_stack property

undo_stack: list[list[Edit]]

A copy of the undo stack, with references to the original Edits.

checkpoint method

def checkpoint(self):

Ensure the next recorded edit starts a new batch.

clear method

def clear(self):

Completely clear the history.

record method

def record(self, edit):

Record an Edit so that it may be undone and redone.

Determines whether to batch the Edit with previous Edits, or create a new batch/checkpoint.

This method must be called exactly once per edit, in chronological order.

A new batch/checkpoint is created when:

  • The undo stack is empty.
  • The checkpoint timer expires.
  • The maximum number of characters permitted in a checkpoint is reached.
  • A redo is performed (we should not add new edits to a batch that has been redone).
  • The programmer has requested a new batch via a call to force_new_batch.
    • e.g. the TextArea widget may call this method in some circumstances.
    • Clicking to move the cursor elsewhere in the document should create a new batch.
    • Movement of the cursor via a keyboard action that is NOT an edit.
    • Blurring the TextArea creates a new checkpoint.
  • The current edit involves a deletion/replacement and the previous edit did not.
  • The current edit is a pure insertion and the previous edit was not.
  • The edit involves insertion or deletion of one or more newline characters.
  • An edit which inserts more than a single character (a paste) gets an isolated batch.
Parameters
Parameter Default Description
edit
Edit
required

The edit to record.

EditResult class

Contains information about an edit that has occurred.

end_location instance-attribute

end_location: Location

The new end Location after the edit is complete.

replaced_text instance-attribute

replaced_text: str

The text that was replaced.

LanguageDoesNotExist class

Bases: Exception

Raised when the user tries to use a language which does not exist. This means a language which is not builtin, or has not been registered.

Selection class

Bases: NamedTuple

A range of characters within a document from a start point to the end point. The location of the cursor is always considered to be the end point of the selection. The selection is inclusive of the minimum point and exclusive of the maximum point.

end instance-attribute class-attribute

end: Location = (0, 0)

The end location of the selection.

If you were to click and drag a selection inside a text-editor, this is where you finished dragging.

is_empty property

is_empty: bool

Return True if the selection has 0 width, i.e. it's just a cursor.

start instance-attribute class-attribute

start: Location = (0, 0)

The start location of the selection.

If you were to click and drag a selection inside a text-editor, this is where you started dragging.

cursor classmethod

def cursor(cls, location):

Create a Selection with the same start and end point - a "cursor".

Parameters
Parameter Default Description
location
Location
required

The location to create the zero-width Selection.

SyntaxAwareDocument class

def __init__(self, text, language):

Bases: Document

A wrapper around a Document which also maintains a tree-sitter syntax tree when the document is edited.

The primary reason for this split is actually to keep tree-sitter stuff separate, since it isn't supported in Python 3.7. By having the tree-sitter code isolated in this subclass, it makes it easier to conditionally import. However, it does come with other design flaws (e.g. Document is required to have methods which only really make sense on SyntaxAwareDocument).

If you're reading this and Python 3.7 is no longer supported by Textual, consider merging this subclass into the Document superclass.

Parameters
Parameter Default Description
text
str
required

The initial text contained in the document.

language
str | Language
required

The language to use. You can pass a string to use a supported language, or pass in your own tree-sitter Language object.

language instance-attribute

language: Language | None = get_language(language)

The tree-sitter Language or None if tree-sitter is unavailable.

get_line method

def get_line(self, line_index):

Return the string representing the line, not including new line characters.

Parameters
Parameter Default Description
line_index
int
required

The index of the line.

Returns
Type Description
str

The string representing the line.

prepare_query method

def prepare_query(self, query):

Prepare a tree-sitter tree query.

Queries should be prepared once, then reused.

To execute a query, call query_syntax_tree.

Parameters
Parameter Default Description
query
str
required

The string query to prepare.

Returns
Type Description
Query | None

The prepared query.

query_syntax_tree method

def query_syntax_tree(
    self, query, start_point=None, end_point=None
):

Query the tree-sitter syntax tree.

The default implementation always returns an empty list.

To support querying in a subclass, this must be implemented.

Parameters
Parameter Default Description
query
Query
required

The tree-sitter Query to perform.

start_point
tuple[int, int] | None
None

The (row, column byte) to start the query at.

end_point
tuple[int, int] | None
None

The (row, column byte) to end the query at.

Returns
Type Description
list[tuple['Node', str]]

A tuple containing the nodes and text captured by the query.

replace_range method

def replace_range(self, start, end, text):

Replace text at the given range.

Parameters
Parameter Default Description
start
Location
required

A tuple (row, column) where the edit starts.

end
Location
required

A tuple (row, column) where the edit ends.

text
str
required

The text to insert between start and end.

Returns
Type Description
EditResult

The new end location after the edit is complete.

TextAreaTheme class

A theme for the TextArea widget.

Allows theming the general widget (gutter, selections, cursor, and so on) and mapping of tree-sitter tokens to Rich styles.

For example, consider the following snippet from the markdown.scm highlight query file. We've assigned the heading_content token type to the name heading.

(heading_content) @heading

Now, we can map this heading name to a Rich style, and it will be styled as such in the TextArea, assuming a parser which returns a heading_content node is used (as will be the case when language="markdown").

TextAreaTheme('my_theme', syntax_styles={'heading': Style(color='cyan', bold=True)})

We can register this theme with our TextArea using the TextArea.register_theme method, and headings in our markdown files will be styled bold cyan.

base_style instance-attribute class-attribute

base_style: Style | None = None

The background style of the text area. If None the parent style will be used.

bracket_matching_style instance-attribute class-attribute

bracket_matching_style: Style | None = None

The style to apply to matching brackets. If None, a legible Style will be generated.

cursor_line_gutter_style instance-attribute class-attribute

cursor_line_gutter_style: Style | None = None

The style to apply to the gutter of the line the cursor is on. If None, a legible Style will be generated.

cursor_line_style instance-attribute class-attribute

cursor_line_style: Style | None = None

The style to apply to the line the cursor is on.

cursor_style instance-attribute class-attribute

cursor_style: Style | None = None

The style of the cursor. If None, a legible Style will be generated.

gutter_style instance-attribute class-attribute

gutter_style: Style | None = None

The style of the gutter. If None, a legible Style will be generated.

name instance-attribute

name: str

The name of the theme.

selection_style instance-attribute class-attribute

selection_style: Style | None = None

The style of the selection. If None a default selection Style will be generated.

syntax_styles instance-attribute class-attribute

syntax_styles: dict[str, Style] = field(
    default_factory=dict
)

The mapping of tree-sitter names from the highlight_query to Rich styles.

apply_css method

def apply_css(self, text_area):

Apply CSS rules from a TextArea to be used for fallback styling.

If any attributes in the theme aren't supplied, they'll be filled with the appropriate base CSS (e.g. color, background, etc.) and component CSS (e.g. text-area--cursor) from the supplied TextArea.

Parameters
Parameter Default Description
text_area
TextArea
required

The TextArea instance to retrieve fallback styling from.

builtin_themes classmethod

def builtin_themes(cls):

Get a list of all builtin TextAreaThemes.

Returns
Type Description
list[TextAreaTheme]

A list of all builtin TextAreaThemes.

get_builtin_theme classmethod

def get_builtin_theme(cls, theme_name):

Get a TextAreaTheme by name.

Given a theme_name, return the corresponding TextAreaTheme object.

Parameters
Parameter Default Description
theme_name
str
required

The name of the theme.

Returns
Type Description
TextAreaTheme | None

The TextAreaTheme corresponding to the name or None if the theme isn't found.

get_highlight method

def get_highlight(self, name):

Return the Rich style corresponding to the name defined in the tree-sitter highlight query for the current theme.

Parameters
Parameter Default Description
name
str
required

The name of the highlight.

Returns
Type Description
Style | None

The Style to use for this highlight, or None if no style.

ThemeDoesNotExist class

Bases: Exception

Raised when the user tries to use a theme which does not exist. This means a theme which is not builtin, or has not been registered.

WrappedDocument class

def __init__(self, document, width=0, tab_width=4):

A view into a Document which wraps the document at a certain width and can be queried to retrieve lines from the wrapped version of the document.

Allows for incremental updates, ensuring that we only re-wrap ranges of the document that were influenced by edits.

By default, a WrappedDocument is wrapped with width=0 (no wrapping). To wrap the document, use the wrap() method.

Parameters
Parameter Default Description
document
DocumentBase
required

The document to wrap.

width
int
0

The width to wrap at.

tab_width
int
4

The maximum width to consider for tab characters.

document instance-attribute

document = document

The document wrapping is performed on.

height property

height: int

The height of the wrapped document.

lines property

lines: list[list[str]]

The lines of the wrapped version of the Document.

Each index in the returned list represents a line index in the raw document. The list[str] at each index is the content of the raw document line split into multiple lines via wrapping.

Note that this is expensive to compute and is not cached.

Returns
Type Description
list[list[str]]

A list of lines from the wrapped version of the document.

wrapped property

wrapped: bool

True if the content is wrapped. This is not the same as wrapping being "enabled". For example, an empty document can have wrapping enabled, but no wrapping has actually occurred.

In other words, this is True if the length of any line in the document is greater than the available width.

get_offsets method

def get_offsets(self, line_index):

Given a line index, get the offsets within that line where wrapping should occur for the current document.

Parameters
Parameter Default Description
line_index
int
required

The index of the line within the document.

Raises
Type Description
ValueError

When line_index is out of bounds.

Returns
Type Description
list[int]

The offsets within the line where wrapping should occur.

get_sections method

def get_sections(self, line_index):

Return the sections for the given line index.

When wrapping is enabled, a single line in the document can visually span multiple lines. The list returned represents that visually (each string in the list represents a single section (y-offset) after wrapping happens).

Parameters
Parameter Default Description
line_index
int
required

The index of the line to get sections for.

Returns
Type Description
list[str]

The wrapped line as a list of strings.

get_tab_widths method

def get_tab_widths(self, line_index):

Return a list of the tab widths for the given line index.

Parameters
Parameter Default Description
line_index
int
required

The index of the line in the document.

Returns
Type Description
list[int]

An ordered list of the expanded width of the tabs in the line.

get_target_document_column method

def get_target_document_column(
    self, line_index, x_offset, y_offset
):

Given a line index and the offsets within the wrapped version of that line, return the corresponding column index in the raw document.

Parameters
Parameter Default Description
line_index
int
required

The index of the line in the document.

x_offset
int
required

The x-offset within the wrapped line.

y_offset
int
required

The y-offset within the wrapped line (supports negative indexing).

Returns
Type Description
int

The column index corresponding to the line index and y offset.

location_to_offset method

def location_to_offset(self, location):

Convert a location in the document to an offset within the wrapped/visual display of the document.

Parameters
Parameter Default Description
location
Location
required

The location in the document.

Returns
Type Description
Offset

The Offset in the document's visual display corresponding to the given location.

offset_to_location method

def offset_to_location(self, offset):

Given an offset within the wrapped/visual display of the document, return the corresponding location in the document.

Parameters
Parameter Default Description
offset
Offset
required

The y-offset within the document.

Raises
Type Description
ValueError

When the given offset does not correspond to a line in the document.

Returns
Type Description
Location

The Location in the document corresponding to the given offset.

wrap method

def wrap(self, width, tab_width=None):

Wrap and cache all lines in the document.

Parameters
Parameter Default Description
width
int
required

The width to wrap at. 0 for no wrapping.

tab_width
int | None
None

The maximum width to consider for tab characters. If None, reuse the tab width.

wrap_range method

def wrap_range(self, start, old_end, new_end):

Incrementally recompute wrapping based on a performed edit.

This must be called after the source document has been edited.

Parameters
Parameter Default Description
start
Location
required

The start location of the edit that was performed in document-space.

old_end
Location
required

The old end location of the edit in document-space.

new_end
Location
required

The new end location of the edit in document-space.