dataframe-textual 1.1.5__tar.gz → 1.2.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.
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/PKG-INFO +44 -15
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/README.md +43 -14
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/pyproject.toml +1 -1
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/data_frame_table.py +340 -159
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/table_screen.py +7 -1
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/uv.lock +1 -1
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/.gitignore +0 -0
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/.python-version +0 -0
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/LICENSE +0 -0
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/main.py +0 -0
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/__init__.py +0 -0
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/__main__.py +0 -0
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/common.py +0 -0
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/data_frame_help_panel.py +0 -0
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/data_frame_viewer.py +0 -0
- {dataframe_textual-1.1.5 → dataframe_textual-1.2.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: 1.
|
|
3
|
+
Version: 1.2.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
|
|
@@ -45,11 +45,11 @@ A powerful, interactive terminal-based viewer/editor for CSV/TSV/Excel/Parquet/J
|
|
|
45
45
|
|
|
46
46
|
## Features
|
|
47
47
|
|
|
48
|
-
###
|
|
48
|
+
### Data Viewing
|
|
49
49
|
- 🚀 **Fast Loading** - Powered by Polars for efficient data handling
|
|
50
|
-
- 🎨 **Rich Terminal UI** - Beautiful, color-coded columns with
|
|
50
|
+
- 🎨 **Rich Terminal UI** - Beautiful, color-coded columns with various data types (e.g., integer, float, string)
|
|
51
51
|
- ⌨️ **Comprehensive Keyboard Navigation** - Intuitive controls for browsing, editing, and manipulating data
|
|
52
|
-
- 📊 **Flexible Input** - Read from files or stdin (pipes/redirects)
|
|
52
|
+
- 📊 **Flexible Input** - Read from files and/or stdin (pipes/redirects)
|
|
53
53
|
- 🔄 **Smart Pagination** - Lazy load rows on demand for handling large datasets
|
|
54
54
|
|
|
55
55
|
### Data Manipulation
|
|
@@ -60,7 +60,7 @@ A powerful, interactive terminal-based viewer/editor for CSV/TSV/Excel/Parquet/J
|
|
|
60
60
|
- 💾 **Save & Undo** - Save edits back to file with full undo/redo support
|
|
61
61
|
|
|
62
62
|
### Advanced Features
|
|
63
|
-
- 📂 **Multi-File Support** - Open multiple files in tabs
|
|
63
|
+
- 📂 **Multi-File Support** - Open multiple files in separate tabs
|
|
64
64
|
- 🔄 **Tab Management** - Seamlessly switch between open files with keyboard shortcuts
|
|
65
65
|
- 📌 **Freeze Rows/Columns** - Keep important rows and columns visible while scrolling
|
|
66
66
|
- 🎯 **Cursor Type Cycling** - Switch between cell, row, and column selection modes
|
|
@@ -206,17 +206,21 @@ When multiple files are opened:
|
|
|
206
206
|
| Key | Action |
|
|
207
207
|
|-----|--------|
|
|
208
208
|
| `Double-click` | Edit cell or rename column header |
|
|
209
|
-
| `
|
|
209
|
+
| `delete` | Clear current cell (set to NULL) |
|
|
210
210
|
| `e` | Edit current cell (respects data type) |
|
|
211
211
|
| `E` | Edit entire column with expression |
|
|
212
212
|
| `a` | Add empty column after current |
|
|
213
213
|
| `A` | Add column with name and value/expression |
|
|
214
214
|
| `-` (minus) | Delete current column |
|
|
215
|
+
| `_` (underscore) | Delete current column and all columns after |
|
|
216
|
+
| `Ctrl+-` | Delete current column and all columns before |
|
|
215
217
|
| `x` | Delete current row |
|
|
218
|
+
| `X` | Delete current row and all rows below |
|
|
219
|
+
| `Ctrl+X` | Delete current row and all rows above |
|
|
216
220
|
| `d` | Duplicate current column (appends '_copy' suffix) |
|
|
217
221
|
| `D` | Duplicate current row |
|
|
218
222
|
| `h` | Hide current column |
|
|
219
|
-
| `H` | Show all hidden columns |
|
|
223
|
+
| `H` | Show all hidden rows/columns |
|
|
220
224
|
|
|
221
225
|
#### Searching & Filtering
|
|
222
226
|
|
|
@@ -283,7 +287,8 @@ When multiple files are opened:
|
|
|
283
287
|
| `Ctrl+R` | Copy row to clipboard (tab-separated) |
|
|
284
288
|
| `Ctrl+S` | Save current tab to file |
|
|
285
289
|
| `u` | Undo last action |
|
|
286
|
-
| `U` |
|
|
290
|
+
| `U` | Redo last undone action |
|
|
291
|
+
| `Ctrl+U` | Reset to initial state |
|
|
287
292
|
|
|
288
293
|
## Features in Detail
|
|
289
294
|
|
|
@@ -512,7 +517,7 @@ Press `F` to see how many times each value appears in the current column. The mo
|
|
|
512
517
|
**In the Frequency Table**:
|
|
513
518
|
- Press `[` and `]` to sort by any column (value, count, or percentage)
|
|
514
519
|
- Press `v` to **filter** the main table to show only rows with the selected value
|
|
515
|
-
- Press `"` to **
|
|
520
|
+
- Press `"` to **exclude** all rows except those containing the selected value
|
|
516
521
|
- Press `q` or `Escape` to close the frequency table
|
|
517
522
|
|
|
518
523
|
This is useful for:
|
|
@@ -560,9 +565,25 @@ This is useful for:
|
|
|
560
565
|
- Delete all selected rows (if any) at once
|
|
561
566
|
- Or delete single row at cursor
|
|
562
567
|
|
|
568
|
+
**Delete Row and Below** (`X`):
|
|
569
|
+
- Deletes the current row and all rows below it
|
|
570
|
+
- Useful for removing trailing data or the end of a dataset
|
|
571
|
+
|
|
572
|
+
**Delete Row and Above** (`Ctrl+X`):
|
|
573
|
+
- Deletes the current row and all rows above it
|
|
574
|
+
- Useful for removing leading rows or the beginning of a dataset
|
|
575
|
+
|
|
563
576
|
**Delete Column** (`-`):
|
|
564
577
|
- Removes the entire column from view and dataframe
|
|
565
578
|
|
|
579
|
+
**Delete Column and After** (`_`):
|
|
580
|
+
- Deletes the current column and all columns to the right
|
|
581
|
+
- Useful for removing trailing columns or the end of a dataset
|
|
582
|
+
|
|
583
|
+
**Delete Column and Before** (`Ctrl+-`):
|
|
584
|
+
- Deletes the current column and all columns to the left
|
|
585
|
+
- Useful for removing leading columns or the beginning of a dataset
|
|
586
|
+
|
|
566
587
|
### 9. Hide & Show Columns
|
|
567
588
|
|
|
568
589
|
**Hide Column** (`h`):
|
|
@@ -570,9 +591,8 @@ This is useful for:
|
|
|
570
591
|
- Column data is preserved in the dataframe
|
|
571
592
|
- Hidden columns are included in saves
|
|
572
593
|
|
|
573
|
-
**Show Hidden Columns** (`H`):
|
|
574
|
-
- Restores all previously hidden columns to the display
|
|
575
|
-
- Returns table to full column view
|
|
594
|
+
**Show Hidden Rows/Columns** (`H`):
|
|
595
|
+
- Restores all previously hidden rows/columns to the display
|
|
576
596
|
|
|
577
597
|
This is useful for:
|
|
578
598
|
- Focusing on specific columns without deleting data
|
|
@@ -634,14 +654,23 @@ Press `Ctrl+S` to save:
|
|
|
634
654
|
- Choose filename in modal dialog
|
|
635
655
|
- Confirm if file already exists
|
|
636
656
|
|
|
637
|
-
### 15. Undo/Redo
|
|
657
|
+
### 15. Undo/Redo/Reset
|
|
638
658
|
|
|
639
|
-
|
|
659
|
+
**Undo** (`u`):
|
|
640
660
|
- Reverts last action with full state restoration
|
|
641
661
|
- Works for edits, deletions, sorts, searches, etc.
|
|
642
662
|
- Shows description of reverted action
|
|
643
663
|
|
|
644
|
-
|
|
664
|
+
**Redo** (`U`):
|
|
665
|
+
- Reapplies the last undone action
|
|
666
|
+
- Restores the state before the undo was performed
|
|
667
|
+
- Useful for redoing actions you've undone by mistake
|
|
668
|
+
- Useful for alternating between two different states
|
|
669
|
+
|
|
670
|
+
**Reset** (`Ctrl+U`):
|
|
671
|
+
- Reverts all changes and returns to original data state when file was first loaded
|
|
672
|
+
- Clears all edits, deletions, selections, filters, and sorts
|
|
673
|
+
- Useful for starting fresh without reloading the file
|
|
645
674
|
|
|
646
675
|
### 16. Column Type Conversion
|
|
647
676
|
|
|
@@ -6,11 +6,11 @@ A powerful, interactive terminal-based viewer/editor for CSV/TSV/Excel/Parquet/J
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
###
|
|
9
|
+
### Data Viewing
|
|
10
10
|
- 🚀 **Fast Loading** - Powered by Polars for efficient data handling
|
|
11
|
-
- 🎨 **Rich Terminal UI** - Beautiful, color-coded columns with
|
|
11
|
+
- 🎨 **Rich Terminal UI** - Beautiful, color-coded columns with various data types (e.g., integer, float, string)
|
|
12
12
|
- ⌨️ **Comprehensive Keyboard Navigation** - Intuitive controls for browsing, editing, and manipulating data
|
|
13
|
-
- 📊 **Flexible Input** - Read from files or stdin (pipes/redirects)
|
|
13
|
+
- 📊 **Flexible Input** - Read from files and/or stdin (pipes/redirects)
|
|
14
14
|
- 🔄 **Smart Pagination** - Lazy load rows on demand for handling large datasets
|
|
15
15
|
|
|
16
16
|
### Data Manipulation
|
|
@@ -21,7 +21,7 @@ A powerful, interactive terminal-based viewer/editor for CSV/TSV/Excel/Parquet/J
|
|
|
21
21
|
- 💾 **Save & Undo** - Save edits back to file with full undo/redo support
|
|
22
22
|
|
|
23
23
|
### Advanced Features
|
|
24
|
-
- 📂 **Multi-File Support** - Open multiple files in tabs
|
|
24
|
+
- 📂 **Multi-File Support** - Open multiple files in separate tabs
|
|
25
25
|
- 🔄 **Tab Management** - Seamlessly switch between open files with keyboard shortcuts
|
|
26
26
|
- 📌 **Freeze Rows/Columns** - Keep important rows and columns visible while scrolling
|
|
27
27
|
- 🎯 **Cursor Type Cycling** - Switch between cell, row, and column selection modes
|
|
@@ -167,17 +167,21 @@ When multiple files are opened:
|
|
|
167
167
|
| Key | Action |
|
|
168
168
|
|-----|--------|
|
|
169
169
|
| `Double-click` | Edit cell or rename column header |
|
|
170
|
-
| `
|
|
170
|
+
| `delete` | Clear current cell (set to NULL) |
|
|
171
171
|
| `e` | Edit current cell (respects data type) |
|
|
172
172
|
| `E` | Edit entire column with expression |
|
|
173
173
|
| `a` | Add empty column after current |
|
|
174
174
|
| `A` | Add column with name and value/expression |
|
|
175
175
|
| `-` (minus) | Delete current column |
|
|
176
|
+
| `_` (underscore) | Delete current column and all columns after |
|
|
177
|
+
| `Ctrl+-` | Delete current column and all columns before |
|
|
176
178
|
| `x` | Delete current row |
|
|
179
|
+
| `X` | Delete current row and all rows below |
|
|
180
|
+
| `Ctrl+X` | Delete current row and all rows above |
|
|
177
181
|
| `d` | Duplicate current column (appends '_copy' suffix) |
|
|
178
182
|
| `D` | Duplicate current row |
|
|
179
183
|
| `h` | Hide current column |
|
|
180
|
-
| `H` | Show all hidden columns |
|
|
184
|
+
| `H` | Show all hidden rows/columns |
|
|
181
185
|
|
|
182
186
|
#### Searching & Filtering
|
|
183
187
|
|
|
@@ -244,7 +248,8 @@ When multiple files are opened:
|
|
|
244
248
|
| `Ctrl+R` | Copy row to clipboard (tab-separated) |
|
|
245
249
|
| `Ctrl+S` | Save current tab to file |
|
|
246
250
|
| `u` | Undo last action |
|
|
247
|
-
| `U` |
|
|
251
|
+
| `U` | Redo last undone action |
|
|
252
|
+
| `Ctrl+U` | Reset to initial state |
|
|
248
253
|
|
|
249
254
|
## Features in Detail
|
|
250
255
|
|
|
@@ -473,7 +478,7 @@ Press `F` to see how many times each value appears in the current column. The mo
|
|
|
473
478
|
**In the Frequency Table**:
|
|
474
479
|
- Press `[` and `]` to sort by any column (value, count, or percentage)
|
|
475
480
|
- Press `v` to **filter** the main table to show only rows with the selected value
|
|
476
|
-
- Press `"` to **
|
|
481
|
+
- Press `"` to **exclude** all rows except those containing the selected value
|
|
477
482
|
- Press `q` or `Escape` to close the frequency table
|
|
478
483
|
|
|
479
484
|
This is useful for:
|
|
@@ -521,9 +526,25 @@ This is useful for:
|
|
|
521
526
|
- Delete all selected rows (if any) at once
|
|
522
527
|
- Or delete single row at cursor
|
|
523
528
|
|
|
529
|
+
**Delete Row and Below** (`X`):
|
|
530
|
+
- Deletes the current row and all rows below it
|
|
531
|
+
- Useful for removing trailing data or the end of a dataset
|
|
532
|
+
|
|
533
|
+
**Delete Row and Above** (`Ctrl+X`):
|
|
534
|
+
- Deletes the current row and all rows above it
|
|
535
|
+
- Useful for removing leading rows or the beginning of a dataset
|
|
536
|
+
|
|
524
537
|
**Delete Column** (`-`):
|
|
525
538
|
- Removes the entire column from view and dataframe
|
|
526
539
|
|
|
540
|
+
**Delete Column and After** (`_`):
|
|
541
|
+
- Deletes the current column and all columns to the right
|
|
542
|
+
- Useful for removing trailing columns or the end of a dataset
|
|
543
|
+
|
|
544
|
+
**Delete Column and Before** (`Ctrl+-`):
|
|
545
|
+
- Deletes the current column and all columns to the left
|
|
546
|
+
- Useful for removing leading columns or the beginning of a dataset
|
|
547
|
+
|
|
527
548
|
### 9. Hide & Show Columns
|
|
528
549
|
|
|
529
550
|
**Hide Column** (`h`):
|
|
@@ -531,9 +552,8 @@ This is useful for:
|
|
|
531
552
|
- Column data is preserved in the dataframe
|
|
532
553
|
- Hidden columns are included in saves
|
|
533
554
|
|
|
534
|
-
**Show Hidden Columns** (`H`):
|
|
535
|
-
- Restores all previously hidden columns to the display
|
|
536
|
-
- Returns table to full column view
|
|
555
|
+
**Show Hidden Rows/Columns** (`H`):
|
|
556
|
+
- Restores all previously hidden rows/columns to the display
|
|
537
557
|
|
|
538
558
|
This is useful for:
|
|
539
559
|
- Focusing on specific columns without deleting data
|
|
@@ -595,14 +615,23 @@ Press `Ctrl+S` to save:
|
|
|
595
615
|
- Choose filename in modal dialog
|
|
596
616
|
- Confirm if file already exists
|
|
597
617
|
|
|
598
|
-
### 15. Undo/Redo
|
|
618
|
+
### 15. Undo/Redo/Reset
|
|
599
619
|
|
|
600
|
-
|
|
620
|
+
**Undo** (`u`):
|
|
601
621
|
- Reverts last action with full state restoration
|
|
602
622
|
- Works for edits, deletions, sorts, searches, etc.
|
|
603
623
|
- Shows description of reverted action
|
|
604
624
|
|
|
605
|
-
|
|
625
|
+
**Redo** (`U`):
|
|
626
|
+
- Reapplies the last undone action
|
|
627
|
+
- Restores the state before the undo was performed
|
|
628
|
+
- Useful for redoing actions you've undone by mistake
|
|
629
|
+
- Useful for alternating between two different states
|
|
630
|
+
|
|
631
|
+
**Reset** (`Ctrl+U`):
|
|
632
|
+
- Reverts all changes and returns to original data state when file was first loaded
|
|
633
|
+
- Clears all edits, deletions, selections, filters, and sorts
|
|
634
|
+
- Useful for starting fresh without reloading the file
|
|
606
635
|
|
|
607
636
|
### 16. Column Type Conversion
|
|
608
637
|
|
{dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/data_frame_table.py
RENAMED
|
@@ -104,8 +104,12 @@ class DataFrameTable(DataTable):
|
|
|
104
104
|
- **F** - 📊 Show frequency distribution
|
|
105
105
|
- **s** - 📈 Show statistics for current column
|
|
106
106
|
- **S** - 📊 Show statistics for entire dataframe
|
|
107
|
-
- **
|
|
107
|
+
- **h** - 👁️ Hide current column
|
|
108
|
+
- **H** - 👀 Show all hidden rows/columns
|
|
109
|
+
- **z** - 📌 Freeze rows and columns
|
|
108
110
|
- **~** - 🏷️ Toggle row labels
|
|
111
|
+
- **,** - 🔢 Toggle thousand separator for numeric display
|
|
112
|
+
- **K** - 🔄 Cycle cursor (cell → row → column → cell)
|
|
109
113
|
|
|
110
114
|
## ↕️ Sorting
|
|
111
115
|
- **[** - 🔼 Sort column ascending
|
|
@@ -136,7 +140,7 @@ class DataFrameTable(DataTable):
|
|
|
136
140
|
- **{** - ⬆️ Go to previous selected row
|
|
137
141
|
- **}** - ⬇️ Go to next selected row
|
|
138
142
|
- **"** - 📍 Filter to show only selected rows
|
|
139
|
-
- **T** - 🧹 Clear all selections
|
|
143
|
+
- **T** - 🧹 Clear all selections and matches
|
|
140
144
|
|
|
141
145
|
## ✏️ Edit & Modify
|
|
142
146
|
- **Double-click** - ✍️ Edit cell or rename column header
|
|
@@ -144,13 +148,16 @@ class DataFrameTable(DataTable):
|
|
|
144
148
|
- **E** - 📊 Edit entire column with expression
|
|
145
149
|
- **a** - ➕ Add empty column after current
|
|
146
150
|
- **A** - ➕ Add column with name and optional expression
|
|
147
|
-
- **x** -
|
|
148
|
-
- **X** -
|
|
149
|
-
- **
|
|
151
|
+
- **x** - ❌ Delete current row
|
|
152
|
+
- **X** - ❌ Delete row and those below
|
|
153
|
+
- **Ctrl+X** - ❌ Delete row and those above
|
|
154
|
+
- **delete** - ❌ Clear current cell (set to NULL)
|
|
150
155
|
- **-** - ❌ Delete current column
|
|
156
|
+
- **_** - ❌ Delete column and those after
|
|
157
|
+
- **Ctrl+-** - ❌ Delete column and those before
|
|
151
158
|
- **d** - 📋 Duplicate current column
|
|
152
|
-
- **
|
|
153
|
-
|
|
159
|
+
- **D** - 📋 Duplicate current row
|
|
160
|
+
|
|
154
161
|
|
|
155
162
|
## 🎯 Reorder
|
|
156
163
|
- **Shift+↑↓** - ⬆️⬇️ Move row up/down
|
|
@@ -166,32 +173,39 @@ class DataFrameTable(DataTable):
|
|
|
166
173
|
- **@** - 🔗 Make URLs in current column clickable with Ctrl/Cmd
|
|
167
174
|
|
|
168
175
|
## 💾 Data Management
|
|
169
|
-
- **z** - 📌 Freeze rows and columns
|
|
170
|
-
- **,** - 🔢 Toggle thousand separator for numeric display
|
|
171
176
|
- **c** - 📋 Copy cell to clipboard
|
|
172
177
|
- **Ctrl+c** - 📊 Copy column to clipboard
|
|
173
178
|
- **Ctrl+r** - 📝 Copy row to clipboard (tab-separated)
|
|
174
179
|
- **Ctrl+s** - 💾 Save current tab to file
|
|
175
180
|
- **u** - ↩️ Undo last action
|
|
176
|
-
- **U** - 🔄
|
|
181
|
+
- **U** - 🔄 Redo last undone action
|
|
182
|
+
- **Ctrl+U** - 🔁 Reset to initial state
|
|
177
183
|
""").strip()
|
|
178
184
|
|
|
179
185
|
# fmt: off
|
|
180
186
|
BINDINGS = [
|
|
187
|
+
# Navigation
|
|
181
188
|
("g", "jump_top", "Jump to top"),
|
|
182
189
|
("G", "jump_bottom", "Jump to bottom"),
|
|
190
|
+
# Display
|
|
183
191
|
("h", "hide_column", "Hide column"),
|
|
184
|
-
("H", "
|
|
192
|
+
("H", "show_hidden_rows_columns", "Show hidden rows/columns"),
|
|
193
|
+
("tilde", "toggle_row_labels", "Toggle row labels"), # `~`
|
|
194
|
+
("K", "cycle_cursor_type", "Cycle cursor mode"), # `K`
|
|
195
|
+
("z", "freeze_row_column", "Freeze rows/columns"),
|
|
196
|
+
("comma", "show_thousand_separator", "Toggle thousand separator"), # `,`
|
|
197
|
+
# Copy
|
|
185
198
|
("c", "copy_cell", "Copy cell to clipboard"),
|
|
186
199
|
("ctrl+c", "copy_column", "Copy column to clipboard"),
|
|
187
200
|
("ctrl+r", "copy_row", "Copy row to clipboard"),
|
|
201
|
+
# Save
|
|
188
202
|
("ctrl+s", "save_to_file", "Save to file"),
|
|
203
|
+
# Detail, Frequency, and Statistics
|
|
189
204
|
("enter", "view_row_detail", "View row details"),
|
|
190
|
-
# Frequency & Statistics
|
|
191
205
|
("F", "show_frequency", "Show frequency"),
|
|
192
206
|
("s", "show_statistics", "Show statistics for column"),
|
|
193
207
|
("S", "show_statistics('dataframe')", "Show statistics for dataframe"),
|
|
194
|
-
#
|
|
208
|
+
# Sort
|
|
195
209
|
("left_square_bracket", "sort_ascending", "Sort ascending"), # `[`
|
|
196
210
|
("right_square_bracket", "sort_descending", "Sort descending"), # `]`
|
|
197
211
|
# View
|
|
@@ -215,16 +229,23 @@ class DataFrameTable(DataTable):
|
|
|
215
229
|
# Selection
|
|
216
230
|
("apostrophe", "make_selections", "Toggle row selection"), # `'`
|
|
217
231
|
("t", "toggle_selections", "Toggle all row selections"),
|
|
218
|
-
("T", "
|
|
232
|
+
("T", "clear_selections_and_matches", "Clear selections"),
|
|
219
233
|
("quotation_mark", "filter_selected_rows", "Filter selected"), # `"`
|
|
220
|
-
#
|
|
234
|
+
# Delete
|
|
235
|
+
("delete", "clear_cell", "Clear cell"),
|
|
221
236
|
("minus", "delete_column", "Delete column"), # `-`
|
|
237
|
+
("underscore", "delete_column_and_after", "Delete column and those after"), # `_`
|
|
238
|
+
("ctrl+minus", "delete_column_and_before", "Delete column and those before"), # `Ctrl+-`
|
|
222
239
|
("x", "delete_row", "Delete row"),
|
|
223
|
-
("X", "
|
|
240
|
+
("X", "delete_row_and_below", "Delete row and those below"),
|
|
241
|
+
("ctrl+x", "delete_row_and_up", "Delete row and those up"),
|
|
242
|
+
# Duplicate
|
|
224
243
|
("d", "duplicate_column", "Duplicate column"),
|
|
225
244
|
("D", "duplicate_row", "Duplicate row"),
|
|
245
|
+
# Edit
|
|
226
246
|
("e", "edit_cell", "Edit cell"),
|
|
227
247
|
("E", "edit_column", "Edit column"),
|
|
248
|
+
# Add
|
|
228
249
|
("a", "add_column", "Add column"),
|
|
229
250
|
("A", "add_column_expr", "Add column with expression"),
|
|
230
251
|
# Reorder
|
|
@@ -238,14 +259,10 @@ class DataFrameTable(DataTable):
|
|
|
238
259
|
("exclamation_mark", "cast_column_dtype('bool')", "Cast column dtype to bool"), # `!`
|
|
239
260
|
("dollar_sign", "cast_column_dtype('string')", "Cast column dtype to string"), # `$`
|
|
240
261
|
("at", "make_cell_clickable", "Make cell clickable"), # `@`
|
|
241
|
-
# Misc
|
|
242
|
-
("tilde", "toggle_row_labels", "Toggle row labels"), # `~`
|
|
243
|
-
("K", "cycle_cursor_type", "Cycle cursor mode"), # `K`
|
|
244
|
-
("z", "freeze_row_column", "Freeze rows/columns"),
|
|
245
|
-
("comma", "show_thousand_separator", "Toggle thousand separator"), # `,`
|
|
246
262
|
# Undo/Redo
|
|
247
263
|
("u", "undo", "Undo"),
|
|
248
|
-
("U", "
|
|
264
|
+
("U", "redo", "Redo"),
|
|
265
|
+
("ctrl+u", "reset", "Reset to initial state"),
|
|
249
266
|
]
|
|
250
267
|
# fmt: on
|
|
251
268
|
|
|
@@ -287,8 +304,10 @@ class DataFrameTable(DataTable):
|
|
|
287
304
|
self.fixed_rows = 0 # Number of fixed rows
|
|
288
305
|
self.fixed_columns = 0 # Number of fixed columns
|
|
289
306
|
|
|
290
|
-
# History stack for undo
|
|
307
|
+
# History stack for undo
|
|
291
308
|
self.histories: deque[History] = deque()
|
|
309
|
+
# Current history state for redo
|
|
310
|
+
self.history: History = None
|
|
292
311
|
|
|
293
312
|
# Pending filename for save operations
|
|
294
313
|
self._pending_filename = ""
|
|
@@ -391,17 +410,27 @@ class DataFrameTable(DataTable):
|
|
|
391
410
|
matches.append((ridx, cidx))
|
|
392
411
|
return matches
|
|
393
412
|
|
|
394
|
-
def
|
|
395
|
-
"""
|
|
413
|
+
def get_row_key(self, row_idx: int) -> RowKey:
|
|
414
|
+
"""Get the row key for a given table row index.
|
|
396
415
|
|
|
397
|
-
|
|
398
|
-
|
|
416
|
+
Args:
|
|
417
|
+
row_idx: Row index in the table display.
|
|
399
418
|
|
|
400
419
|
Returns:
|
|
401
|
-
|
|
420
|
+
Corresponding row key as string.
|
|
402
421
|
"""
|
|
403
|
-
|
|
404
|
-
|
|
422
|
+
return self._row_locations.get_key(row_idx)
|
|
423
|
+
|
|
424
|
+
def get_column_key(self, col_idx: int) -> ColumnKey:
|
|
425
|
+
"""Get the column key for a given table column index.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
col_idx: Column index in the table display.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
Corresponding column key as string.
|
|
432
|
+
"""
|
|
433
|
+
return self._column_locations.get_key(col_idx)
|
|
405
434
|
|
|
406
435
|
def _should_highlight(self, cursor: Coordinate, target_cell: Coordinate, type_of_cursor: CursorType) -> bool:
|
|
407
436
|
"""Determine if the given cell should be highlighted because of the cursor.
|
|
@@ -486,6 +515,30 @@ class DataFrameTable(DataTable):
|
|
|
486
515
|
else:
|
|
487
516
|
self._scroll_cursor_into_view()
|
|
488
517
|
|
|
518
|
+
def move_cursor_to(self, ridx: int, cidx: int) -> None:
|
|
519
|
+
"""Move cursor based on the dataframe indices.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
ridx: Row index (0-based) in the dataframe.
|
|
523
|
+
cidx: Column index (0-based) in the dataframe.
|
|
524
|
+
"""
|
|
525
|
+
row_key = str(ridx)
|
|
526
|
+
col_key = self.df.columns[cidx]
|
|
527
|
+
row_idx, col_idx = self.get_cell_coordinate(row_key, col_key)
|
|
528
|
+
self.move_cursor(row=row_idx, column=col_idx)
|
|
529
|
+
|
|
530
|
+
def on_mount(self) -> None:
|
|
531
|
+
"""Initialize table display when the widget is mounted.
|
|
532
|
+
|
|
533
|
+
Called by Textual when the widget is first added to the display tree.
|
|
534
|
+
Currently a placeholder as table setup is deferred until first use.
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
None
|
|
538
|
+
"""
|
|
539
|
+
# self._setup_table()
|
|
540
|
+
pass
|
|
541
|
+
|
|
489
542
|
def on_key(self, event) -> None:
|
|
490
543
|
"""Handle key press events for pagination.
|
|
491
544
|
|
|
@@ -544,13 +597,21 @@ class DataFrameTable(DataTable):
|
|
|
544
597
|
"""Delete the current column."""
|
|
545
598
|
self._delete_column()
|
|
546
599
|
|
|
600
|
+
def action_delete_column_and_after(self) -> None:
|
|
601
|
+
"""Delete the current column and those after."""
|
|
602
|
+
self._delete_column(more="after")
|
|
603
|
+
|
|
604
|
+
def action_delete_column_and_before(self) -> None:
|
|
605
|
+
"""Delete the current column and those before."""
|
|
606
|
+
self._delete_column(more="before")
|
|
607
|
+
|
|
547
608
|
def action_hide_column(self) -> None:
|
|
548
609
|
"""Hide the current column."""
|
|
549
610
|
self._hide_column()
|
|
550
611
|
|
|
551
|
-
def
|
|
552
|
-
"""Show all hidden columns."""
|
|
553
|
-
self.
|
|
612
|
+
def action_show_hidden_rows_columns(self) -> None:
|
|
613
|
+
"""Show all hidden rows/columns."""
|
|
614
|
+
self._show_hidden_rows_columns()
|
|
554
615
|
|
|
555
616
|
def action_sort_ascending(self) -> None:
|
|
556
617
|
"""Sort by current column in ascending order."""
|
|
@@ -656,6 +717,14 @@ class DataFrameTable(DataTable):
|
|
|
656
717
|
"""Delete the current row."""
|
|
657
718
|
self._delete_row()
|
|
658
719
|
|
|
720
|
+
def action_delete_row_and_below(self) -> None:
|
|
721
|
+
"""Delete the current row and those below."""
|
|
722
|
+
self._delete_row(more="below")
|
|
723
|
+
|
|
724
|
+
def action_delete_row_and_up(self) -> None:
|
|
725
|
+
"""Delete the current row and those above."""
|
|
726
|
+
self._delete_row(more="above")
|
|
727
|
+
|
|
659
728
|
def action_duplicate_column(self) -> None:
|
|
660
729
|
"""Duplicate the current column."""
|
|
661
730
|
self._duplicate_column()
|
|
@@ -668,10 +737,14 @@ class DataFrameTable(DataTable):
|
|
|
668
737
|
"""Undo the last action."""
|
|
669
738
|
self._undo()
|
|
670
739
|
|
|
740
|
+
def action_redo(self) -> None:
|
|
741
|
+
"""Redo the last undone action."""
|
|
742
|
+
self._redo()
|
|
743
|
+
|
|
671
744
|
def action_reset(self) -> None:
|
|
672
|
-
"""Reset to the
|
|
745
|
+
"""Reset to the initial state."""
|
|
673
746
|
self._setup_table(reset=True)
|
|
674
|
-
self.notify("Restored
|
|
747
|
+
self.notify("Restored initial state", title="Reset")
|
|
675
748
|
|
|
676
749
|
def action_move_column_left(self) -> None:
|
|
677
750
|
"""Move the current column to the left."""
|
|
@@ -689,9 +762,9 @@ class DataFrameTable(DataTable):
|
|
|
689
762
|
"""Move the current row down."""
|
|
690
763
|
self._move_row("down")
|
|
691
764
|
|
|
692
|
-
def
|
|
693
|
-
"""Clear all row selections."""
|
|
694
|
-
self.
|
|
765
|
+
def action_clear_selections_and_matches(self) -> None:
|
|
766
|
+
"""Clear all row selections and matches."""
|
|
767
|
+
self._clear_selections_and_matches()
|
|
695
768
|
|
|
696
769
|
def action_cycle_cursor_type(self) -> None:
|
|
697
770
|
"""Cycle through cursor types."""
|
|
@@ -781,41 +854,6 @@ class DataFrameTable(DataTable):
|
|
|
781
854
|
"""Go to the previous selected row."""
|
|
782
855
|
self._previous_selected_row()
|
|
783
856
|
|
|
784
|
-
def _make_cell_clickable(self) -> None:
|
|
785
|
-
"""Make cells with URLs in the current column clickable.
|
|
786
|
-
|
|
787
|
-
Scans all loaded rows in the current column for cells containing URLs
|
|
788
|
-
(starting with 'http://' or 'https://') and applies Textual link styling
|
|
789
|
-
to make them clickable. Does not modify the dataframe.
|
|
790
|
-
|
|
791
|
-
Returns:
|
|
792
|
-
None
|
|
793
|
-
"""
|
|
794
|
-
cidx = self.cursor_col_idx
|
|
795
|
-
col_key = self.cursor_col_key
|
|
796
|
-
dtype = self.df.dtypes[cidx]
|
|
797
|
-
|
|
798
|
-
# Only process string columns
|
|
799
|
-
if dtype != pl.String:
|
|
800
|
-
return
|
|
801
|
-
|
|
802
|
-
# Count how many URLs were made clickable
|
|
803
|
-
url_count = 0
|
|
804
|
-
|
|
805
|
-
# Iterate through all loaded rows and make URLs clickable
|
|
806
|
-
for row in self.ordered_rows:
|
|
807
|
-
cell_text: Text = self.get_cell(row.key, col_key)
|
|
808
|
-
if cell_text.plain.startswith(("http://", "https://")):
|
|
809
|
-
cell_text.style = f"#00afff link {cell_text.plain}" # sky blue
|
|
810
|
-
self.update_cell(row.key, col_key, cell_text)
|
|
811
|
-
url_count += 1
|
|
812
|
-
|
|
813
|
-
if url_count:
|
|
814
|
-
self.notify(
|
|
815
|
-
f"Made [$accent]{url_count}[/] cell(s) clickable in column [$success]{col_key.value}[/]",
|
|
816
|
-
title="Make Clickable",
|
|
817
|
-
)
|
|
818
|
-
|
|
819
857
|
def on_mouse_scroll_down(self, event) -> None:
|
|
820
858
|
"""Load more rows when scrolling down with mouse."""
|
|
821
859
|
self._check_and_load_more()
|
|
@@ -909,6 +947,7 @@ class DataFrameTable(DataTable):
|
|
|
909
947
|
for row_idx, row in enumerate(df_slice.rows(), start):
|
|
910
948
|
if not self.visible_rows[row_idx]:
|
|
911
949
|
continue # Skip hidden rows
|
|
950
|
+
|
|
912
951
|
vals, dtypes = [], []
|
|
913
952
|
for val, col, dtype in zip(row, self.df.columns, self.df.dtypes):
|
|
914
953
|
if col in self.hidden_columns:
|
|
@@ -916,6 +955,7 @@ class DataFrameTable(DataTable):
|
|
|
916
955
|
vals.append(val)
|
|
917
956
|
dtypes.append(dtype)
|
|
918
957
|
formatted_row = format_row(vals, dtypes, thousand_separator=self.thousand_separator)
|
|
958
|
+
|
|
919
959
|
# Always add labels so they can be shown/hidden via CSS
|
|
920
960
|
self.add_row(*formatted_row, key=str(row_idx), label=str(row_idx + 1))
|
|
921
961
|
|
|
@@ -937,51 +977,59 @@ class DataFrameTable(DataTable):
|
|
|
937
977
|
if bottom_visible_row >= self.loaded_rows - 10:
|
|
938
978
|
self._load_rows(self.loaded_rows + self.BATCH_SIZE)
|
|
939
979
|
|
|
940
|
-
def _do_highlight(self,
|
|
980
|
+
def _do_highlight(self, force: bool = False) -> None:
|
|
941
981
|
"""Update all rows, highlighting selected ones and restoring others to default.
|
|
942
982
|
|
|
943
983
|
Args:
|
|
944
|
-
|
|
984
|
+
force: If True, clear all highlights and restore default styles.
|
|
945
985
|
"""
|
|
946
|
-
if clear:
|
|
947
|
-
self.selected_rows = [False] * len(self.df)
|
|
948
|
-
self.matches = defaultdict(set)
|
|
949
|
-
|
|
950
986
|
# Ensure all selected rows or matches are loaded
|
|
951
987
|
stop = rindex(self.selected_rows, True) + 1
|
|
952
988
|
stop = max(stop, max(self.matches.keys(), default=0) + 1)
|
|
953
989
|
|
|
954
990
|
self._load_rows(stop)
|
|
955
|
-
self._highlight_table()
|
|
991
|
+
self._highlight_table(force)
|
|
956
992
|
|
|
957
|
-
def _highlight_table(self) -> None:
|
|
993
|
+
def _highlight_table(self, force: bool = False) -> None:
|
|
958
994
|
"""Highlight selected rows/cells in red."""
|
|
995
|
+
if not force and not any(self.selected_rows) and not self.matches:
|
|
996
|
+
return # Nothing to highlight
|
|
997
|
+
|
|
959
998
|
# Update all rows based on selected state
|
|
960
999
|
for row in self.ordered_rows:
|
|
961
|
-
|
|
962
|
-
is_selected = self.selected_rows[
|
|
963
|
-
match_cols = self.matches.get(
|
|
1000
|
+
ridx = int(row.key.value) # 0-based index
|
|
1001
|
+
is_selected = self.selected_rows[ridx]
|
|
1002
|
+
match_cols = self.matches.get(ridx, set())
|
|
1003
|
+
|
|
1004
|
+
if not force and not is_selected and not match_cols:
|
|
1005
|
+
continue # No highlight needed for this row
|
|
964
1006
|
|
|
965
1007
|
# Update all cells in this row
|
|
966
1008
|
for col_idx, col in enumerate(self.ordered_columns):
|
|
967
|
-
|
|
1009
|
+
if not force and not is_selected and col_idx not in match_cols:
|
|
1010
|
+
continue # No highlight needed for this cell
|
|
968
1011
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1012
|
+
cell_text: Text = self.get_cell(row.key, col.key)
|
|
1013
|
+
need_update = False
|
|
1014
|
+
|
|
1015
|
+
if is_selected or col_idx in match_cols:
|
|
1016
|
+
cell_text.style = "red"
|
|
1017
|
+
need_update = True
|
|
1018
|
+
elif force:
|
|
1019
|
+
# Restore original style based on dtype
|
|
1020
|
+
dtype = self.df.schema[col.key.value]
|
|
1021
|
+
dc = DtypeConfig(dtype)
|
|
1022
|
+
cell_text.style = dc.style
|
|
1023
|
+
need_update = True
|
|
973
1024
|
|
|
974
1025
|
# Update the cell in the table
|
|
975
|
-
|
|
1026
|
+
if need_update:
|
|
1027
|
+
self.update_cell(row.key, col.key, cell_text)
|
|
976
1028
|
|
|
977
1029
|
# History & Undo
|
|
978
|
-
def
|
|
979
|
-
"""
|
|
980
|
-
|
|
981
|
-
Args:
|
|
982
|
-
description: Description of the action for this history entry.
|
|
983
|
-
"""
|
|
984
|
-
history = History(
|
|
1030
|
+
def _create_history(self, description: str) -> None:
|
|
1031
|
+
"""Create the initial history state."""
|
|
1032
|
+
return History(
|
|
985
1033
|
description=description,
|
|
986
1034
|
df=self.df,
|
|
987
1035
|
filename=self.filename,
|
|
@@ -995,16 +1043,12 @@ class DataFrameTable(DataTable):
|
|
|
995
1043
|
cursor_coordinate=self.cursor_coordinate,
|
|
996
1044
|
matches={k: v.copy() for k, v in self.matches.items()},
|
|
997
1045
|
)
|
|
998
|
-
self.histories.append(history)
|
|
999
1046
|
|
|
1000
|
-
def
|
|
1001
|
-
"""
|
|
1002
|
-
if
|
|
1003
|
-
self.notify("No actions to undo", title="Undo", severity="warning")
|
|
1047
|
+
def _apply_history(self, history: History) -> None:
|
|
1048
|
+
"""Apply the current history state to the table."""
|
|
1049
|
+
if history is None:
|
|
1004
1050
|
return
|
|
1005
1051
|
|
|
1006
|
-
history = self.histories.pop()
|
|
1007
|
-
|
|
1008
1052
|
# Restore state
|
|
1009
1053
|
self.df = history.df
|
|
1010
1054
|
self.filename = history.filename
|
|
@@ -1016,13 +1060,54 @@ class DataFrameTable(DataTable):
|
|
|
1016
1060
|
self.fixed_rows = history.fixed_rows
|
|
1017
1061
|
self.fixed_columns = history.fixed_columns
|
|
1018
1062
|
self.cursor_coordinate = history.cursor_coordinate
|
|
1019
|
-
self.matches = {k: v.copy() for k, v in history.matches.items()}
|
|
1063
|
+
self.matches = {k: v.copy() for k, v in history.matches.items()} if history.matches else defaultdict(set)
|
|
1020
1064
|
|
|
1021
1065
|
# Recreate the table for display
|
|
1022
1066
|
self._setup_table()
|
|
1023
1067
|
|
|
1068
|
+
def _add_history(self, description: str) -> None:
|
|
1069
|
+
"""Add the current state to the history stack.
|
|
1070
|
+
|
|
1071
|
+
Args:
|
|
1072
|
+
description: Description of the action for this history entry.
|
|
1073
|
+
"""
|
|
1074
|
+
history = self._create_history(description)
|
|
1075
|
+
self.histories.append(history)
|
|
1076
|
+
|
|
1077
|
+
def _undo(self) -> None:
|
|
1078
|
+
"""Undo the last action."""
|
|
1079
|
+
if not self.histories:
|
|
1080
|
+
self.notify("No actions to undo", title="Undo", severity="warning")
|
|
1081
|
+
return
|
|
1082
|
+
|
|
1083
|
+
# Save current state for redo
|
|
1084
|
+
self.history = self._create_history("Redo state")
|
|
1085
|
+
|
|
1086
|
+
# Pop the last history state for undo
|
|
1087
|
+
history = self.histories.pop()
|
|
1088
|
+
|
|
1089
|
+
# Restore state
|
|
1090
|
+
self._apply_history(history)
|
|
1091
|
+
|
|
1024
1092
|
# self.notify(f"Reverted: {history.description}", title="Undo")
|
|
1025
1093
|
|
|
1094
|
+
def _redo(self) -> None:
|
|
1095
|
+
"""Redo the last undone action."""
|
|
1096
|
+
if self.history is None:
|
|
1097
|
+
self.notify("No actions to redo", title="Redo", severity="warning")
|
|
1098
|
+
return
|
|
1099
|
+
|
|
1100
|
+
# Save current state for undo
|
|
1101
|
+
self._add_history("Undo state")
|
|
1102
|
+
|
|
1103
|
+
# Restore state
|
|
1104
|
+
self._apply_history(self.history)
|
|
1105
|
+
|
|
1106
|
+
# Clear redo state
|
|
1107
|
+
self.history = None
|
|
1108
|
+
|
|
1109
|
+
# self.notify(f"Reapplied: {history.description}", title="Redo")
|
|
1110
|
+
|
|
1026
1111
|
# View
|
|
1027
1112
|
def _view_row_detail(self) -> None:
|
|
1028
1113
|
"""Open a modal screen to view the selected row's details."""
|
|
@@ -1071,9 +1156,9 @@ class DataFrameTable(DataTable):
|
|
|
1071
1156
|
self._add_history(f"Pinned [$accent]{fixed_rows}[/] rows and [$success]{fixed_columns}[/] columns")
|
|
1072
1157
|
|
|
1073
1158
|
# Apply the pin settings to the table
|
|
1074
|
-
if fixed_rows
|
|
1159
|
+
if fixed_rows >= 0:
|
|
1075
1160
|
self.fixed_rows = fixed_rows
|
|
1076
|
-
if fixed_columns
|
|
1161
|
+
if fixed_columns >= 0:
|
|
1077
1162
|
self.fixed_columns = fixed_columns
|
|
1078
1163
|
|
|
1079
1164
|
self.notify(
|
|
@@ -1082,38 +1167,69 @@ class DataFrameTable(DataTable):
|
|
|
1082
1167
|
)
|
|
1083
1168
|
|
|
1084
1169
|
# Delete & Move
|
|
1085
|
-
def _delete_column(self) -> None:
|
|
1170
|
+
def _delete_column(self, more: str = None) -> None:
|
|
1086
1171
|
"""Remove the currently selected column from the table."""
|
|
1087
1172
|
# Get the column to remove
|
|
1088
1173
|
col_idx = self.cursor_column
|
|
1089
1174
|
col_name = self.cursor_col_name
|
|
1090
1175
|
col_key = self.cursor_col_key
|
|
1091
1176
|
|
|
1177
|
+
col_names_to_remove = []
|
|
1178
|
+
col_keys_to_remove = []
|
|
1179
|
+
|
|
1180
|
+
# Remove all columns before the current column
|
|
1181
|
+
if more == "before":
|
|
1182
|
+
for i in range(col_idx + 1):
|
|
1183
|
+
col_key = self.get_column_key(i)
|
|
1184
|
+
col_names_to_remove.append(col_key.value)
|
|
1185
|
+
col_keys_to_remove.append(col_key)
|
|
1186
|
+
|
|
1187
|
+
descr = f"Removed column [$success]{col_name}[/] and all columns before"
|
|
1188
|
+
|
|
1189
|
+
# Remove all columns after the current column
|
|
1190
|
+
elif more == "after":
|
|
1191
|
+
for i in range(col_idx, len(self.columns)):
|
|
1192
|
+
col_key = self.get_column_key(i)
|
|
1193
|
+
col_names_to_remove.append(col_key.value)
|
|
1194
|
+
col_keys_to_remove.append(col_key)
|
|
1195
|
+
|
|
1196
|
+
descr = f"Removed column [$success]{col_name}[/] and all columns after"
|
|
1197
|
+
|
|
1198
|
+
# Remove only the current column
|
|
1199
|
+
else:
|
|
1200
|
+
col_names_to_remove.append(col_name)
|
|
1201
|
+
col_keys_to_remove.append(col_key)
|
|
1202
|
+
descr = f"Removed column [$success]{col_name}[/]"
|
|
1203
|
+
|
|
1092
1204
|
# Add to history
|
|
1093
|
-
self._add_history(
|
|
1205
|
+
self._add_history(descr)
|
|
1094
1206
|
|
|
1095
|
-
# Remove the
|
|
1096
|
-
|
|
1207
|
+
# Remove the columns from the table display using the column names as keys
|
|
1208
|
+
for ck in col_keys_to_remove:
|
|
1209
|
+
self.remove_column(ck)
|
|
1097
1210
|
|
|
1098
|
-
# Move cursor left if we deleted the last column
|
|
1099
|
-
|
|
1100
|
-
|
|
1211
|
+
# Move cursor left if we deleted the last column(s)
|
|
1212
|
+
last_col_idx = len(self.columns) - 1
|
|
1213
|
+
if col_idx > last_col_idx:
|
|
1214
|
+
self.move_cursor(column=last_col_idx)
|
|
1101
1215
|
|
|
1102
1216
|
# Remove from sorted columns if present
|
|
1103
|
-
|
|
1104
|
-
|
|
1217
|
+
for col_name in col_names_to_remove:
|
|
1218
|
+
if col_name in self.sorted_columns:
|
|
1219
|
+
del self.sorted_columns[col_name]
|
|
1105
1220
|
|
|
1106
1221
|
# Remove from matches
|
|
1222
|
+
col_indices_to_remove = set(self.df.columns.index(name) for name in col_names_to_remove)
|
|
1107
1223
|
for row_idx in list(self.matches.keys()):
|
|
1108
|
-
self.matches[row_idx].
|
|
1224
|
+
self.matches[row_idx].difference_update(col_indices_to_remove)
|
|
1109
1225
|
# Remove empty entries
|
|
1110
1226
|
if not self.matches[row_idx]:
|
|
1111
1227
|
del self.matches[row_idx]
|
|
1112
1228
|
|
|
1113
1229
|
# Remove from dataframe
|
|
1114
|
-
self.df = self.df.drop(
|
|
1230
|
+
self.df = self.df.drop(col_names_to_remove)
|
|
1115
1231
|
|
|
1116
|
-
self.notify(
|
|
1232
|
+
# self.notify(descr, title="Delete")
|
|
1117
1233
|
|
|
1118
1234
|
def _hide_column(self) -> None:
|
|
1119
1235
|
"""Hide the currently selected column from the table display."""
|
|
@@ -1136,28 +1252,32 @@ class DataFrameTable(DataTable):
|
|
|
1136
1252
|
|
|
1137
1253
|
# self.notify(f"Hid column [$accent]{col_name}[/]. Press [$success]H[/] to show hidden columns", title="Hide")
|
|
1138
1254
|
|
|
1139
|
-
def
|
|
1140
|
-
"""Show all hidden columns by recreating the table
|
|
1255
|
+
def _show_hidden_rows_columns(self) -> None:
|
|
1256
|
+
"""Show all hidden rows/columns by recreating the table."""
|
|
1141
1257
|
# Get currently visible columns
|
|
1142
1258
|
visible_cols = set(col.key for col in self.ordered_columns)
|
|
1143
1259
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1260
|
+
hidden_row_count = sum(0 if visible else 1 for visible in self.visible_rows)
|
|
1261
|
+
hidden_col_count = sum(0 if col in visible_cols else 1 for col in self.df.columns)
|
|
1146
1262
|
|
|
1147
|
-
if not
|
|
1148
|
-
self.notify("No hidden columns to show", title="
|
|
1263
|
+
if not hidden_row_count and not hidden_col_count:
|
|
1264
|
+
self.notify("No hidden columns or rows to show", title="Show", severity="warning")
|
|
1149
1265
|
return
|
|
1150
1266
|
|
|
1151
1267
|
# Add to history
|
|
1152
|
-
self._add_history(
|
|
1268
|
+
self._add_history("Showed hidden rows/columns")
|
|
1153
1269
|
|
|
1154
|
-
# Clear hidden columns tracking
|
|
1270
|
+
# Clear hidden rows/columns tracking
|
|
1271
|
+
self.visible_rows = [True] * len(self.df)
|
|
1155
1272
|
self.hidden_columns.clear()
|
|
1156
1273
|
|
|
1157
|
-
# Recreate table
|
|
1274
|
+
# Recreate table for display
|
|
1158
1275
|
self._setup_table()
|
|
1159
1276
|
|
|
1160
|
-
self.notify(
|
|
1277
|
+
self.notify(
|
|
1278
|
+
f"Showed [$accent]{hidden_row_count}[/] hidden row(s) and/or [$accent]{hidden_col_count}[/] hidden column(s)",
|
|
1279
|
+
title="Show",
|
|
1280
|
+
)
|
|
1161
1281
|
|
|
1162
1282
|
def _duplicate_column(self) -> None:
|
|
1163
1283
|
"""Duplicate the currently selected column, inserting it right after the current column."""
|
|
@@ -1179,6 +1299,18 @@ class DataFrameTable(DataTable):
|
|
|
1179
1299
|
list(cols_before) + [new_col_name] + list(cols_after)
|
|
1180
1300
|
)
|
|
1181
1301
|
|
|
1302
|
+
# Update matches to account for new column
|
|
1303
|
+
new_matches = defaultdict(set)
|
|
1304
|
+
for row_idx, cols in self.matches.items():
|
|
1305
|
+
new_cols = set()
|
|
1306
|
+
for col_idx_in_set in cols:
|
|
1307
|
+
if col_idx_in_set <= cidx:
|
|
1308
|
+
new_cols.add(col_idx_in_set)
|
|
1309
|
+
else:
|
|
1310
|
+
new_cols.add(col_idx_in_set + 1)
|
|
1311
|
+
new_matches[row_idx] = new_cols
|
|
1312
|
+
self.matches = new_matches
|
|
1313
|
+
|
|
1182
1314
|
# Recreate the table for display
|
|
1183
1315
|
self._setup_table()
|
|
1184
1316
|
|
|
@@ -1190,7 +1322,7 @@ class DataFrameTable(DataTable):
|
|
|
1190
1322
|
title="Duplicate",
|
|
1191
1323
|
)
|
|
1192
1324
|
|
|
1193
|
-
def _delete_row(self) -> None:
|
|
1325
|
+
def _delete_row(self, more: str = None) -> None:
|
|
1194
1326
|
"""Delete rows from the table and dataframe.
|
|
1195
1327
|
|
|
1196
1328
|
Supports deleting multiple selected rows. If no rows are selected, deletes the row at the cursor.
|
|
@@ -1206,11 +1338,27 @@ class DataFrameTable(DataTable):
|
|
|
1206
1338
|
if selected:
|
|
1207
1339
|
predicates[ridx] = False
|
|
1208
1340
|
|
|
1341
|
+
# Delete current row and those above
|
|
1342
|
+
elif more == "above":
|
|
1343
|
+
ridx = self.cursor_row_idx
|
|
1344
|
+
history_desc = f"Deleted current row [$success]{ridx + 1}[/] and those above"
|
|
1345
|
+
for i in range(ridx + 1):
|
|
1346
|
+
predicates[i] = False
|
|
1347
|
+
|
|
1348
|
+
# Delete current row and those below
|
|
1349
|
+
elif more == "below":
|
|
1350
|
+
ridx = self.cursor_row_idx
|
|
1351
|
+
history_desc = f"Deleted current row [$success]{ridx + 1}[/] and those below"
|
|
1352
|
+
for i in range(ridx, len(self.df)):
|
|
1353
|
+
if self.visible_rows[i]:
|
|
1354
|
+
predicates[i] = False
|
|
1355
|
+
|
|
1209
1356
|
# Delete the row at the cursor
|
|
1210
1357
|
else:
|
|
1211
1358
|
ridx = self.cursor_row_idx
|
|
1212
1359
|
history_desc = f"Deleted row [$success]{ridx + 1}[/]"
|
|
1213
|
-
|
|
1360
|
+
if self.visible_rows[ridx]:
|
|
1361
|
+
predicates[ridx] = False
|
|
1214
1362
|
|
|
1215
1363
|
# Add to history
|
|
1216
1364
|
self._add_history(history_desc)
|
|
@@ -1238,7 +1386,7 @@ class DataFrameTable(DataTable):
|
|
|
1238
1386
|
|
|
1239
1387
|
deleted_count = old_count - len(self.df)
|
|
1240
1388
|
if deleted_count > 1:
|
|
1241
|
-
self.notify(f"Deleted {deleted_count} row(s)", title="Delete")
|
|
1389
|
+
self.notify(f"Deleted [$accent]{deleted_count}[/] row(s)", title="Delete")
|
|
1242
1390
|
|
|
1243
1391
|
def _duplicate_row(self) -> None:
|
|
1244
1392
|
"""Duplicate the currently selected row, inserting it right after the current row."""
|
|
@@ -1263,8 +1411,14 @@ class DataFrameTable(DataTable):
|
|
|
1263
1411
|
self.selected_rows = new_selected_rows
|
|
1264
1412
|
self.visible_rows = new_visible_rows
|
|
1265
1413
|
|
|
1266
|
-
#
|
|
1267
|
-
|
|
1414
|
+
# Update matches to account for new row
|
|
1415
|
+
new_matches = defaultdict(set)
|
|
1416
|
+
for row_idx, cols in self.matches.items():
|
|
1417
|
+
if row_idx <= ridx:
|
|
1418
|
+
new_matches[row_idx] = cols
|
|
1419
|
+
else:
|
|
1420
|
+
new_matches[row_idx + 1] = cols
|
|
1421
|
+
self.matches = new_matches
|
|
1268
1422
|
|
|
1269
1423
|
# Recreate the table display
|
|
1270
1424
|
self._setup_table()
|
|
@@ -2072,18 +2226,6 @@ class DataFrameTable(DataTable):
|
|
|
2072
2226
|
title="Global Find",
|
|
2073
2227
|
)
|
|
2074
2228
|
|
|
2075
|
-
def _move_cursor(self, ridx: int, cidx: int) -> None:
|
|
2076
|
-
"""Move cursor based on the dataframe indices.
|
|
2077
|
-
|
|
2078
|
-
Args:
|
|
2079
|
-
ridx: Row index (0-based) in the dataframe.
|
|
2080
|
-
cidx: Column index (0-based) in the dataframe.
|
|
2081
|
-
"""
|
|
2082
|
-
row_key = str(ridx)
|
|
2083
|
-
col_key = self.df.columns[cidx]
|
|
2084
|
-
row_idx, col_idx = self.get_cell_coordinate(row_key, col_key)
|
|
2085
|
-
self.move_cursor(row=row_idx, column=col_idx)
|
|
2086
|
-
|
|
2087
2229
|
def _next_match(self) -> None:
|
|
2088
2230
|
"""Move cursor to the next match."""
|
|
2089
2231
|
if not self.matches:
|
|
@@ -2099,12 +2241,12 @@ class DataFrameTable(DataTable):
|
|
|
2099
2241
|
# Find the next match after current position
|
|
2100
2242
|
for ridx, cidx in ordered_matches:
|
|
2101
2243
|
if (ridx, cidx) > current_pos:
|
|
2102
|
-
self.
|
|
2244
|
+
self.move_cursor_to(ridx, cidx)
|
|
2103
2245
|
return
|
|
2104
2246
|
|
|
2105
2247
|
# If no next match, wrap around to the first match
|
|
2106
2248
|
first_ridx, first_cidx = ordered_matches[0]
|
|
2107
|
-
self.
|
|
2249
|
+
self.move_cursor_to(first_ridx, first_cidx)
|
|
2108
2250
|
|
|
2109
2251
|
def _previous_match(self) -> None:
|
|
2110
2252
|
"""Move cursor to the previous match."""
|
|
@@ -2149,12 +2291,12 @@ class DataFrameTable(DataTable):
|
|
|
2149
2291
|
# Find the next selected row after current position
|
|
2150
2292
|
for ridx in selected_row_indices:
|
|
2151
2293
|
if ridx > current_ridx:
|
|
2152
|
-
self.
|
|
2294
|
+
self.move_cursor_to(ridx, self.cursor_col_idx)
|
|
2153
2295
|
return
|
|
2154
2296
|
|
|
2155
2297
|
# If no next selected row, wrap around to the first selected row
|
|
2156
2298
|
first_ridx = selected_row_indices[0]
|
|
2157
|
-
self.
|
|
2299
|
+
self.move_cursor_to(first_ridx, self.cursor_col_idx)
|
|
2158
2300
|
|
|
2159
2301
|
def _previous_selected_row(self) -> None:
|
|
2160
2302
|
"""Move cursor to the previous selected row."""
|
|
@@ -2171,12 +2313,12 @@ class DataFrameTable(DataTable):
|
|
|
2171
2313
|
# Find the previous selected row before current position
|
|
2172
2314
|
for ridx in reversed(selected_row_indices):
|
|
2173
2315
|
if ridx < current_ridx:
|
|
2174
|
-
self.
|
|
2316
|
+
self.move_cursor_to(ridx, self.cursor_col_idx)
|
|
2175
2317
|
return
|
|
2176
2318
|
|
|
2177
2319
|
# If no previous selected row, wrap around to the last selected row
|
|
2178
2320
|
last_ridx = selected_row_indices[-1]
|
|
2179
|
-
self.
|
|
2321
|
+
self.move_cursor_to(last_ridx, self.cursor_col_idx)
|
|
2180
2322
|
|
|
2181
2323
|
def _replace(self) -> None:
|
|
2182
2324
|
"""Open replace screen for current column."""
|
|
@@ -2459,8 +2601,8 @@ class DataFrameTable(DataTable):
|
|
|
2459
2601
|
title="Toggle",
|
|
2460
2602
|
)
|
|
2461
2603
|
|
|
2462
|
-
# Refresh the highlighting
|
|
2463
|
-
self._do_highlight()
|
|
2604
|
+
# Refresh the highlighting
|
|
2605
|
+
self._do_highlight(force=True)
|
|
2464
2606
|
|
|
2465
2607
|
def _make_selections(self) -> None:
|
|
2466
2608
|
"""Make selections based on current matches or toggle current row selection."""
|
|
@@ -2481,10 +2623,10 @@ class DataFrameTable(DataTable):
|
|
|
2481
2623
|
self.notify(f"Selected [$accent]{new_selected_count}[/] rows", title="Toggle")
|
|
2482
2624
|
|
|
2483
2625
|
# Refresh the highlighting (also restores default styles for unselected rows)
|
|
2484
|
-
self._do_highlight()
|
|
2626
|
+
self._do_highlight(force=True)
|
|
2485
2627
|
|
|
2486
|
-
def
|
|
2487
|
-
"""Clear all selected rows without removing them from the dataframe."""
|
|
2628
|
+
def _clear_selections_and_matches(self) -> None:
|
|
2629
|
+
"""Clear all selected rows and matches without removing them from the dataframe."""
|
|
2488
2630
|
# Check if any selected rows or matches
|
|
2489
2631
|
if not any(self.selected_rows) and not self.matches:
|
|
2490
2632
|
self.notify("No selections to clear", title="Clear", severity="warning")
|
|
@@ -2497,8 +2639,12 @@ class DataFrameTable(DataTable):
|
|
|
2497
2639
|
# Save current state to history
|
|
2498
2640
|
self._add_history("Cleared all selected rows")
|
|
2499
2641
|
|
|
2500
|
-
# Clear all selections
|
|
2501
|
-
self.
|
|
2642
|
+
# Clear all selections
|
|
2643
|
+
self.selected_rows = [False] * len(self.df)
|
|
2644
|
+
self.matches = defaultdict(set)
|
|
2645
|
+
|
|
2646
|
+
# Refresh the highlighting to remove all highlights
|
|
2647
|
+
self._do_highlight(force=True)
|
|
2502
2648
|
|
|
2503
2649
|
self.notify(f"Cleared selections for [$accent]{row_count}[/] rows", title="Clear")
|
|
2504
2650
|
|
|
@@ -2766,3 +2912,38 @@ class DataFrameTable(DataTable):
|
|
|
2766
2912
|
f"Saved current tab with [$accent]{len(self.df)}[/] rows to [$success]{filename}[/]",
|
|
2767
2913
|
title="Save",
|
|
2768
2914
|
)
|
|
2915
|
+
|
|
2916
|
+
def _make_cell_clickable(self) -> None:
|
|
2917
|
+
"""Make cells with URLs in the current column clickable.
|
|
2918
|
+
|
|
2919
|
+
Scans all loaded rows in the current column for cells containing URLs
|
|
2920
|
+
(starting with 'http://' or 'https://') and applies Textual link styling
|
|
2921
|
+
to make them clickable. Does not modify the dataframe.
|
|
2922
|
+
|
|
2923
|
+
Returns:
|
|
2924
|
+
None
|
|
2925
|
+
"""
|
|
2926
|
+
cidx = self.cursor_col_idx
|
|
2927
|
+
col_key = self.cursor_col_key
|
|
2928
|
+
dtype = self.df.dtypes[cidx]
|
|
2929
|
+
|
|
2930
|
+
# Only process string columns
|
|
2931
|
+
if dtype != pl.String:
|
|
2932
|
+
return
|
|
2933
|
+
|
|
2934
|
+
# Count how many URLs were made clickable
|
|
2935
|
+
url_count = 0
|
|
2936
|
+
|
|
2937
|
+
# Iterate through all loaded rows and make URLs clickable
|
|
2938
|
+
for row in self.ordered_rows:
|
|
2939
|
+
cell_text: Text = self.get_cell(row.key, col_key)
|
|
2940
|
+
if cell_text.plain.startswith(("http://", "https://")):
|
|
2941
|
+
cell_text.style = f"#00afff link {cell_text.plain}" # sky blue
|
|
2942
|
+
self.update_cell(row.key, col_key, cell_text)
|
|
2943
|
+
url_count += 1
|
|
2944
|
+
|
|
2945
|
+
if url_count:
|
|
2946
|
+
self.notify(
|
|
2947
|
+
f"Made [$accent]{url_count}[/] cell(s) clickable in column [$success]{col_key.value}[/]",
|
|
2948
|
+
title="Make Clickable",
|
|
2949
|
+
)
|
|
@@ -30,8 +30,10 @@ class TableScreen(ModalScreen):
|
|
|
30
30
|
|
|
31
31
|
TableScreen > DataTable {
|
|
32
32
|
width: auto;
|
|
33
|
-
|
|
33
|
+
height: auto;
|
|
34
34
|
border: solid $primary;
|
|
35
|
+
max-width: 100%;
|
|
36
|
+
overflow: auto;
|
|
35
37
|
}
|
|
36
38
|
"""
|
|
37
39
|
|
|
@@ -291,6 +293,10 @@ class StatisticsScreen(TableScreen):
|
|
|
291
293
|
if False in self.dftable.visible_rows:
|
|
292
294
|
lf = lf.filter(self.dftable.visible_rows)
|
|
293
295
|
|
|
296
|
+
# Apply only to non-hidden columns
|
|
297
|
+
if self.dftable.hidden_columns:
|
|
298
|
+
lf = lf.select(pl.exclude(self.dftable.hidden_columns))
|
|
299
|
+
|
|
294
300
|
# Get dataframe statistics
|
|
295
301
|
stats_df = lf.collect().describe()
|
|
296
302
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/data_frame_help_panel.py
RENAMED
|
File without changes
|
{dataframe_textual-1.1.5 → dataframe_textual-1.2.0}/src/dataframe_textual/data_frame_viewer.py
RENAMED
|
File without changes
|
|
File without changes
|