curses-components 26.7.2__tar.gz → 26.9.2__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.
- {curses_components-26.7.2 → curses_components-26.9.2}/PKG-INFO +21 -4
- {curses_components-26.7.2 → curses_components-26.9.2}/README.md +19 -2
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components/__init__.py +2 -1
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components/editor.py +6 -1
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components/grid.py +88 -6
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components/popup.py +109 -7
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components.egg-info/PKG-INFO +21 -4
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components.egg-info/top_level.txt +1 -0
- {curses_components-26.7.2 → curses_components-26.9.2}/pyproject.toml +1 -1
- {curses_components-26.7.2 → curses_components-26.9.2}/LICENSE +0 -0
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components/theme.py +0 -0
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components.egg-info/SOURCES.txt +0 -0
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components.egg-info/dependency_links.txt +0 -0
- {curses_components-26.7.2 → curses_components-26.9.2}/curses_components.egg-info/requires.txt +0 -0
- {curses_components-26.7.2 → curses_components-26.9.2}/examples/grid_editor.py +0 -0
- {curses_components-26.7.2 → curses_components-26.9.2}/examples/text_editor.py +0 -0
- {curses_components-26.7.2 → curses_components-26.9.2}/setup.cfg +0 -0
- {curses_components-26.7.2 → curses_components-26.9.2}/util/show_colors.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: curses_components
|
|
3
|
-
Version: 26.
|
|
3
|
+
Version: 26.9.2
|
|
4
4
|
Summary: A reusable curses component library for terminal UI (TUI) applications.
|
|
5
5
|
Author: Cobus Nel
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Repository, https://github.com/cobusn/curses_components
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: pyperclip
|
|
@@ -92,10 +92,27 @@ See examples/ for runnable demos:
|
|
|
92
92
|
GridComponent key bindings
|
|
93
93
|
--------------------------
|
|
94
94
|
|
|
95
|
+
Grid extensions can display reusable popups through `show_popup`:
|
|
96
|
+
|
|
97
|
+
grid.show_popup('Messages', lines=['first line', 'second line'])
|
|
98
|
+
grid.show_popup('Results', rows=[('Name', 'Alice'), ('State', 'Active')])
|
|
99
|
+
|
|
100
|
+
The popup inherits the grid's configured colors.
|
|
101
|
+
|
|
102
|
+
Commands can be added with a handler that receives the grid and parsed
|
|
103
|
+
arguments:
|
|
104
|
+
|
|
105
|
+
def show_current_column(grid, args):
|
|
106
|
+
column = grid.columns[grid.col_idx]
|
|
107
|
+
grid.show_popup(column, lines=[str(row.get(column, ''))
|
|
108
|
+
for row in grid.data])
|
|
109
|
+
|
|
110
|
+
grid.register_command('values', show_current_column)
|
|
111
|
+
|
|
95
112
|
Navigation: j/k/h/l or arrow keys, Home/End, Page Up/Down, ^/$
|
|
96
113
|
Search: / (substring), r/<pattern> (regex), n/N (next/previous)
|
|
97
114
|
Input mode: : (enter), q (quit), sort, filter, col, freeze, copy,
|
|
98
|
-
copyrow, export <file>
|
|
115
|
+
copyrow, count, export <file>
|
|
99
116
|
Sorting: sort col1 col2! (! suffix = descending)
|
|
100
117
|
Filtering: filter <val> (current column, wildcards supported)
|
|
101
118
|
filter reset
|
|
@@ -127,7 +144,7 @@ EditorPopup also accepts bold=True (bright text) and bold_background=True.
|
|
|
127
144
|
Dependencies
|
|
128
145
|
------------
|
|
129
146
|
|
|
130
|
-
- Python 3.
|
|
147
|
+
- Python 3.8+
|
|
131
148
|
- pyperclip (for copy/copyrow commands in GridComponent)
|
|
132
149
|
|
|
133
150
|
License
|
|
@@ -77,10 +77,27 @@ See examples/ for runnable demos:
|
|
|
77
77
|
GridComponent key bindings
|
|
78
78
|
--------------------------
|
|
79
79
|
|
|
80
|
+
Grid extensions can display reusable popups through `show_popup`:
|
|
81
|
+
|
|
82
|
+
grid.show_popup('Messages', lines=['first line', 'second line'])
|
|
83
|
+
grid.show_popup('Results', rows=[('Name', 'Alice'), ('State', 'Active')])
|
|
84
|
+
|
|
85
|
+
The popup inherits the grid's configured colors.
|
|
86
|
+
|
|
87
|
+
Commands can be added with a handler that receives the grid and parsed
|
|
88
|
+
arguments:
|
|
89
|
+
|
|
90
|
+
def show_current_column(grid, args):
|
|
91
|
+
column = grid.columns[grid.col_idx]
|
|
92
|
+
grid.show_popup(column, lines=[str(row.get(column, ''))
|
|
93
|
+
for row in grid.data])
|
|
94
|
+
|
|
95
|
+
grid.register_command('values', show_current_column)
|
|
96
|
+
|
|
80
97
|
Navigation: j/k/h/l or arrow keys, Home/End, Page Up/Down, ^/$
|
|
81
98
|
Search: / (substring), r/<pattern> (regex), n/N (next/previous)
|
|
82
99
|
Input mode: : (enter), q (quit), sort, filter, col, freeze, copy,
|
|
83
|
-
copyrow, export <file>
|
|
100
|
+
copyrow, count, export <file>
|
|
84
101
|
Sorting: sort col1 col2! (! suffix = descending)
|
|
85
102
|
Filtering: filter <val> (current column, wildcards supported)
|
|
86
103
|
filter reset
|
|
@@ -112,7 +129,7 @@ EditorPopup also accepts bold=True (bright text) and bold_background=True.
|
|
|
112
129
|
Dependencies
|
|
113
130
|
------------
|
|
114
131
|
|
|
115
|
-
- Python 3.
|
|
132
|
+
- Python 3.8+
|
|
116
133
|
- pyperclip (for copy/copyrow commands in GridComponent)
|
|
117
134
|
|
|
118
135
|
License
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
2
|
# Copyright (c) 2026 Cobus Nel
|
|
3
|
-
__version__ = "26.
|
|
3
|
+
__version__ = "26.9.2"
|
|
4
4
|
|
|
5
5
|
from curses_components.editor import EditorPopup
|
|
6
|
+
from curses_components.grid import CommandHandler, GridComponent
|
|
6
7
|
from curses_components.popup import EditorHelpPopup, HelpPopup, ScrollablePopup
|
|
7
8
|
from curses_components.theme import COLORS, resolve_color
|
|
@@ -612,7 +612,12 @@ class EditorPopup:
|
|
|
612
612
|
return None
|
|
613
613
|
|
|
614
614
|
if verb == 'help':
|
|
615
|
-
EditorHelpPopup(
|
|
615
|
+
EditorHelpPopup(
|
|
616
|
+
self.stdscr,
|
|
617
|
+
fg_color=self.fg_color,
|
|
618
|
+
bg_color=self.bg_color,
|
|
619
|
+
border_color=self.border_color,
|
|
620
|
+
).display()
|
|
616
621
|
return None
|
|
617
622
|
|
|
618
623
|
self._status_msg = f'unknown command: {verb}'
|
|
@@ -10,12 +10,14 @@ import json
|
|
|
10
10
|
import logging
|
|
11
11
|
import re
|
|
12
12
|
import time
|
|
13
|
+
from collections import Counter
|
|
14
|
+
from typing import Protocol, Sequence
|
|
15
|
+
|
|
13
16
|
import pyperclip
|
|
14
17
|
|
|
15
|
-
from curses_components.popup import HelpPopup
|
|
18
|
+
from curses_components.popup import HelpPopup, ScrollablePopup, TextPopup
|
|
16
19
|
from curses_components.theme import resolve_color
|
|
17
20
|
|
|
18
|
-
|
|
19
21
|
logging.basicConfig(filename='help_debug.log', level=logging.DEBUG)
|
|
20
22
|
|
|
21
23
|
|
|
@@ -23,6 +25,13 @@ class QuitApplication(Exception):
|
|
|
23
25
|
"""Custom exception to signal application exit."""
|
|
24
26
|
|
|
25
27
|
|
|
28
|
+
class CommandHandler(Protocol):
|
|
29
|
+
"""Public signature for commands registered by grid extensions."""
|
|
30
|
+
|
|
31
|
+
def __call__(self, grid: "GridComponent", args: Sequence[str]) -> None:
|
|
32
|
+
"""Handle a command for the grid and its parsed arguments."""
|
|
33
|
+
|
|
34
|
+
|
|
26
35
|
|
|
27
36
|
class GridComponent:
|
|
28
37
|
"""
|
|
@@ -64,6 +73,7 @@ class GridComponent:
|
|
|
64
73
|
"col": self._cmd_col,
|
|
65
74
|
"copy": self._cmd_copy,
|
|
66
75
|
"copyrow": self._cmd_copyrow,
|
|
76
|
+
"count": self._cmd_count,
|
|
67
77
|
"export": self._cmd_export,
|
|
68
78
|
"filter": self._cmd_filter,
|
|
69
79
|
"freeze": self._cmd_freeze,
|
|
@@ -73,6 +83,21 @@ class GridComponent:
|
|
|
73
83
|
"sort": self._cmd_sort,
|
|
74
84
|
}
|
|
75
85
|
|
|
86
|
+
def register_command(self, name: str, handler: CommandHandler) -> None:
|
|
87
|
+
"""Register an extension command receiving ``(grid, args)``."""
|
|
88
|
+
if not isinstance(name, str):
|
|
89
|
+
raise TypeError("command name must be a string")
|
|
90
|
+
command_name = name.strip().lower()
|
|
91
|
+
if not command_name or any(char.isspace() for char in command_name):
|
|
92
|
+
raise ValueError("command name must be a non-empty single token")
|
|
93
|
+
if not callable(handler):
|
|
94
|
+
raise TypeError("command handler must be callable")
|
|
95
|
+
|
|
96
|
+
def invoke(args):
|
|
97
|
+
return handler(self, args)
|
|
98
|
+
|
|
99
|
+
self.commands[command_name] = invoke
|
|
100
|
+
|
|
76
101
|
def display(self, data, columns=None, max_rows=10000):
|
|
77
102
|
"""
|
|
78
103
|
Initializes the curses application and displays the grid.
|
|
@@ -164,6 +189,27 @@ class GridComponent:
|
|
|
164
189
|
self.error_message = message
|
|
165
190
|
self.error_message_expiry = time.time() + delay
|
|
166
191
|
|
|
192
|
+
def show_popup(self, title, *, rows=None, lines=None, key_col_width=20):
|
|
193
|
+
"""Display a scrollable popup with text lines or two-column rows."""
|
|
194
|
+
if (rows is None) == (lines is None):
|
|
195
|
+
raise ValueError("provide exactly one of rows or lines")
|
|
196
|
+
|
|
197
|
+
popup_kwargs = {
|
|
198
|
+
"title": title,
|
|
199
|
+
"fg_color": self.fg_color,
|
|
200
|
+
"bg_color": self.bg_color,
|
|
201
|
+
"border_color": self.border_color,
|
|
202
|
+
}
|
|
203
|
+
if lines is not None:
|
|
204
|
+
TextPopup(self.stdscr, lines, **popup_kwargs).display()
|
|
205
|
+
else:
|
|
206
|
+
ScrollablePopup(
|
|
207
|
+
self.stdscr,
|
|
208
|
+
rows=rows,
|
|
209
|
+
key_col_width=key_col_width,
|
|
210
|
+
**popup_kwargs,
|
|
211
|
+
).display()
|
|
212
|
+
|
|
167
213
|
def _get_col_stats(self, col):
|
|
168
214
|
"""Returns (min, max, avg, count) for a numeric column over current data, or None."""
|
|
169
215
|
values = [float(str(row.get(col, ''))) for row in self.data
|
|
@@ -372,6 +418,40 @@ class GridComponent:
|
|
|
372
418
|
pyperclip.copy(json.dumps(row, ensure_ascii=False))
|
|
373
419
|
self.show_error("Row copied as JSON", delay=0.8)
|
|
374
420
|
|
|
421
|
+
def _cmd_count(self, _cmds):
|
|
422
|
+
"""Display counts of distinct raw values in the current column."""
|
|
423
|
+
if _cmds:
|
|
424
|
+
self.show_error("Usage: count")
|
|
425
|
+
return
|
|
426
|
+
if not self.data or not self.columns:
|
|
427
|
+
self.show_error("No data to count")
|
|
428
|
+
return
|
|
429
|
+
|
|
430
|
+
column = self.columns[self.col_idx]
|
|
431
|
+
values = [row.get(column, '') for row in self.data]
|
|
432
|
+
try:
|
|
433
|
+
counts = Counter(values)
|
|
434
|
+
except TypeError:
|
|
435
|
+
self.show_error("Cannot count unhashable values")
|
|
436
|
+
return
|
|
437
|
+
rows = [("Value", "Count"), ("", "")]
|
|
438
|
+
rows.extend(
|
|
439
|
+
(str(value), str(count))
|
|
440
|
+
for value, count in sorted(
|
|
441
|
+
counts.items(),
|
|
442
|
+
key=lambda item: (-item[1], str(item[0])),
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
key_col_width = min(
|
|
446
|
+
40,
|
|
447
|
+
max(24, max((len(str(value)) for value in counts), default=0) + 3),
|
|
448
|
+
)
|
|
449
|
+
self.show_popup(
|
|
450
|
+
f"{column} ({len(counts)} unique)",
|
|
451
|
+
rows=rows,
|
|
452
|
+
key_col_width=key_col_width,
|
|
453
|
+
)
|
|
454
|
+
|
|
375
455
|
def _cmd_export(self, _cmds):
|
|
376
456
|
"""Handles the 'export' command, writing current data to a CSV file."""
|
|
377
457
|
if not _cmds:
|
|
@@ -389,8 +469,11 @@ class GridComponent:
|
|
|
389
469
|
|
|
390
470
|
def _cmd_help(self, _cmds):
|
|
391
471
|
"""Handles the 'help' command, displaying the help screen."""
|
|
392
|
-
|
|
393
|
-
|
|
472
|
+
self.show_popup(
|
|
473
|
+
HelpPopup.title,
|
|
474
|
+
rows=HelpPopup(self.stdscr).rows,
|
|
475
|
+
key_col_width=HelpPopup.key_col_width,
|
|
476
|
+
)
|
|
394
477
|
|
|
395
478
|
def _cmd_dollar(self, _cmds):
|
|
396
479
|
"""Handles the '$' command, moving to the last row."""
|
|
@@ -662,8 +745,7 @@ class GridComponent:
|
|
|
662
745
|
else:
|
|
663
746
|
self.show_error("No mark set")
|
|
664
747
|
elif key == ord('?'):
|
|
665
|
-
|
|
666
|
-
help_screen.display()
|
|
748
|
+
self._cmd_help([])
|
|
667
749
|
|
|
668
750
|
def _get_visible_cols(self):
|
|
669
751
|
"""Returns a list of scrollable column names currently visible on screen."""
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
"""Reusable curses popup components."""
|
|
4
4
|
|
|
5
5
|
import curses
|
|
6
|
+
from collections import Counter
|
|
7
|
+
|
|
8
|
+
from curses_components.theme import resolve_color
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class ScrollablePopup:
|
|
@@ -22,16 +25,34 @@ class ScrollablePopup:
|
|
|
22
25
|
title = ""
|
|
23
26
|
key_col_width = 20 # characters reserved for the left column
|
|
24
27
|
|
|
25
|
-
def __init__(self, stdscr
|
|
28
|
+
def __init__(self, stdscr, title=None, rows=None, key_col_width=None,
|
|
29
|
+
fg_color=None, bg_color=None, border_color=None):
|
|
26
30
|
self.stdscr = stdscr
|
|
31
|
+
if title is not None:
|
|
32
|
+
self.title = title
|
|
33
|
+
self._rows = rows
|
|
34
|
+
if key_col_width is not None:
|
|
35
|
+
self.key_col_width = key_col_width
|
|
36
|
+
self.fg_color = fg_color
|
|
37
|
+
self.bg_color = bg_color
|
|
38
|
+
self.border_color = border_color
|
|
27
39
|
self.scroll_pos = 0
|
|
28
40
|
|
|
41
|
+
def _init_colors(self):
|
|
42
|
+
"""Initialize color pairs used by the popup."""
|
|
43
|
+
bg = resolve_color(self.bg_color, -1)
|
|
44
|
+
fg = resolve_color(self.fg_color, -1)
|
|
45
|
+
border = resolve_color(self.border_color, -1)
|
|
46
|
+
curses.init_pair(5, fg, bg)
|
|
47
|
+
curses.init_pair(6, border, bg)
|
|
48
|
+
|
|
29
49
|
@property
|
|
30
50
|
def rows(self):
|
|
31
51
|
"""Return a list of (left, right) string tuples to display."""
|
|
32
|
-
return []
|
|
52
|
+
return self._rows or []
|
|
33
53
|
|
|
34
54
|
def display(self):
|
|
55
|
+
self._init_colors()
|
|
35
56
|
height, width = self.stdscr.getmaxyx()
|
|
36
57
|
win_height = min(25, height - 2)
|
|
37
58
|
win_width = min(80, width - 2)
|
|
@@ -44,23 +65,48 @@ class ScrollablePopup:
|
|
|
44
65
|
max_scroll = max(0, len(content) - (win_height - 4))
|
|
45
66
|
|
|
46
67
|
while True:
|
|
68
|
+
win.attrset(curses.color_pair(6))
|
|
47
69
|
win.border()
|
|
48
70
|
title = self.title
|
|
49
|
-
|
|
71
|
+
title_x = max(0, (win_width - len(title)) // 2)
|
|
72
|
+
win.addstr(
|
|
73
|
+
0,
|
|
74
|
+
title_x,
|
|
75
|
+
title[:max(0, win_width - title_x - 1)],
|
|
76
|
+
curses.color_pair(6) | curses.A_REVERSE,
|
|
77
|
+
)
|
|
50
78
|
|
|
51
79
|
for i in range(1, win_height - 1):
|
|
52
|
-
win.addstr(i, 1, " " * (win_width - 2))
|
|
80
|
+
win.addstr(i, 1, " " * (win_width - 2), curses.color_pair(5))
|
|
53
81
|
|
|
54
82
|
text_y = 2
|
|
55
83
|
for left, right in content[self.scroll_pos:]:
|
|
56
84
|
if text_y >= win_height - 2:
|
|
57
85
|
break
|
|
58
|
-
|
|
86
|
+
left_width = max(0, self.key_col_width - 3)
|
|
87
|
+
right_width = max(0, win_width - self.key_col_width - 2)
|
|
88
|
+
win.addstr(
|
|
89
|
+
text_y,
|
|
90
|
+
2,
|
|
91
|
+
left[:left_width],
|
|
92
|
+
curses.color_pair(5) | curses.A_BOLD,
|
|
93
|
+
)
|
|
59
94
|
if right:
|
|
60
|
-
win.addstr(
|
|
95
|
+
win.addstr(
|
|
96
|
+
text_y,
|
|
97
|
+
self.key_col_width,
|
|
98
|
+
right[:right_width],
|
|
99
|
+
curses.color_pair(5),
|
|
100
|
+
)
|
|
61
101
|
text_y += 1
|
|
62
102
|
|
|
63
|
-
|
|
103
|
+
footer = "Press 'q' to close..."
|
|
104
|
+
win.addstr(
|
|
105
|
+
win_height - 2,
|
|
106
|
+
2,
|
|
107
|
+
footer[:max(0, win_width - 4)],
|
|
108
|
+
curses.color_pair(5),
|
|
109
|
+
)
|
|
64
110
|
win.noutrefresh()
|
|
65
111
|
curses.doupdate()
|
|
66
112
|
|
|
@@ -73,6 +119,27 @@ class ScrollablePopup:
|
|
|
73
119
|
self.scroll_pos = min(max_scroll, self.scroll_pos + 1)
|
|
74
120
|
|
|
75
121
|
|
|
122
|
+
class TextPopup(ScrollablePopup):
|
|
123
|
+
"""Popup displaying one scrollable line of text per row."""
|
|
124
|
+
|
|
125
|
+
def __init__(self, stdscr, lines, **kwargs):
|
|
126
|
+
super().__init__(stdscr, **kwargs)
|
|
127
|
+
self.lines = [str(line) for line in lines]
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def rows(self):
|
|
131
|
+
return [(line, "") for line in self.lines]
|
|
132
|
+
|
|
133
|
+
def display(self):
|
|
134
|
+
original_key_col_width = self.key_col_width
|
|
135
|
+
screen_width = self.stdscr.getmaxyx()[1]
|
|
136
|
+
self.key_col_width = min(80, max(1, screen_width - 2))
|
|
137
|
+
try:
|
|
138
|
+
super().display()
|
|
139
|
+
finally:
|
|
140
|
+
self.key_col_width = original_key_col_width
|
|
141
|
+
|
|
142
|
+
|
|
76
143
|
class HelpPopup(ScrollablePopup):
|
|
77
144
|
"""Help popup for GridComponent."""
|
|
78
145
|
|
|
@@ -106,6 +173,7 @@ class HelpPopup(ScrollablePopup):
|
|
|
106
173
|
("sort col1 col2!", "col1 asc, col2 desc (! = descending)"),
|
|
107
174
|
("copy", "Copy current cell value"),
|
|
108
175
|
("copyrow", "Copy current row as JSON"),
|
|
176
|
+
("count", "Count distinct values in current column"),
|
|
109
177
|
("export <file>", "Export current data to CSV file"),
|
|
110
178
|
("ESC", "Exit input mode"),
|
|
111
179
|
("", ""),
|
|
@@ -131,6 +199,40 @@ class HelpPopup(ScrollablePopup):
|
|
|
131
199
|
]
|
|
132
200
|
|
|
133
201
|
|
|
202
|
+
class ValueCountPopup(ScrollablePopup):
|
|
203
|
+
"""Popup displaying the frequency of each value in a grid column."""
|
|
204
|
+
|
|
205
|
+
title = "Value Counts"
|
|
206
|
+
|
|
207
|
+
def __init__(self, stdscr, column, values, **colors):
|
|
208
|
+
super().__init__(stdscr, **colors)
|
|
209
|
+
self.column = column
|
|
210
|
+
self.counts = Counter(values)
|
|
211
|
+
self.key_col_width = min(
|
|
212
|
+
40,
|
|
213
|
+
max(
|
|
214
|
+
24,
|
|
215
|
+
max((len(str(value)) for value in self.counts), default=0) + 3,
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def rows(self):
|
|
221
|
+
rows = [("Value", "Count"), ("", "")]
|
|
222
|
+
rows.extend(
|
|
223
|
+
(str(value), str(count))
|
|
224
|
+
for value, count in sorted(
|
|
225
|
+
self.counts.items(),
|
|
226
|
+
key=lambda item: (-item[1], str(item[0])),
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
return rows
|
|
230
|
+
|
|
231
|
+
def display(self):
|
|
232
|
+
self.title = f"{self.column} ({len(self.counts)} unique)"
|
|
233
|
+
super().display()
|
|
234
|
+
|
|
235
|
+
|
|
134
236
|
class EditorHelpPopup(ScrollablePopup):
|
|
135
237
|
"""Help popup for EditorPopup."""
|
|
136
238
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: curses_components
|
|
3
|
-
Version: 26.
|
|
3
|
+
Version: 26.9.2
|
|
4
4
|
Summary: A reusable curses component library for terminal UI (TUI) applications.
|
|
5
5
|
Author: Cobus Nel
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Repository, https://github.com/cobusn/curses_components
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: pyperclip
|
|
@@ -92,10 +92,27 @@ See examples/ for runnable demos:
|
|
|
92
92
|
GridComponent key bindings
|
|
93
93
|
--------------------------
|
|
94
94
|
|
|
95
|
+
Grid extensions can display reusable popups through `show_popup`:
|
|
96
|
+
|
|
97
|
+
grid.show_popup('Messages', lines=['first line', 'second line'])
|
|
98
|
+
grid.show_popup('Results', rows=[('Name', 'Alice'), ('State', 'Active')])
|
|
99
|
+
|
|
100
|
+
The popup inherits the grid's configured colors.
|
|
101
|
+
|
|
102
|
+
Commands can be added with a handler that receives the grid and parsed
|
|
103
|
+
arguments:
|
|
104
|
+
|
|
105
|
+
def show_current_column(grid, args):
|
|
106
|
+
column = grid.columns[grid.col_idx]
|
|
107
|
+
grid.show_popup(column, lines=[str(row.get(column, ''))
|
|
108
|
+
for row in grid.data])
|
|
109
|
+
|
|
110
|
+
grid.register_command('values', show_current_column)
|
|
111
|
+
|
|
95
112
|
Navigation: j/k/h/l or arrow keys, Home/End, Page Up/Down, ^/$
|
|
96
113
|
Search: / (substring), r/<pattern> (regex), n/N (next/previous)
|
|
97
114
|
Input mode: : (enter), q (quit), sort, filter, col, freeze, copy,
|
|
98
|
-
copyrow, export <file>
|
|
115
|
+
copyrow, count, export <file>
|
|
99
116
|
Sorting: sort col1 col2! (! suffix = descending)
|
|
100
117
|
Filtering: filter <val> (current column, wildcards supported)
|
|
101
118
|
filter reset
|
|
@@ -127,7 +144,7 @@ EditorPopup also accepts bold=True (bright text) and bold_background=True.
|
|
|
127
144
|
Dependencies
|
|
128
145
|
------------
|
|
129
146
|
|
|
130
|
-
- Python 3.
|
|
147
|
+
- Python 3.8+
|
|
131
148
|
- pyperclip (for copy/copyrow commands in GridComponent)
|
|
132
149
|
|
|
133
150
|
License
|
|
@@ -9,7 +9,7 @@ description = "A reusable curses component library for terminal UI (TUI) applica
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
authors = [{ name = "Cobus Nel" }]
|
|
12
|
-
requires-python = ">=3.
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
13
|
dependencies = ["pyperclip"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
File without changes
|
|
File without changes
|
{curses_components-26.7.2 → curses_components-26.9.2}/curses_components.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{curses_components-26.7.2 → curses_components-26.9.2}/curses_components.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|