textual: DataTable render_line redraw bug when using `update_cell()` method

When updating cells in a data table (via the update_cell() method) that would require the column to expand the row is redrawn incorrectly. As you can see in the code the update_width=True parameter is passed to the update_cell() method.

As you can see in VIDEO 1 below, if you insert spaces, the column width actually updates but the remaining characters are not drawn correctly. Oddly enough, when you hover over the cell or when you backspace once the row redraws correctly.

As you can see in VIDEO 2 below, if you add alphanumeric characters, the row draws correctly except for the last character is missing once the text reaches the end of the original column width.

I didn’t have time to fully investigate the issue but the problem seems to stem from calls to the console.render_lines() method and specifically the Styled() renderable that includes Padding(cell, (0, self.cell_padding)). If you change the second argument self.cell_padding of the supplied tuple to 0, things seem to work but then any padding is not accounted for. I ran out of time to dig any deeper, but hopefully this helps.

I ran into this problem inside of a larger project, so I am including a minimal example here that has the same issue and is what was used when creating the videos.

Let me know if you have other questions. Thanks!

# Textual Diagnostics

## Versions

| Name    | Value  |
|---------|--------|
| Textual | 0.40.0 |
| Rich    | 13.5.2 |

## Python

| Name           | Value                                                            |
|----------------|------------------------------------------------------------------|
| Version        | 3.11.6                                                           |
| Implementation | CPython                                                          |
| Compiler       | Clang 15.0.0 (clang-1500.0.40.1)                                 |
| Executable     | /Users/jbd/Dropbox/DEV/projects/textual-play/venv/bin/python3.11 |

## Operating System

| Name    | Value                                                                                                 |
|---------|-------------------------------------------------------------------------------------------------------|
| System  | Darwin                                                                                                |
| Release | 23.0.0                                                                                                |
| Version | Darwin Kernel Version 23.0.0: Fri Sep 15 14:41:43 PDT 2023; root:xnu-10002.1.13~1/RELEASE_ARM64_T6000 |

## Terminal

| Name                 | Value              |
|----------------------|--------------------|
| Terminal Application | iTerm.app (3.4.21) |
| TERM                 | xterm-256color     |
| COLORTERM            | truecolor          |
| FORCE_COLOR          | *Not set*          |
| NO_COLOR             | *Not set*          |

## Rich Console options

| Name           | Value               |
|----------------|---------------------|
| size           | width=81, height=25 |
| legacy_windows | False               |
| min_width      | 1                   |
| max_width      | 81                  |
| is_terminal    | False               |
| encoding       | utf-8               |
| max_height     | 25                  |
| justify        | None                |
| overflow       | None                |
| no_wrap        | False               |
| highlight      | None                |
| markup         | None                |
| height         | None                |

Videos

VIDEO 1 https://github.com/Textualize/textual/assets/44387852/7d521232-4a5d-4969-8a76-00c37f7e78d7

VIDEO 2 https://github.com/Textualize/textual/assets/44387852/81846302-66ff-46ea-b1d9-79e3dbf0e1aa

Minimal Example Code

from textual import on
from textual.app import App, ComposeResult
from textual.events import Ready
from textual.widgets import DataTable, Input

ROWS = [
    ("DSC-BLAH-1-2023-10.jpg", "DSC-BLAH-1-2023-10.jpg"),
    ("DSC-BLAH-2-2023-10.jpg", "DSC-BLAH-2-2023-10.jpg"),
    ("DSC-BLAH-3-2023-10.jpg", "DSC-BLAH-3-2023-10.jpg"),
    ("DSC-BLAH-4-2023-10.jpg", "DSC-BLAH-4-2023-10.jpg"),
    ("DSC-BLAH-5-2023-10.jpg", "DSC-BLAH-5-2023-10.jpg"),
]


class TableApp(App):
    def compose(self) -> ComposeResult:
        yield DataTable(cursor_type="row")
        yield Input(placeholder="Find", id="find")
        yield Input(placeholder="Replace", id="replace")

    def on_mount(self) -> None:
        table = self.query_one(DataTable)
        table.add_column("Current Name", key="current_name")
        table.add_column("New Name", key="new_name")
        table.add_rows(ROWS)

    @on(Ready)
    def focus_input(self) -> None:
        self.query_one("#find", Input).focus()

    @on(Input.Changed, "#find")
    @on(Input.Changed, "#replace")
    def update_data_table(self) -> None:
        dt = self.query_one(DataTable)
        find_str = self.query_one("#find", Input).value
        replace_str = self.query_one("#replace", Input).value
        for row_key in dt.rows:
            name, _ = dt.get_row(row_key)
            if name.find(find_str) >= 0:
                new_name = name.replace(find_str, replace_str)
            else:
                new_name = name
            dt.update_cell(row_key, "new_name", new_name, update_width=True)


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

About this issue

  • Original URL
  • State: closed
  • Created 9 months ago
  • Comments: 16 (12 by maintainers)

Most upvoted comments

Going to re-open this. It should work out-of-the-box

Looking into this now. This is a fairly recent regression - introduced in commit dfba992722, from this PR: https://github.com/Textualize/textual/pull/3213

Glad I might have helped, but I was a bit surprised you closed this issue!

I think this needs further investigation, as updating the DataTable from an Input feels like it should just work.

But I’m not sure of the proper fix and I will leave it up to the Textual maintainers.

If you inspect the lines return in the _render_cell() method, you see all kinds of weirdness (extra spaces, cropped letters, too many spaces) getting returned.

def _render_cell():
    ...

            lines = self.app.console.render_lines(
                Styled(
                    Padding(cell, (0, self.cell_padding)),
                    pre_style=base_style + component_style,
                    post_style=post_style,
                ),
                options,
            )
            # inspect generated lines
            self.log([segment.text for line in lines for segment in line])

    ...

So, it seems, by simply adding +1 to the get_render_width() method from the Column class, the missing characters show correctly. Naturally, this breaks a handful of tests, so I’ll need to look at those closer. You can the characters showing in my example and your example below.

CleanShot 2023-10-14 at 23 43 37

CleanShot 2023-10-14 at 23 44 18

I did notice another oddity that shows up in the current version of the code and also in my +1 version. As you can see in the GIF below, when you press the backspace key, the column width actually grows by 1 (instead of shrinking). It correctly adjusts (temporarily) when the mouse hovers over the row/column.

CleanShot 2023-10-14 at 23 45 10