Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Datatable remove row #2253

Merged
merged 16 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).


## Unreleased

### Added

- Added `DataTable.remove_row` method https://github.com/Textualize/textual/pull/2253
- `Widget.scroll_to_center` now scrolls the widget to the center of the screen https://github.com/Textualize/textual/pull/2255

## [0.19.1] - 2023-04-10
Expand Down
3 changes: 3 additions & 0 deletions src/textual/_two_way_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def __delitem__(self, key: Key) -> None:
self._forward.__delitem__(key)
self._reverse.__delitem__(value)

def __iter__(self):
return iter(self._forward)

def get(self, key: Key) -> Value:
"""Given a key, efficiently lookup and return the associated value.

Expand Down
43 changes: 42 additions & 1 deletion src/textual/widgets/_data_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,9 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
cursor_coordinate: Reactive[Coordinate] = Reactive(
Coordinate(0, 0), repaint=False, always_update=True
)
hover_coordinate: Reactive[Coordinate] = Reactive(Coordinate(0, 0), repaint=False)
hover_coordinate: Reactive[Coordinate] = Reactive(
Coordinate(0, 0), repaint=False, always_update=True
)

class CellHighlighted(Message, bubble=True):
"""Posted when the cursor moves to highlight a new cell.
Expand Down Expand Up @@ -1186,6 +1188,10 @@ def clear(self, columns: bool = False) -> Self:
self._label_column = Column(self._label_column_key, Text(), auto_width=True)
self._labelled_row_exists = False
self.refresh()
self.scroll_x = 0
self.scroll_y = 0
self.scroll_target_x = 0
self.scroll_target_y = 0
return self

def add_column(
Expand Down Expand Up @@ -1322,6 +1328,41 @@ def add_rows(self, rows: Iterable[Iterable[CellType]]) -> list[RowKey]:
row_keys.append(row_key)
return row_keys

def remove_row(self, row_key: RowKey | str) -> None:
"""Remove a row (identified by a key) from the DataTable.

Args:
row_key: The key identifying the row to remove.

Raises:
RowDoesNotExist: If the row key does not exist.
"""
if row_key not in self._row_locations:
raise RowDoesNotExist(f"Row key {row_key!r} is not valid.")

self._require_update_dimensions = True
darrenburns marked this conversation as resolved.
Show resolved Hide resolved
self.check_idle()

index_to_delete = self._row_locations.get(row_key)
new_row_locations = TwoWayDict({})
willmcgugan marked this conversation as resolved.
Show resolved Hide resolved
for row_location_key in self._row_locations:
row_index = self._row_locations.get(row_location_key)
if row_index > index_to_delete:
new_row_locations[row_location_key] = row_index - 1
elif row_index < index_to_delete:
new_row_locations[row_location_key] = row_index

self._row_locations = new_row_locations

del self.rows[row_key]
del self._data[row_key]
rodrigogiraoserrao marked this conversation as resolved.
Show resolved Hide resolved

self.cursor_coordinate = self.cursor_coordinate
self.hover_coordinate = self.hover_coordinate

self._update_count += 1
self.refresh(layout=True)

def on_idle(self) -> None:
"""Runs when the message pump is empty.

Expand Down
158 changes: 158 additions & 0 deletions tests/snapshot_tests/__snapshots__/test_snapshots.ambr

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions tests/snapshot_tests/snapshot_apps/data_table_remove_row.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.widgets import DataTable

ROWS = [
("lane", "swimmer", "country", "time"),
(5, "Chad le Clos", "South Africa", 51.14),
(4, "Joseph Schooling", "Singapore", 50.39),
(2, "Michael Phelps", "United States", 51.14),
(6, "László Cseh", "Hungary", 51.14),
(3, "Li Zhuhao", "China", 51.26),
(8, "Mehdy Metella", "France", 51.58),
(7, "Tom Shields", "United States", 51.73),
(10, "Darren Burns", "Scotland", 51.84),
(1, "Aleksandr Sadovnikov", "Russia", 51.84),
]


class TableApp(App):
"""Snapshot app for testing removal of rows.
Removes several rows, so we can check that the display of the
DataTable updates as expected."""

BINDINGS = [
Binding("r", "remove_row", "Remove Row"),
]

def compose(self) -> ComposeResult:
yield DataTable()

def on_mount(self) -> None:
table = self.query_one(DataTable)
table.focus()
rows = iter(ROWS)
column_labels = next(rows)
for column in column_labels:
table.add_column(column, key=column)

for row in rows:
table.add_row(*row, key=str(row[0]))

def action_remove_row(self):
table = self.query_one(DataTable)
table.remove_row("2")
table.remove_row("8")
table.remove_row("1")


app = TableApp()
if __name__ == "__main__":
app.run()
5 changes: 5 additions & 0 deletions tests/snapshot_tests/test_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ def test_datatable_sort_multikey(snap_compare):
assert snap_compare(SNAPSHOT_APPS_DIR / "data_table_sort.py", press=press)


def test_datatable_remove_row(snap_compare):
press = ["r"]
assert snap_compare(SNAPSHOT_APPS_DIR / "data_table_remove_row.py", press=press)


def test_datatable_labels_and_fixed_data(snap_compare):
# Ensure that we render correctly when there are fixed rows/cols and labels.
assert snap_compare(SNAPSHOT_APPS_DIR / "data_table_row_labels.py")
Expand Down
13 changes: 13 additions & 0 deletions tests/test_data_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,19 @@ async def test_add_columns_user_defined_keys():
assert key == key


async def test_remove_row():
app = DataTableApp()
async with app.run_test():
table = app.query_one(DataTable)
table.add_columns("A", "B")
for row in ROWS:
table.add_row(row, key=row[0])

assert len(table.rows) == 3
table.remove_row(ROWS[0][0])
assert len(table.rows) == 2


async def test_clear():
app = DataTableApp()
async with app.run_test():
Expand Down