dataframe-textual 2.2.1__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/PKG-INFO +3 -2
  2. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/README.md +2 -1
  3. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/pyproject.toml +1 -1
  4. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/src/dataframe_textual/common.py +8 -0
  5. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/src/dataframe_textual/data_frame_table.py +116 -54
  6. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/src/dataframe_textual/data_frame_viewer.py +13 -8
  7. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/uv.lock +1 -1
  8. dataframe_textual-2.2.1/x +0 -40532
  9. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/.gitignore +0 -0
  10. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/1811.csv.gz +0 -0
  11. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/LICENSE +0 -0
  12. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/large_malformed.tsv.gz +0 -0
  13. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/main.py +0 -0
  14. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/src/dataframe_textual/__init__.py +0 -0
  15. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/src/dataframe_textual/__main__.py +0 -0
  16. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/src/dataframe_textual/data_frame_help_panel.py +0 -0
  17. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/src/dataframe_textual/sql_screen.py +0 -0
  18. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/src/dataframe_textual/table_screen.py +0 -0
  19. {dataframe_textual-2.2.1 → dataframe_textual-2.3.0}/src/dataframe_textual/yes_no_screen.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataframe-textual
3
- Version: 2.2.1
3
+ Version: 2.3.0
4
4
  Summary: Interactive terminal viewer/editor for tabular data
5
5
  Project-URL: Homepage, https://github.com/need47/dataframe-textual
6
6
  Project-URL: Repository, https://github.com/need47/dataframe-textual.git
@@ -302,6 +302,7 @@ zcat compressed_data.csv.gz | dv -f csv
302
302
  | `_` (underscore) | Toggle column full width |
303
303
  | `z` | Freeze rows and columns |
304
304
  | `,` | Toggle thousand separator for numeric display |
305
+ | `&` | Set current row as the new header row |
305
306
  | `h` | Hide current column |
306
307
  | `H` | Show all hidden rows/columns |
307
308
 
@@ -696,7 +697,7 @@ Press `,` to toggle thousand separator formatting for numeric data:
696
697
 
697
698
  ### 15. Save File
698
699
 
699
- Press `Ctrl+S` to save filtered, edited, or sorted data back to file
700
+ Press `Ctrl+S` to save filtered, edited, or sorted data back to file. The output format is automatically determined by the file extension, making it easy to convert between different formats (e.g., CSV to TSV).
700
701
 
701
702
  ### 16. Undo/Redo/Reset
702
703
 
@@ -263,6 +263,7 @@ zcat compressed_data.csv.gz | dv -f csv
263
263
  | `_` (underscore) | Toggle column full width |
264
264
  | `z` | Freeze rows and columns |
265
265
  | `,` | Toggle thousand separator for numeric display |
266
+ | `&` | Set current row as the new header row |
266
267
  | `h` | Hide current column |
267
268
  | `H` | Show all hidden rows/columns |
268
269
 
@@ -657,7 +658,7 @@ Press `,` to toggle thousand separator formatting for numeric data:
657
658
 
658
659
  ### 15. Save File
659
660
 
660
- Press `Ctrl+S` to save filtered, edited, or sorted data back to file
661
+ Press `Ctrl+S` to save filtered, edited, or sorted data back to file. The output format is automatically determined by the file extension, making it easy to convert between different formats (e.g., CSV to TSV).
661
662
 
662
663
  ### 16. Undo/Redo/Reset
663
664
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dataframe-textual"
7
- version = "2.2.1"
7
+ version = "2.3.0"
8
8
  description = "Interactive terminal viewer/editor for tabular data"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -717,6 +717,14 @@ def load_file(
717
717
  # Attempt to collect, handling ComputeError for schema inference issues
718
718
  try:
719
719
  data = [Source(src.frame.collect(), src.filename, src.tabname) for src in data]
720
+ except pl.exceptions.NoDataError:
721
+ print(
722
+ "Warning: No data from stdin."
723
+ if isinstance(source, StringIO)
724
+ else f"Warning: No data found in file `{filename}`.",
725
+ file=sys.stderr,
726
+ )
727
+ sys.exit()
720
728
  except pl.exceptions.ComputeError as ce:
721
729
  # Handle the error and determine retry strategy
722
730
  infer_schema, schema_overrides = handle_compute_error(str(ce), file_format, infer_schema, schema_overrides)
@@ -1,5 +1,6 @@
1
1
  """DataFrameTable widget for displaying and interacting with Polars DataFrames."""
2
2
 
3
+ import io
3
4
  import sys
4
5
  from collections import defaultdict, deque
5
6
  from dataclasses import dataclass
@@ -140,6 +141,7 @@ class DataFrameTable(DataTable):
140
141
  - **Ctrl+F** - 📜 Page down
141
142
  - **Ctrl+B** - 📜 Page up
142
143
  - **PgUp/PgDn** - 📜 Page up/down
144
+ - **&** - 📌 Mark current row as header
143
145
 
144
146
  ## ♻️ Undo/Redo/Reset
145
147
  - **u** - ↩️ Undo last action
@@ -245,9 +247,10 @@ class DataFrameTable(DataTable):
245
247
  ("tilde", "toggle_row_labels", "Toggle row labels"), # `~`
246
248
  ("K", "cycle_cursor_type", "Cycle cursor mode"), # `K`
247
249
  ("z", "freeze_row_column", "Freeze rows/columns"),
248
- ("comma", "show_thousand_separator", "Toggle thousand separator"), # `,`
250
+ ("comma", "toggle_thousand_separator", "Toggle thousand separator"), # `,`
249
251
  ("underscore", "expand_column", "Expand column to full width"), # `_`
250
252
  ("circumflex_accent", "toggle_rid", "Toggle internal row index"), # `^`
253
+ ("ampersand", "set_cursor_row_as_header", "Set cursor row as the new header row"), # `&`
251
254
  # Copy
252
255
  ("c", "copy_cell", "Copy cell to clipboard"),
253
256
  ("ctrl+c", "copy_column", "Copy column to clipboard"),
@@ -725,6 +728,10 @@ class DataFrameTable(DataTable):
725
728
  """Toggle the internal row index column visibility."""
726
729
  self.do_toggle_rid()
727
730
 
731
+ def action_set_cursor_row_as_header(self) -> None:
732
+ """Set cursor row as the new header row."""
733
+ self.do_set_cursor_row_as_header()
734
+
728
735
  def action_show_hidden_rows_columns(self) -> None:
729
736
  """Show all hidden rows/columns."""
730
737
  self.do_show_hidden_rows_columns()
@@ -905,7 +912,7 @@ class DataFrameTable(DataTable):
905
912
  """Toggle row labels visibility."""
906
913
  self.show_row_labels = not self.show_row_labels
907
914
  # status = "shown" if self.show_row_labels else "hidden"
908
- # self.notify(f"Row labels {status}", title="Labels")
915
+ # self.notify(f"Row labels {status}", title="Toggle Row Labels")
909
916
 
910
917
  def action_cast_column_dtype(self, dtype: str | pl.DataType) -> None:
911
918
  """Cast the current column to a different data type."""
@@ -921,8 +928,8 @@ class DataFrameTable(DataTable):
921
928
  self.do_copy_to_clipboard(cell_str, f"Copied: [$success]{cell_str[:50]}[/]")
922
929
  except IndexError:
923
930
  self.notify(
924
- f"Error copying cell ([$error]{ridx}[/], [$accent]{cidx}[/])",
925
- title="Clipboard",
931
+ f"Error copying cell ([$error]{ridx}[/], [$accent]{cidx}[/]) to clipboard",
932
+ title="Copy Cell",
926
933
  severity="error",
927
934
  timeout=10,
928
935
  )
@@ -941,7 +948,12 @@ class DataFrameTable(DataTable):
941
948
  f"Copied [$accent]{len(col_values)}[/] values from column [$success]{col_name}[/]",
942
949
  )
943
950
  except (FileNotFoundError, IndexError):
944
- self.notify(f"Error copying column [$error]{col_name}[/]", title="Clipboard", severity="error", timeout=10)
951
+ self.notify(
952
+ f"Error copying column [$error]{col_name}[/] to clipboard",
953
+ title="Copy Column",
954
+ severity="error",
955
+ timeout=10,
956
+ )
945
957
 
946
958
  def action_copy_row(self) -> None:
947
959
  """Copy the current row to clipboard (values separated by tabs)."""
@@ -957,14 +969,16 @@ class DataFrameTable(DataTable):
957
969
  f"Copied row [$accent]{ridx + 1}[/] with [$success]{len(row_values)}[/] values",
958
970
  )
959
971
  except (FileNotFoundError, IndexError):
960
- self.notify(f"Error copying row [$error]{ridx}[/]", title="Clipboard", severity="error", timeout=10)
972
+ self.notify(
973
+ f"Error copying row [$error]{ridx}[/] to clipboard", title="Copy Row", severity="error", timeout=10
974
+ )
961
975
 
962
- def action_show_thousand_separator(self) -> None:
976
+ def action_toggle_thousand_separator(self) -> None:
963
977
  """Toggle thousand separator for numeric display."""
964
978
  self.thousand_separator = not self.thousand_separator
965
979
  self.setup_table()
966
980
  # status = "enabled" if self.thousand_separator else "disabled"
967
- # self.notify(f"Thousand separator {status}", title="Display")
981
+ # self.notify(f"Thousand separator {status}", title="Toggle Thousand Separator")
968
982
 
969
983
  def action_next_match(self) -> None:
970
984
  """Go to the next matched cell."""
@@ -1344,7 +1358,7 @@ class DataFrameTable(DataTable):
1344
1358
  return range_count
1345
1359
 
1346
1360
  except Exception as e:
1347
- self.notify("Error loading rows", title="Load", severity="error", timeout=10)
1361
+ self.notify("Error loading rows", title="Load Rows", severity="error", timeout=10)
1348
1362
  self.log(f"Error loading rows: {str(e)}")
1349
1363
  return 0
1350
1364
 
@@ -1593,7 +1607,7 @@ class DataFrameTable(DataTable):
1593
1607
  def do_undo(self) -> None:
1594
1608
  """Undo the last action."""
1595
1609
  if not self.histories_undo:
1596
- self.notify("No actions to undo", title="Undo", severity="warning")
1610
+ # self.notify("No actions to undo", title="Undo", severity="warning")
1597
1611
  return
1598
1612
 
1599
1613
  # Pop the last history state for undo and save to redo stack
@@ -1608,7 +1622,7 @@ class DataFrameTable(DataTable):
1608
1622
  def do_redo(self) -> None:
1609
1623
  """Redo the last undone action."""
1610
1624
  if not self.histories_redo:
1611
- self.notify("No actions to redo", title="Redo", severity="warning")
1625
+ # self.notify("No actions to redo", title="Redo", severity="warning")
1612
1626
  return
1613
1627
 
1614
1628
  # Pop the last undone state from redo stack
@@ -1635,7 +1649,7 @@ class DataFrameTable(DataTable):
1635
1649
  next_type = get_next_item(CURSOR_TYPES, self.cursor_type)
1636
1650
  self.cursor_type = next_type
1637
1651
 
1638
- # self.notify(f"Changed cursor type to [$success]{next_type}[/]", title="Cursor")
1652
+ # self.notify(f"Changed cursor type to [$success]{next_type}[/]", title="Cycle Cursor Type")
1639
1653
 
1640
1654
  def do_view_row_detail(self) -> None:
1641
1655
  """Open a modal screen to view the selected row's details."""
@@ -1697,7 +1711,7 @@ class DataFrameTable(DataTable):
1697
1711
  if fixed_columns >= 0:
1698
1712
  self.fixed_columns = fixed_columns
1699
1713
 
1700
- # self.notify(f"Pinned [$success]{fixed_rows}[/] rows and [$accent]{fixed_columns}[/] columns", title="Pin")
1714
+ # self.notify(f"Pinned [$success]{fixed_rows}[/] rows and [$accent]{fixed_columns}[/] columns", title="Pin Row/Column")
1701
1715
 
1702
1716
  def do_hide_column(self) -> None:
1703
1717
  """Hide the currently selected column from the table display."""
@@ -1718,7 +1732,9 @@ class DataFrameTable(DataTable):
1718
1732
  if col_idx >= len(self.columns):
1719
1733
  self.move_cursor(column=len(self.columns) - 1)
1720
1734
 
1721
- # self.notify(f"Hid column [$success]{col_name}[/]. Press [$accent]H[/] to show hidden columns", title="Hide")
1735
+ # self.notify(
1736
+ # f"Hid column [$success]{col_name}[/]. Press [$accent]H[/] to show hidden columns", title="Hide Column"
1737
+ # )
1722
1738
 
1723
1739
  def do_expand_column(self) -> None:
1724
1740
  """Expand the current column to show the widest cell in the loaded data."""
@@ -1773,7 +1789,7 @@ class DataFrameTable(DataTable):
1773
1789
  self._require_update_dimensions = True
1774
1790
  self.refresh(layout=True)
1775
1791
 
1776
- # self.notify(f"Expanded column [$success]{col_name}[/] to width [$accent]{max_width}[/]", title="Expand")
1792
+ # self.notify(f"Expanded column [$success]{col_name}[/] to width [$accent]{max_width}[/]", title="Expand Column")
1777
1793
 
1778
1794
  def do_toggle_rid(self) -> None:
1779
1795
  """Toggle display of the internal RID column."""
@@ -1782,10 +1798,52 @@ class DataFrameTable(DataTable):
1782
1798
  # Recreate table for display
1783
1799
  self.setup_table()
1784
1800
 
1801
+ def do_set_cursor_row_as_header(self) -> None:
1802
+ """Set cursor row as the new header row."""
1803
+ ridx = self.cursor_row_idx
1804
+
1805
+ # Get the new header values
1806
+ new_header = list(self.df.row(ridx))
1807
+ new_header[-1] = RID # Ensure last column remains RID
1808
+
1809
+ # Handle duplicate column names by appending suffixes
1810
+ seen = {}
1811
+ for i, col in enumerate(new_header):
1812
+ if col in seen:
1813
+ seen[col] += 1
1814
+ new_header[i] = f"{col}_{seen[col]}"
1815
+ else:
1816
+ seen[col] = 0
1817
+
1818
+ # Create a mapping of old column names to new column names
1819
+ col_rename_map = {old_col: str(new_col) for old_col, new_col in zip(self.df.columns, new_header)}
1820
+
1821
+ # Add to history
1822
+ self.add_history(f"Set row [$success]{ridx + 1}[/] as header", dirty=False)
1823
+
1824
+ # Rename columns in the dataframe
1825
+ self.df = self.df.slice(ridx + 1).rename(col_rename_map)
1826
+
1827
+ # Write to string buffer
1828
+ buffer = io.StringIO()
1829
+ self.df.write_csv(buffer)
1830
+
1831
+ # Re-read with inferred schema to reset dtypes
1832
+ buffer.seek(0)
1833
+ self.df = pl.read_csv(buffer)
1834
+
1835
+ # Recreate table for display
1836
+ self.setup_table()
1837
+
1838
+ # Move cursor to first column
1839
+ self.move_cursor(row=ridx, column=0)
1840
+
1841
+ # self.notify(f"Set row [$success]{ridx + 1}[/] as header", title="Set Row as Header")
1842
+
1785
1843
  def do_show_hidden_rows_columns(self) -> None:
1786
1844
  """Show all hidden rows/columns by recreating the table."""
1787
1845
  if not self.hidden_columns and self.df_view is None:
1788
- self.notify("No hidden rows or columns to show", title="Show", severity="warning")
1846
+ # self.notify("No hidden rows or columns to show", title="Show Hidden Rows/Columns", severity="warning")
1789
1847
  return
1790
1848
 
1791
1849
  # Add to history
@@ -1802,7 +1860,7 @@ class DataFrameTable(DataTable):
1802
1860
  # Recreate table for display
1803
1861
  self.setup_table()
1804
1862
 
1805
- self.notify("Showed hidden row(s) and/or hidden column(s)", title="Show")
1863
+ # self.notify("Showed hidden row(s) and/or hidden column(s)", title="Show Hidden Rows/Columns")
1806
1864
 
1807
1865
  # Sort
1808
1866
  def do_sort_by_column(self, descending: bool = False) -> None:
@@ -1979,7 +2037,7 @@ class DataFrameTable(DataTable):
1979
2037
  except Exception:
1980
2038
  self.notify(
1981
2039
  f"Error converting [$error]{term}[/] to [$accent]{dtype}[/]. Cast to string.",
1982
- title="Edit",
2040
+ title="Edit Column",
1983
2041
  severity="error",
1984
2042
  )
1985
2043
  expr = pl.lit(str(term))
@@ -2084,7 +2142,7 @@ class DataFrameTable(DataTable):
2084
2142
  # Move cursor to the renamed column
2085
2143
  self.move_cursor(column=col_idx)
2086
2144
 
2087
- # self.notify(f"Renamed column [$success]{col_name}[/] to [$success]{new_name}[/]", title="Column")
2145
+ # self.notify(f"Renamed column [$success]{col_name}[/] to [$success]{new_name}[/]", title="Rename Column")
2088
2146
 
2089
2147
  def do_clear_cell(self) -> None:
2090
2148
  """Clear the current cell by setting its value to None."""
@@ -2296,7 +2354,7 @@ class DataFrameTable(DataTable):
2296
2354
  # Move cursor to the new column
2297
2355
  self.move_cursor(column=cidx + 1)
2298
2356
 
2299
- self.notify(f"Added link column [$success]{new_col_name}[/]. Use Ctrl/Cmd click to open.", title="Add Link")
2357
+ # self.notify(f"Added link column [$success]{new_col_name}[/]. Use Ctrl/Cmd click to open.", title="Add Link")
2300
2358
 
2301
2359
  except Exception as e:
2302
2360
  self.notify(
@@ -2311,7 +2369,7 @@ class DataFrameTable(DataTable):
2311
2369
  try:
2312
2370
  col_name = self.cursor_col_name
2313
2371
  except CellDoesNotExist:
2314
- self.notify("No column to delete at the current cursor position", title="Delete Column", severity="warning")
2372
+ # self.notify("No column to delete at the current cursor position", title="Delete Column", severity="warning")
2315
2373
  return
2316
2374
 
2317
2375
  col_key = self.cursor_col_key
@@ -2378,7 +2436,7 @@ class DataFrameTable(DataTable):
2378
2436
  if self.df_view is not None:
2379
2437
  self.df_view = self.df_view.drop(col_names_to_remove)
2380
2438
 
2381
- self.notify(message, title="Delete Column")
2439
+ # self.notify(message, title="Delete Column")
2382
2440
 
2383
2441
  def do_duplicate_column(self) -> None:
2384
2442
  """Duplicate the currently selected column, inserting it right after the current column."""
@@ -2417,7 +2475,7 @@ class DataFrameTable(DataTable):
2417
2475
  # Move cursor to the new duplicated column
2418
2476
  self.move_cursor(column=col_idx + 1)
2419
2477
 
2420
- # self.notify(f"Duplicated column [$success]{col_name}[/] as [$accent]{new_col_name}[/]", title="Duplicate")
2478
+ # self.notify(f"Duplicated column [$success]{col_name}[/] as [$accent]{new_col_name}[/]", title="Duplicate Column")
2421
2479
 
2422
2480
  def do_delete_row(self, more: str = None) -> None:
2423
2481
  """Delete rows from the table and dataframe.
@@ -2459,7 +2517,7 @@ class DataFrameTable(DataTable):
2459
2517
  try:
2460
2518
  df_filtered = self.df.lazy().filter(~pl.col(RID).is_in(rids_to_delete)).collect()
2461
2519
  except Exception as e:
2462
- self.notify(f"Error deleting row(s): {e}", title="Delete", severity="error", timeout=10)
2520
+ self.notify(f"Error deleting row(s): {e}", title="Delete Row(s)", severity="error", timeout=10)
2463
2521
  self.histories_undo.pop() # Remove last history entry
2464
2522
  return
2465
2523
 
@@ -2486,7 +2544,7 @@ class DataFrameTable(DataTable):
2486
2544
 
2487
2545
  deleted_count = old_count - len(self.df)
2488
2546
  if deleted_count > 0:
2489
- self.notify(f"Deleted [$success]{deleted_count}[/] row(s)", title="Delete")
2547
+ self.notify(f"Deleted [$success]{deleted_count}[/] row(s)", title="Delete Row(s)")
2490
2548
 
2491
2549
  def do_duplicate_row(self) -> None:
2492
2550
  """Duplicate the currently selected row, inserting it right after the current row."""
@@ -2521,7 +2579,7 @@ class DataFrameTable(DataTable):
2521
2579
  # Move cursor to the new duplicated row
2522
2580
  self.move_cursor(row=ridx + 1)
2523
2581
 
2524
- # self.notify(f"Duplicated row [$success]{ridx + 1}[/]", title="Row")
2582
+ # self.notify(f"Duplicated row [$success]{ridx + 1}[/]", title="Duplicate Row")
2525
2583
 
2526
2584
  def do_move_column(self, direction: str) -> None:
2527
2585
  """Move the current column left or right.
@@ -2537,12 +2595,12 @@ class DataFrameTable(DataTable):
2537
2595
  # Validate move is possible
2538
2596
  if direction == "left":
2539
2597
  if col_idx <= 0:
2540
- self.notify("Cannot move column left", title="Move", severity="warning")
2598
+ # self.notify("Cannot move column left", title="Move Column", severity="warning")
2541
2599
  return
2542
2600
  swap_idx = col_idx - 1
2543
2601
  elif direction == "right":
2544
2602
  if col_idx >= len(self.columns) - 1:
2545
- self.notify("Cannot move column right", title="Move", severity="warning")
2603
+ # self.notify("Cannot move column right", title="Move Column", severity="warning")
2546
2604
  return
2547
2605
  swap_idx = col_idx + 1
2548
2606
 
@@ -2583,7 +2641,7 @@ class DataFrameTable(DataTable):
2583
2641
  if self.df_view is not None:
2584
2642
  self.df_view = self.df_view.select(cols)
2585
2643
 
2586
- # self.notify(f"Moved column [$success]{col_name}[/] {direction}", title="Move")
2644
+ # self.notify(f"Moved column [$success]{col_name}[/] {direction}", title="Move Column")
2587
2645
 
2588
2646
  def do_move_row(self, direction: str) -> None:
2589
2647
  """Move the current row up or down.
@@ -2596,12 +2654,12 @@ class DataFrameTable(DataTable):
2596
2654
  # Validate move is possible
2597
2655
  if direction == "up":
2598
2656
  if curr_row_idx <= 0:
2599
- self.notify("Cannot move row up", title="Move", severity="warning")
2657
+ # self.notify("Cannot move row up", title="Move Row", severity="warning")
2600
2658
  return
2601
2659
  swap_row_idx = curr_row_idx - 1
2602
2660
  elif direction == "down":
2603
2661
  if curr_row_idx >= len(self.rows) - 1:
2604
- self.notify("Cannot move row down", title="Move", severity="warning")
2662
+ # self.notify("Cannot move row down", title="Move Row", severity="warning")
2605
2663
  return
2606
2664
  swap_row_idx = curr_row_idx + 1
2607
2665
  else:
@@ -2688,15 +2746,17 @@ class DataFrameTable(DataTable):
2688
2746
  try:
2689
2747
  target_dtype = eval(dtype)
2690
2748
  except Exception:
2691
- self.notify(f"Invalid target data type: [$error]{dtype}[/]", title="Cast", severity="error", timeout=10)
2749
+ self.notify(
2750
+ f"Invalid target data type: [$error]{dtype}[/]", title="Cast Column", severity="error", timeout=10
2751
+ )
2692
2752
  return
2693
2753
 
2694
2754
  if current_dtype == target_dtype:
2695
- self.notify(
2696
- f"Column [$warning]{col_name}[/] is already of type [$accent]{target_dtype}[/]",
2697
- title="Cast",
2698
- severity="warning",
2699
- )
2755
+ # self.notify(
2756
+ # f"Column [$warning]{col_name}[/] is already of type [$accent]{target_dtype}[/]",
2757
+ # title="Cast Column",
2758
+ # severity="warning",
2759
+ # )
2700
2760
  return # No change needed
2701
2761
 
2702
2762
  # Add to history
@@ -2716,11 +2776,11 @@ class DataFrameTable(DataTable):
2716
2776
  # Recreate table for display
2717
2777
  self.setup_table()
2718
2778
 
2719
- self.notify(f"Cast column [$success]{col_name}[/] to [$accent]{target_dtype}[/]", title="Cast")
2779
+ # self.notify(f"Cast column [$success]{col_name}[/] to [$accent]{target_dtype}[/]", title="Cast")
2720
2780
  except Exception as e:
2721
2781
  self.notify(
2722
2782
  f"Error casting column [$error]{col_name}[/] to [$accent]{target_dtype}[/]",
2723
- title="Cast",
2783
+ title="Cast Column",
2724
2784
  severity="error",
2725
2785
  timeout=10,
2726
2786
  )
@@ -2865,7 +2925,7 @@ class DataFrameTable(DataTable):
2865
2925
 
2866
2926
  # Check if we're highlighting or un-highlighting
2867
2927
  if selected_count := len(self.selected_rows):
2868
- self.notify(f"Toggled selection for [$success]{selected_count}[/] rows", title="Toggle")
2928
+ self.notify(f"Toggled selection for [$success]{selected_count}[/] rows", title="Toggle Selection(s)")
2869
2929
 
2870
2930
  # Recreate table for display
2871
2931
  self.setup_table()
@@ -2907,7 +2967,7 @@ class DataFrameTable(DataTable):
2907
2967
  """Clear all selected rows and matches without removing them from the dataframe."""
2908
2968
  # Check if any selected rows or matches
2909
2969
  if not self.selected_rows and not self.matches:
2910
- self.notify("No selections to clear", title="Clear", severity="warning")
2970
+ # self.notify("No selections to clear", title="Clear Selections and Matches", severity="warning")
2911
2971
  return
2912
2972
 
2913
2973
  row_count = len(self.selected_rows | set(self.matches.keys()))
@@ -2922,7 +2982,7 @@ class DataFrameTable(DataTable):
2922
2982
  # Recreate table for display
2923
2983
  self.setup_table()
2924
2984
 
2925
- self.notify(f"Cleared selections for [$success]{row_count}[/] rows", title="Clear")
2985
+ self.notify(f"Cleared selections for [$success]{row_count}[/] rows", title="Clear Selections and Matches")
2926
2986
 
2927
2987
  # Find & Replace
2928
2988
  def find_matches(
@@ -3096,7 +3156,7 @@ class DataFrameTable(DataTable):
3096
3156
  def do_next_match(self) -> None:
3097
3157
  """Move cursor to the next match."""
3098
3158
  if not self.matches:
3099
- self.notify("No matches to navigate", title="Next Match", severity="warning")
3159
+ # self.notify("No matches to navigate", title="Next Match", severity="warning")
3100
3160
  return
3101
3161
 
3102
3162
  # Get sorted list of matched coordinates
@@ -3118,7 +3178,7 @@ class DataFrameTable(DataTable):
3118
3178
  def do_previous_match(self) -> None:
3119
3179
  """Move cursor to the previous match."""
3120
3180
  if not self.matches:
3121
- self.notify("No matches to navigate", title="Previous Match", severity="warning")
3181
+ # self.notify("No matches to navigate", title="Previous Match", severity="warning")
3122
3182
  return
3123
3183
 
3124
3184
  # Get sorted list of matched coordinates
@@ -3146,7 +3206,7 @@ class DataFrameTable(DataTable):
3146
3206
  def do_next_selected_row(self) -> None:
3147
3207
  """Move cursor to the next selected row."""
3148
3208
  if not self.selected_rows:
3149
- self.notify("No selected rows to navigate", title="Next Selected Row", severity="warning")
3209
+ # self.notify("No selected rows to navigate", title="Next Selected Row", severity="warning")
3150
3210
  return
3151
3211
 
3152
3212
  # Get list of selected row indices in order
@@ -3168,7 +3228,7 @@ class DataFrameTable(DataTable):
3168
3228
  def do_previous_selected_row(self) -> None:
3169
3229
  """Move cursor to the previous selected row."""
3170
3230
  if not self.selected_rows:
3171
- self.notify("No selected rows to navigate", title="Previous Selected Row", severity="warning")
3231
+ # self.notify("No selected rows to navigate", title="Previous Selected Row", severity="warning")
3172
3232
  return
3173
3233
 
3174
3234
  # Get list of selected row indices in order
@@ -3567,7 +3627,7 @@ class DataFrameTable(DataTable):
3567
3627
  expr = validate_expr(term, self.df.columns, cidx)
3568
3628
  except Exception as e:
3569
3629
  self.notify(
3570
- f"Error validating expression [$error]{term}[/]", title="Filter", severity="error", timeout=10
3630
+ f"Error validating expression [$error]{term}[/]", title="Filter Rows", severity="error", timeout=10
3571
3631
  )
3572
3632
  self.log(f"Error validating expression `{term}`: {str(e)}")
3573
3633
  return
@@ -3592,7 +3652,9 @@ class DataFrameTable(DataTable):
3592
3652
  term = f"(?i){term}"
3593
3653
  expr = pl.col(col_name).cast(pl.String).str.contains(term)
3594
3654
  self.notify(
3595
- f"Unknown column type [$warning]{dtype}[/]. Cast to string.", title="Filter", severity="warning"
3655
+ f"Unknown column type [$warning]{dtype}[/]. Cast to string.",
3656
+ title="View Rows",
3657
+ severity="warning",
3596
3658
  )
3597
3659
 
3598
3660
  # Lazyframe with row indices
@@ -3605,13 +3667,13 @@ class DataFrameTable(DataTable):
3605
3667
  df_filtered = lf.filter(expr).collect()
3606
3668
  except Exception as e:
3607
3669
  self.histories_undo.pop() # Remove last history entry
3608
- self.notify(f"Error applying filter [$error]{expr_str}[/]", title="Filter", severity="error", timeout=10)
3670
+ self.notify(f"Error applying filter [$error]{expr_str}[/]", title="View Rows", severity="error", timeout=10)
3609
3671
  self.log(f"Error applying filter `{expr_str}`: {str(e)}")
3610
3672
  return
3611
3673
 
3612
3674
  matched_count = len(df_filtered)
3613
3675
  if not matched_count:
3614
- self.notify(f"No rows match the expression: [$success]{expr}[/]", title="Filter", severity="warning")
3676
+ self.notify(f"No rows match the expression: [$success]{expr}[/]", title="View Rows", severity="warning")
3615
3677
  return
3616
3678
 
3617
3679
  # Add to history
@@ -3637,7 +3699,7 @@ class DataFrameTable(DataTable):
3637
3699
  # Recreate table for display
3638
3700
  self.setup_table()
3639
3701
 
3640
- self.notify(f"Filtered to [$success]{matched_count}[/] matching row(s)", title="Filter")
3702
+ self.notify(f"Filtered to [$success]{matched_count}[/] matching row(s)", title="View Rows")
3641
3703
 
3642
3704
  def do_filter_rows(self) -> None:
3643
3705
  """Filter rows.
@@ -3691,7 +3753,7 @@ class DataFrameTable(DataTable):
3691
3753
  # Recreate table for display
3692
3754
  self.setup_table()
3693
3755
 
3694
- self.notify(f"{message}. Now showing [$success]{len(self.df)}[/] rows.", title="Filter")
3756
+ self.notify(f"{message}. Now showing [$success]{len(self.df)}[/] rows.", title="Filter Rows")
3695
3757
 
3696
3758
  # Copy & Save
3697
3759
  def do_copy_to_clipboard(self, content: str, message: str) -> None:
@@ -3713,9 +3775,9 @@ class DataFrameTable(DataTable):
3713
3775
  input=content,
3714
3776
  text=True,
3715
3777
  )
3716
- self.notify(message, title="Clipboard")
3778
+ self.notify(message, title="Copy to Clipboard")
3717
3779
  except FileNotFoundError:
3718
- self.notify("Error copying to clipboard", title="Clipboard", severity="error", timeout=10)
3780
+ self.notify("Error copying to clipboard", title="Copy to Clipboard", severity="error", timeout=10)
3719
3781
 
3720
3782
  def do_save_to_file(self, all_tabs: bool | None = None, task_after_save: str | None = None) -> None:
3721
3783
  """Open screen to save file."""
@@ -137,7 +137,9 @@ class DataFrameViewer(App):
137
137
  except Exception as e:
138
138
  self.notify(
139
139
  f"Error loading [$error]{filename}[/]: Try [$accent]-I[/] to disable schema inference",
140
+ title="Load File",
140
141
  severity="error",
142
+ timeout=10,
141
143
  )
142
144
  self.log(f"Error loading `{filename}`: {str(e)}")
143
145
 
@@ -166,7 +168,7 @@ class DataFrameViewer(App):
166
168
  """
167
169
  if event.key == "k":
168
170
  self.theme = get_next_item(list(BUILTIN_THEMES.keys()), self.theme)
169
- self.notify(f"Switched to theme: [$success]{self.theme}[/]", title="Theme")
171
+ self.notify(f"Switched to theme: [$success]{self.theme}[/]", title="SwitchTheme")
170
172
 
171
173
  def on_click(self, event: Click) -> None:
172
174
  """Handle mouse click events on tabs.
@@ -360,7 +362,7 @@ class DataFrameViewer(App):
360
362
  tabs = self.query_one(ContentTabs)
361
363
  tabs.display = not tabs.display
362
364
  # status = "shown" if tabs.display else "hidden"
363
- # self.notify(f"Tab bar [$success]{status}[/]", title="Toggle")
365
+ # self.notify(f"Tab bar [$success]{status}[/]", title="Toggle Tab Bar")
364
366
 
365
367
  def get_active_table(self) -> DataFrameTable | None:
366
368
  """Get the currently active DataFrameTable widget.
@@ -376,7 +378,8 @@ class DataFrameViewer(App):
376
378
  if active_pane := tabbed.active_pane:
377
379
  return active_pane.query_one(DataFrameTable)
378
380
  except (NoMatches, AttributeError):
379
- self.notify("No active table found", title="Locate", severity="error")
381
+ self.notify("No active table found", title="Locate Table", severity="error", timeout=10)
382
+
380
383
  return None
381
384
 
382
385
  def get_unique_tabname(self, tab_name: str) -> str:
@@ -414,11 +417,13 @@ class DataFrameViewer(App):
414
417
  for source in load_file(filename, prefix_sheet=True):
415
418
  self.add_tab(source.frame, filename, source.tabname, after=self.tabbed.active_pane)
416
419
  n_tab += 1
417
- # self.notify(f"Added [$accent]{n_tab}[/] tab(s) for [$success]{filename}[/]", title="Open")
420
+ # self.notify(f"Added [$accent]{n_tab}[/] tab(s) for [$success]{filename}[/]", title="Open File")
418
421
  except Exception as e:
419
- self.notify(f"Error loading [$error]{filename}[/]: {str(e)}", title="Open", severity="error")
422
+ self.notify(
423
+ f"Error loading [$error]{filename}[/]: {str(e)}", title="Open File", severity="error", timeout=10
424
+ )
420
425
  else:
421
- self.notify(f"File does not exist: [$warning]{filename}[/]", title="Open", severity="warning")
426
+ self.notify(f"File does not exist: [$warning]{filename}[/]", title="Open File", severity="warning")
422
427
 
423
428
  def add_tab(
424
429
  self,
@@ -610,7 +615,7 @@ class DataFrameViewer(App):
610
615
  content_tab, new_name = result
611
616
 
612
617
  # Update the tab name
613
- old_name = content_tab.label_text
618
+ # old_name = content_tab.label_text
614
619
  content_tab.label = new_name
615
620
 
616
621
  # Mark tab as dirty to indicate name change
@@ -622,4 +627,4 @@ class DataFrameViewer(App):
622
627
  table.focus()
623
628
  break
624
629
 
625
- self.notify(f"Renamed tab [$accent]{old_name}[/] to [$success]{new_name}[/]", title="Rename")
630
+ # self.notify(f"Renamed tab [$accent]{old_name}[/] to [$success]{new_name}[/]", title="Rename Tab")
@@ -171,7 +171,7 @@ wheels = [
171
171
 
172
172
  [[package]]
173
173
  name = "dataframe-textual"
174
- version = "2.2.1"
174
+ version = "2.3.0"
175
175
  source = { editable = "." }
176
176
  dependencies = [
177
177
  { name = "polars" },