listpick 0.1.13.62__py3-none-any.whl → 0.1.14.1__py3-none-any.whl
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.
Potentially problematic release.
This version of listpick might be problematic. Click here for more details.
- listpick/listpick_app.py +392 -68
- listpick/ui/footer.py +98 -16
- listpick/ui/keys.py +5 -4
- listpick/utils/table_to_list_of_lists.py +51 -36
- listpick/utils/utils.py +5 -4
- {listpick-0.1.13.62.dist-info → listpick-0.1.14.1.dist-info}/METADATA +2 -1
- {listpick-0.1.13.62.dist-info → listpick-0.1.14.1.dist-info}/RECORD +11 -11
- {listpick-0.1.13.62.dist-info → listpick-0.1.14.1.dist-info}/WHEEL +0 -0
- {listpick-0.1.13.62.dist-info → listpick-0.1.14.1.dist-info}/entry_points.txt +0 -0
- {listpick-0.1.13.62.dist-info → listpick-0.1.14.1.dist-info}/licenses/LICENSE.txt +0 -0
- {listpick-0.1.13.62.dist-info → listpick-0.1.14.1.dist-info}/top_level.txt +0 -0
listpick/listpick_app.py
CHANGED
|
@@ -106,6 +106,7 @@ class Picker:
|
|
|
106
106
|
|
|
107
107
|
selections: dict = {},
|
|
108
108
|
cell_selections: dict[tuple[int,int], bool] = {},
|
|
109
|
+
selected_cells_by_row: dict = {},
|
|
109
110
|
highlight_full_row: bool =False,
|
|
110
111
|
cell_cursor: bool = False,
|
|
111
112
|
|
|
@@ -122,7 +123,7 @@ class Picker:
|
|
|
122
123
|
|
|
123
124
|
paginate: bool =False,
|
|
124
125
|
cancel_is_back: bool = False,
|
|
125
|
-
mode_index: int =0,
|
|
126
|
+
mode_index: int = 0,
|
|
126
127
|
modes: list[dict] = [],
|
|
127
128
|
display_modes: bool =False,
|
|
128
129
|
require_option: list=[],
|
|
@@ -161,10 +162,8 @@ class Picker:
|
|
|
161
162
|
|
|
162
163
|
startup_notification:str = "",
|
|
163
164
|
|
|
164
|
-
leftmost_column: int = 0,
|
|
165
165
|
leftmost_char: int = 0,
|
|
166
166
|
|
|
167
|
-
|
|
168
167
|
history_filter_and_search: list[str] = [],
|
|
169
168
|
history_opts: list[str] = [],
|
|
170
169
|
history_settings: list[str] = [],
|
|
@@ -173,6 +172,19 @@ class Picker:
|
|
|
173
172
|
debug: bool = False,
|
|
174
173
|
debug_level: int = 1,
|
|
175
174
|
|
|
175
|
+
command_stack: list = [],
|
|
176
|
+
|
|
177
|
+
loaded_file: str = "Untitled",
|
|
178
|
+
loaded_files: list[str] = ["Untitled"],
|
|
179
|
+
loaded_file_index: int = 0,
|
|
180
|
+
loaded_file_states: list[dict] = [{}],
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
sheets = ["Untitled"],
|
|
184
|
+
sheet_name = "Untitled",
|
|
185
|
+
sheet_index = 0,
|
|
186
|
+
sheet_states = [{}],
|
|
187
|
+
|
|
176
188
|
):
|
|
177
189
|
self.stdscr = stdscr
|
|
178
190
|
self.items = items
|
|
@@ -225,6 +237,7 @@ class Picker:
|
|
|
225
237
|
|
|
226
238
|
self.selections = selections
|
|
227
239
|
self.cell_selections = cell_selections
|
|
240
|
+
self.selected_cells_by_row = selected_cells_by_row
|
|
228
241
|
self.highlight_full_row = highlight_full_row
|
|
229
242
|
self.cell_cursor = cell_cursor
|
|
230
243
|
|
|
@@ -283,8 +296,7 @@ class Picker:
|
|
|
283
296
|
self.registers = {}
|
|
284
297
|
|
|
285
298
|
self.SORT_METHODS = SORT_METHODS
|
|
286
|
-
self.command_stack =
|
|
287
|
-
self.leftmost_column = leftmost_column
|
|
299
|
+
self.command_stack = command_stack
|
|
288
300
|
self.leftmost_char = leftmost_char
|
|
289
301
|
|
|
290
302
|
|
|
@@ -297,7 +309,6 @@ class Picker:
|
|
|
297
309
|
self.cursor_pos_prev = 0
|
|
298
310
|
self.ids = []
|
|
299
311
|
self.ids_tuples = []
|
|
300
|
-
self.selected_cells_by_row = {}
|
|
301
312
|
|
|
302
313
|
# History variables
|
|
303
314
|
self.history_filter_and_search = history_filter_and_search
|
|
@@ -307,18 +318,37 @@ class Picker:
|
|
|
307
318
|
self.history_edits = history_edits
|
|
308
319
|
|
|
309
320
|
|
|
310
|
-
|
|
311
|
-
|
|
312
321
|
self.debug = debug
|
|
313
322
|
self.debug_level = debug_level
|
|
314
323
|
|
|
324
|
+
# Multiple file support
|
|
325
|
+
self.loaded_files = loaded_files
|
|
326
|
+
self.loaded_file = loaded_file
|
|
327
|
+
self.loaded_file_index = loaded_file_index
|
|
328
|
+
self.loaded_file_states = loaded_file_states
|
|
329
|
+
|
|
330
|
+
# Multiple sheet support
|
|
331
|
+
self.sheet_index = sheet_index
|
|
332
|
+
self.sheet_name = sheet_name
|
|
333
|
+
self.sheet_states = sheet_states
|
|
334
|
+
self.sheets = sheets
|
|
315
335
|
|
|
316
336
|
self.initialise_picker_state(reset_colours=self.reset_colours)
|
|
317
337
|
|
|
318
338
|
# Note: We have to set the footer after initialising the picker state so that the footer can use the get_function_data method
|
|
319
339
|
self.footer_options = [StandardFooter(self.stdscr, colours_start, self.get_function_data), CompactFooter(self.stdscr, colours_start, self.get_function_data), NoFooter(self.stdscr, colours_start, self.get_function_data)]
|
|
320
340
|
self.footer = self.footer_options[self.footer_style]
|
|
341
|
+
self.__version__ = "1.0"
|
|
321
342
|
|
|
343
|
+
def __sizeof__(self):
|
|
344
|
+
|
|
345
|
+
size = super().__sizeof__()
|
|
346
|
+
|
|
347
|
+
# Add the size of each attribute directly owned by the object
|
|
348
|
+
for attr_name in dir(self):
|
|
349
|
+
if not attr_name.startswith('__') and not callable(getattr(self, attr_name)):
|
|
350
|
+
size += sys.getsizeof(getattr(self, attr_name))
|
|
351
|
+
return size
|
|
322
352
|
|
|
323
353
|
def calculate_section_sizes(self):
|
|
324
354
|
"""
|
|
@@ -495,8 +525,12 @@ class Picker:
|
|
|
495
525
|
|
|
496
526
|
if len(self.items) and len(self.cell_selections) != len(self.items)*len(self.items[0]):
|
|
497
527
|
self.cell_selections = {(i, j) : False if (i, j) not in self.cell_selections else self.cell_selections[(i, j)] for i in range(len(self.items)) for j in range(len(self.items[0]))}
|
|
528
|
+
self.selected_cells_by_row = get_selected_cells_by_row(self.cell_selections)
|
|
498
529
|
elif len(self.items) == 0:
|
|
499
530
|
self.cell_selections = {}
|
|
531
|
+
self.selected_cells_by_row = {}
|
|
532
|
+
|
|
533
|
+
|
|
500
534
|
|
|
501
535
|
if len(self.require_option) < len(self.items):
|
|
502
536
|
self.require_option += [self.require_option_default for i in range(len(self.items)-len(self.require_option))]
|
|
@@ -556,6 +590,20 @@ class Picker:
|
|
|
556
590
|
assert new_pos < len(self.items)
|
|
557
591
|
self.cursor_pos = new_pos
|
|
558
592
|
|
|
593
|
+
# Sheets and files
|
|
594
|
+
if len(self.sheet_states) < len(self.sheets):
|
|
595
|
+
self.sheet_states += [{} for _ in range(len(self.sheets) - len(self.sheet_states))]
|
|
596
|
+
if len(self.sheets):
|
|
597
|
+
if self.sheet_index >= len(self.sheets):
|
|
598
|
+
self.sheet_index = 0
|
|
599
|
+
self.sheet_name = self.sheets[self.sheet_index]
|
|
600
|
+
|
|
601
|
+
if len(self.loaded_file_states) < len(self.loaded_files):
|
|
602
|
+
self.loaded_file_states += [{} for _ in range(len(self.loaded_files) - len(self.loaded_file_states))]
|
|
603
|
+
if len(self.loaded_files):
|
|
604
|
+
if self.loaded_file_index >= len(self.loaded_files):
|
|
605
|
+
self.loaded_file_index = 0
|
|
606
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
559
607
|
|
|
560
608
|
# if tracking and len(self.items) > 1:
|
|
561
609
|
# Ensure that selected indices are tracked upon data refresh
|
|
@@ -591,6 +639,7 @@ class Picker:
|
|
|
591
639
|
self.cursor_pos = [i[0] for i in self.indexed_items].index(cursor_pos_x)
|
|
592
640
|
else:
|
|
593
641
|
self.cursor_pos = 0
|
|
642
|
+
|
|
594
643
|
|
|
595
644
|
|
|
596
645
|
|
|
@@ -1045,6 +1094,7 @@ class Picker:
|
|
|
1045
1094
|
function_data = {
|
|
1046
1095
|
"selections": self.selections,
|
|
1047
1096
|
"cell_selections": self.cell_selections,
|
|
1097
|
+
"selected_cells_by_row": self.selected_cells_by_row,
|
|
1048
1098
|
"items_per_page": self.items_per_page,
|
|
1049
1099
|
"current_row": self.current_row,
|
|
1050
1100
|
"current_page": self.current_page,
|
|
@@ -1123,7 +1173,6 @@ class Picker:
|
|
|
1123
1173
|
"keys_dict": self.keys_dict,
|
|
1124
1174
|
"cancel_is_back": self.cancel_is_back,
|
|
1125
1175
|
"paginate": self.paginate,
|
|
1126
|
-
"leftmost_column": self.leftmost_column,
|
|
1127
1176
|
"leftmost_char": self.leftmost_char,
|
|
1128
1177
|
"history_filter_and_search" : self.history_filter_and_search,
|
|
1129
1178
|
"history_pipes" : self.history_pipes,
|
|
@@ -1136,17 +1185,55 @@ class Picker:
|
|
|
1136
1185
|
"debug_level": self.debug_level,
|
|
1137
1186
|
"reset_colours": self.reset_colours,
|
|
1138
1187
|
"unicode_char_width": self.unicode_char_width,
|
|
1188
|
+
"command_stack": self.command_stack,
|
|
1189
|
+
"loaded_file": self.loaded_file,
|
|
1190
|
+
"loaded_files": self.loaded_files,
|
|
1191
|
+
"loaded_file_index": self.loaded_file_index,
|
|
1192
|
+
"loaded_file_states": self.loaded_file_states,
|
|
1193
|
+
"sheet_index": self.sheet_index,
|
|
1194
|
+
"sheets": self.sheets,
|
|
1195
|
+
"sheet_name": self.sheet_name,
|
|
1196
|
+
"sheet_states": self.sheet_states,
|
|
1139
1197
|
}
|
|
1140
1198
|
return function_data
|
|
1141
1199
|
|
|
1142
|
-
def set_function_data(self, function_data: dict) -> None:
|
|
1200
|
+
def set_function_data(self, function_data: dict, reset_absent_variables: bool = False, do_not_set: list=[]) -> None:
|
|
1143
1201
|
""" Set variables from state dict containing core variables."""
|
|
1144
1202
|
self.logger.info(f"function: set_function_data()")
|
|
1145
1203
|
variables = self.get_function_data().keys()
|
|
1146
1204
|
|
|
1205
|
+
x = Picker(self.stdscr, reset_colours=False)
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
common_picker_vars = [
|
|
1209
|
+
"loaded_file_index",
|
|
1210
|
+
"loaded_file_states",
|
|
1211
|
+
"loaded_files",
|
|
1212
|
+
"loaded_file",
|
|
1213
|
+
"command_stack",
|
|
1214
|
+
"colour_theme_number",
|
|
1215
|
+
"reset_colours",
|
|
1216
|
+
"show_footer",
|
|
1217
|
+
"show_header",
|
|
1218
|
+
"history_filter_and_search",
|
|
1219
|
+
"history_settings",
|
|
1220
|
+
"history_opts",
|
|
1221
|
+
"history_edits",
|
|
1222
|
+
"history_pipes",
|
|
1223
|
+
"reset_colours",
|
|
1224
|
+
"cell_cursor",
|
|
1225
|
+
"top_gap",
|
|
1226
|
+
"unicode_char_width",
|
|
1227
|
+
"show_row_header",
|
|
1228
|
+
]
|
|
1229
|
+
|
|
1147
1230
|
for var in variables:
|
|
1148
1231
|
if var in function_data:
|
|
1149
1232
|
setattr(self, var, function_data[var])
|
|
1233
|
+
elif reset_absent_variables and var not in common_picker_vars and var not in do_not_set:
|
|
1234
|
+
# Set value to the default for an empty picker
|
|
1235
|
+
setattr(self, var, getattr(x, var))
|
|
1236
|
+
|
|
1150
1237
|
|
|
1151
1238
|
reset_colours = bool("colour_theme_number" in function_data)
|
|
1152
1239
|
self.initialise_picker_state(reset_colours=reset_colours)
|
|
@@ -1275,6 +1362,7 @@ class Picker:
|
|
|
1275
1362
|
h, w = stdscr.getmaxyx()
|
|
1276
1363
|
|
|
1277
1364
|
submenu_win = curses.newwin(notification_height, notification_width, 3, w - (notification_width+4))
|
|
1365
|
+
# submenu_win = self.stdscr.subwin(notification_height, notification_width, 3, w - (notification_width+4))
|
|
1278
1366
|
notification_data = {
|
|
1279
1367
|
"items": submenu_items,
|
|
1280
1368
|
"title": title,
|
|
@@ -1291,6 +1379,10 @@ class Picker:
|
|
|
1291
1379
|
"cancel_is_back": True,
|
|
1292
1380
|
"reset_colours": False,
|
|
1293
1381
|
|
|
1382
|
+
"loaded_files": [],
|
|
1383
|
+
"loaded_file_states": [],
|
|
1384
|
+
"loaded_file": "",
|
|
1385
|
+
"loaded_file_index": 0,
|
|
1294
1386
|
}
|
|
1295
1387
|
OptionPicker = Picker(submenu_win, **notification_data)
|
|
1296
1388
|
s, o, f = OptionPicker.run()
|
|
@@ -1343,15 +1435,15 @@ class Picker:
|
|
|
1343
1435
|
# highlights = [highlight for highlight in highlights if "type" not in highlight or highlight["type"] != "search" ]
|
|
1344
1436
|
|
|
1345
1437
|
self.highlights_hide = not self.highlights_hide
|
|
1346
|
-
elif setting[0] == "s":
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1438
|
+
# elif setting[0] == "s":
|
|
1439
|
+
# if 0 <= int(setting[1:]) < len(self.items[0]):
|
|
1440
|
+
# self.sort_column = int(setting[1:])
|
|
1441
|
+
# if len(self.indexed_items):
|
|
1442
|
+
# current_pos = self.indexed_items[self.cursor_pos][0]
|
|
1443
|
+
# sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort items based on new column
|
|
1444
|
+
# if len(self.indexed_items):
|
|
1445
|
+
# new_pos = [row[0] for row in self.indexed_items].index(current_pos)
|
|
1446
|
+
# self.cursor_pos = new_pos
|
|
1355
1447
|
elif setting == "ct":
|
|
1356
1448
|
self.centre_in_terminal = not self.centre_in_terminal
|
|
1357
1449
|
elif setting == "cc":
|
|
@@ -1396,6 +1488,47 @@ class Picker:
|
|
|
1396
1488
|
self.pin_cursor = not self.pin_cursor
|
|
1397
1489
|
elif setting == "unicode":
|
|
1398
1490
|
self.unicode_char_width = not self.unicode_char_width
|
|
1491
|
+
elif setting == "file_next":
|
|
1492
|
+
if len(self.loaded_files) > 1:
|
|
1493
|
+
self.command_stack.append(Command("setting", self.user_settings))
|
|
1494
|
+
# Cache file state
|
|
1495
|
+
self.loaded_file_states[self.loaded_file_index] = self.get_function_data()
|
|
1496
|
+
|
|
1497
|
+
self.loaded_file_index = (self.loaded_file_index + 1) % len(self.loaded_files)
|
|
1498
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
1499
|
+
|
|
1500
|
+
# If we already have a loaded state for this file
|
|
1501
|
+
if self.loaded_file_states[self.loaded_file_index]:
|
|
1502
|
+
self.set_function_data(self.loaded_file_states[self.loaded_file_index])
|
|
1503
|
+
else:
|
|
1504
|
+
self.set_function_data({}, reset_absent_variables=True)
|
|
1505
|
+
self.load_file(self.loaded_file)
|
|
1506
|
+
|
|
1507
|
+
elif setting == "sheet_next":
|
|
1508
|
+
if not os.path.exists(self.loaded_file):
|
|
1509
|
+
self.notification(self.stdscr, message=f"File {repr(self.loaded_file)} not found.")
|
|
1510
|
+
return None
|
|
1511
|
+
if len(self.sheets) > 1:
|
|
1512
|
+
self.command_stack.append(Command("setting", self.user_settings))
|
|
1513
|
+
|
|
1514
|
+
# Cache sheet state
|
|
1515
|
+
self.sheet_states[self.sheet_index] = self.get_function_data()
|
|
1516
|
+
self.sheet_index = (self.sheet_index + 1) % len(self.sheets)
|
|
1517
|
+
self.sheet_name = self.sheets[self.sheet_index]
|
|
1518
|
+
|
|
1519
|
+
# If we already have a loaded state for this file
|
|
1520
|
+
if self.sheet_states[self.sheet_index]:
|
|
1521
|
+
self.set_function_data(self.sheet_states[self.sheet_index])
|
|
1522
|
+
else:
|
|
1523
|
+
function_data = {
|
|
1524
|
+
"sheet_index": self.sheet_index,
|
|
1525
|
+
"sheet_name": self.sheet_name,
|
|
1526
|
+
"sheet_states":self.sheet_states,
|
|
1527
|
+
"sheets": self.sheets,
|
|
1528
|
+
}
|
|
1529
|
+
self.set_function_data(function_data, reset_absent_variables=True)
|
|
1530
|
+
self.load_sheet(self.loaded_file, sheet_number=self.sheet_index)
|
|
1531
|
+
|
|
1399
1532
|
|
|
1400
1533
|
elif setting.startswith("ft"):
|
|
1401
1534
|
if len(setting) > 2 and setting[2:].isnumeric():
|
|
@@ -1463,9 +1596,9 @@ class Picker:
|
|
|
1463
1596
|
self.user_settings = ""
|
|
1464
1597
|
return None
|
|
1465
1598
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1599
|
+
if self.user_settings:
|
|
1600
|
+
self.command_stack.append(Command("setting", self.user_settings))
|
|
1601
|
+
self.user_settings = ""
|
|
1469
1602
|
|
|
1470
1603
|
def apply_command(self, command: Command):
|
|
1471
1604
|
self.logger.info(f"function: apply_command()")
|
|
@@ -1773,17 +1906,41 @@ class Picker:
|
|
|
1773
1906
|
]
|
|
1774
1907
|
|
|
1775
1908
|
if s:
|
|
1776
|
-
|
|
1777
|
-
|
|
1909
|
+
restrict_curses(self.stdscr)
|
|
1910
|
+
files_to_load = file_picker()
|
|
1911
|
+
unrestrict_curses(self.stdscr)
|
|
1912
|
+
if files_to_load:
|
|
1778
1913
|
index = list(s.keys())[0]
|
|
1914
|
+
file_to_load = files_to_load[0]
|
|
1779
1915
|
return_val = funcs[index](file_to_load)
|
|
1780
|
-
self.set_function_data(return_val)
|
|
1781
1916
|
|
|
1917
|
+
self.loaded_file_states[self.loaded_file_index] = self.get_function_data()
|
|
1918
|
+
|
|
1919
|
+
self.stdscr.clear()
|
|
1920
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
|
1921
|
+
|
|
1922
|
+
tmp = self.stdscr
|
|
1923
|
+
|
|
1924
|
+
self.loaded_files += files_to_load
|
|
1925
|
+
self.loaded_file_states += [{} for _ in files_to_load]
|
|
1926
|
+
self.loaded_file = file_to_load
|
|
1927
|
+
self.loaded_file_index = len(self.loaded_files)-len(files_to_load)
|
|
1928
|
+
|
|
1929
|
+
|
|
1930
|
+
self.stdscr = tmp
|
|
1931
|
+
|
|
1932
|
+
h, w = self.stdscr.getmaxyx()
|
|
1933
|
+
self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
|
|
1934
|
+
|
|
1935
|
+
self.set_function_data({}, reset_absent_variables=True)
|
|
1936
|
+
self.load_file(self.loaded_file)
|
|
1782
1937
|
# items = return_val["items"]
|
|
1783
1938
|
# header = return_val["header"]
|
|
1784
|
-
self.
|
|
1939
|
+
self.stdscr.clear()
|
|
1940
|
+
# self.initialise_variables()
|
|
1785
1941
|
self.draw_screen(self.indexed_items, self.highlights)
|
|
1786
|
-
self.
|
|
1942
|
+
# self.stdscr.refresh()
|
|
1943
|
+
|
|
1787
1944
|
# if return_val:
|
|
1788
1945
|
# notification(stdscr, message=return_val, title="Error")
|
|
1789
1946
|
|
|
@@ -1915,6 +2072,36 @@ class Picker:
|
|
|
1915
2072
|
self.cursor_pos = current_cursor_pos
|
|
1916
2073
|
|
|
1917
2074
|
|
|
2075
|
+
def load_file(self, filename: str) -> None:
|
|
2076
|
+
if not os.path.exists(filename):
|
|
2077
|
+
self.notification(self.stdscr, message = f"File not found: {filename}")
|
|
2078
|
+
return None
|
|
2079
|
+
|
|
2080
|
+
filetype = guess_file_type(filename)
|
|
2081
|
+
items, header, sheets = table_to_list(filename, file_type=filetype)
|
|
2082
|
+
|
|
2083
|
+
if items != None:
|
|
2084
|
+
self.items = items
|
|
2085
|
+
self.header = header if header != None else []
|
|
2086
|
+
self.sheets = sheets
|
|
2087
|
+
|
|
2088
|
+
|
|
2089
|
+
self.initialise_variables()
|
|
2090
|
+
|
|
2091
|
+
def load_sheet(self, filename: str, sheet_number: int = 0):
|
|
2092
|
+
filetype = guess_file_type(filename)
|
|
2093
|
+
items, header, sheets = table_to_list(filename, file_type=filetype, sheet_number=sheet_number)
|
|
2094
|
+
|
|
2095
|
+
if items != None:
|
|
2096
|
+
self.items = items
|
|
2097
|
+
self.header = header if header != None else []
|
|
2098
|
+
self.sheets = sheets
|
|
2099
|
+
|
|
2100
|
+
self.initialise_variables()
|
|
2101
|
+
|
|
2102
|
+
|
|
2103
|
+
|
|
2104
|
+
|
|
1918
2105
|
|
|
1919
2106
|
def run(self) -> Tuple[list[int], str, dict]:
|
|
1920
2107
|
""" Run the picker. """
|
|
@@ -1963,7 +2150,7 @@ class Picker:
|
|
|
1963
2150
|
|
|
1964
2151
|
while True:
|
|
1965
2152
|
key = self.stdscr.getch()
|
|
1966
|
-
if key:
|
|
2153
|
+
if key != -1:
|
|
1967
2154
|
self.logger.info(f"key={key}")
|
|
1968
2155
|
h, w = self.stdscr.getmaxyx()
|
|
1969
2156
|
if key in self.disabled_keys: continue
|
|
@@ -2042,12 +2229,130 @@ class Picker:
|
|
|
2042
2229
|
}
|
|
2043
2230
|
OptionPicker = Picker(self.stdscr, **help_data)
|
|
2044
2231
|
s, o, f = OptionPicker.run()
|
|
2232
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
|
2233
|
+
|
|
2234
|
+
if self.check_key("info", key, self.keys_dict):
|
|
2235
|
+
self.logger.info(f"key_function help")
|
|
2236
|
+
self.stdscr.clear()
|
|
2237
|
+
self.stdscr.refresh()
|
|
2238
|
+
import importlib.metadata as metadata
|
|
2239
|
+
version = metadata.version('listpick')
|
|
2240
|
+
|
|
2241
|
+
info_items = [
|
|
2242
|
+
[" Listpick info:", "-*"*30],
|
|
2243
|
+
["",""],
|
|
2244
|
+
["listpick version", f"{version}"],
|
|
2245
|
+
|
|
2246
|
+
["",""],
|
|
2247
|
+
[" Global:", "-*"*30],
|
|
2248
|
+
["",""],
|
|
2249
|
+
["current_file", self.loaded_file],
|
|
2250
|
+
["loaded_files", repr(self.loaded_files)],
|
|
2251
|
+
["auto_refresh", f"{repr(self.auto_refresh)}"],
|
|
2252
|
+
["timer", f"{repr(self.timer)}"],
|
|
2253
|
+
["pin_cursor", f"{repr(self.pin_cursor)}"],
|
|
2254
|
+
["cwd", f"{os.getcwd()}"],
|
|
2255
|
+
["Picker memory", f"{format_size(sys.getsizeof(self))}"],
|
|
2256
|
+
["debug", f"{repr(self.debug)}"],
|
|
2257
|
+
["debug level", f"{repr(self.debug_level)}"],
|
|
2258
|
+
|
|
2259
|
+
["",""],
|
|
2260
|
+
[" Current File:", "-*"*30],
|
|
2261
|
+
["",""],
|
|
2262
|
+
["row/row count", f"{self.cursor_pos}/{len(self.indexed_items)}"],
|
|
2263
|
+
["total rows", f"{len(self.items)}"],
|
|
2264
|
+
["selections", f"{self.selected_cells_by_row}"],
|
|
2265
|
+
["current_sheet", self.sheet_name],
|
|
2266
|
+
["sheets", repr(self.sheets)],
|
|
2267
|
+
["current column/column_count", f"{self.selected_column}/{len(self.column_widths)}"],
|
|
2268
|
+
["hidden columns", f"{self.hidden_columns}"],
|
|
2269
|
+
["sort column", f"{self.sort_column}"],
|
|
2270
|
+
["sort method", f"{self.SORT_METHODS[self.columns_sort_method[self.sort_column]]}"],
|
|
2271
|
+
["sort order", f"{'Descending' if self.sort_reverse[self.sort_column] else 'Ascending'}"],
|
|
2272
|
+
["id_column", f"{self.id_column}"],
|
|
2273
|
+
|
|
2274
|
+
["",""],
|
|
2275
|
+
[" Display options:", "-*"*30],
|
|
2276
|
+
["",""],
|
|
2277
|
+
["show_header", str(self.show_header)],
|
|
2278
|
+
["show_footer", repr(self.show_footer)],
|
|
2279
|
+
["show_row_header", repr(self.show_row_header)],
|
|
2280
|
+
["max_column_width", str(self.max_column_width)],
|
|
2281
|
+
["colour_theme_number", str(self.colour_theme_number)],
|
|
2282
|
+
["top_gap", str(self.top_gap)],
|
|
2283
|
+
["highlight_full_row", repr(self.highlight_full_row)],
|
|
2284
|
+
["cell_cursor", repr(self.cell_cursor)],
|
|
2285
|
+
["items_per_page", repr(self.items_per_page)],
|
|
2286
|
+
["paginate", repr(self.paginate)],
|
|
2287
|
+
["display_modes", repr(self.display_modes)],
|
|
2288
|
+
["footer_style", repr(self.footer_style)],
|
|
2289
|
+
["unicode_char_width", repr(self.unicode_char_width)],
|
|
2290
|
+
["centre_in_terminal", repr(self.centre_in_terminal)],
|
|
2291
|
+
["centre_in_cols", repr(self.centre_in_cols)],
|
|
2292
|
+
["centre_in_terminal_vertical", repr(self.centre_in_terminal_vertical)],
|
|
2293
|
+
]
|
|
2294
|
+
|
|
2295
|
+
data = self.get_function_data()
|
|
2296
|
+
del data["indexed_items"]
|
|
2297
|
+
del data["selections"]
|
|
2298
|
+
del data["selected_cells_by_row"]
|
|
2299
|
+
del data["cell_selections"]
|
|
2300
|
+
del data["items"]
|
|
2301
|
+
del data["require_option"]
|
|
2302
|
+
del data["option_functions"]
|
|
2303
|
+
info_items += [
|
|
2304
|
+
["",""],
|
|
2305
|
+
[" get_function_data():", "-*"*30],
|
|
2306
|
+
["",""],
|
|
2307
|
+
["show_header", str(self.show_header)],
|
|
2308
|
+
]
|
|
2309
|
+
info_items += [[key, repr(value)] for key, value in data.items()]
|
|
2310
|
+
info_header = ["Option", "Value"]
|
|
2311
|
+
info_data = {
|
|
2312
|
+
"items": info_items,
|
|
2313
|
+
"header": info_header,
|
|
2314
|
+
"title": f"{self.title} Info",
|
|
2315
|
+
"colours_start": self.help_colours_start,
|
|
2316
|
+
"colours": help_colours,
|
|
2317
|
+
"show_footer": True,
|
|
2318
|
+
"max_selected": 1,
|
|
2319
|
+
"keys_dict": help_keys,
|
|
2320
|
+
"disabled_keys": [ord('?'), ord('v'), ord('V'), ord('m'), ord('M'), ord('l'), curses.KEY_ENTER, ord('\n')],
|
|
2321
|
+
"highlight_full_row": True,
|
|
2322
|
+
"top_gap": 0,
|
|
2323
|
+
"paginate": self.paginate,
|
|
2324
|
+
"centre_in_terminal": False,
|
|
2325
|
+
"centre_in_terminal_vertical": True,
|
|
2326
|
+
"hidden_columns": [],
|
|
2327
|
+
"reset_colours": False,
|
|
2328
|
+
|
|
2329
|
+
}
|
|
2330
|
+
OptionPicker = Picker(self.stdscr, **info_data)
|
|
2331
|
+
s, o, f = OptionPicker.run()
|
|
2332
|
+
|
|
2333
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
|
2045
2334
|
|
|
2046
2335
|
elif self.check_key("exit", key, self.keys_dict):
|
|
2047
2336
|
self.stdscr.clear()
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2337
|
+
if len(self.loaded_files) <= 1:
|
|
2338
|
+
function_data = self.get_function_data()
|
|
2339
|
+
function_data["last_key"] = key
|
|
2340
|
+
return [], "", function_data
|
|
2341
|
+
else:
|
|
2342
|
+
del self.loaded_files[self.loaded_file_index]
|
|
2343
|
+
del self.loaded_file_states[self.loaded_file_index]
|
|
2344
|
+
self.loaded_file_index = min(self.loaded_file_index, len(self.loaded_files)-1)
|
|
2345
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
2346
|
+
|
|
2347
|
+
|
|
2348
|
+
# If we already have a loaded state for this file
|
|
2349
|
+
if self.loaded_file_states[self.loaded_file_index]:
|
|
2350
|
+
self.set_function_data(self.loaded_file_states[self.loaded_file_index])
|
|
2351
|
+
else:
|
|
2352
|
+
self.set_function_data({}, reset_absent_variables=True)
|
|
2353
|
+
self.load_file(self.loaded_file)
|
|
2354
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
|
2355
|
+
|
|
2051
2356
|
elif self.check_key("full_exit", key, self.keys_dict):
|
|
2052
2357
|
close_curses(self.stdscr)
|
|
2053
2358
|
exit()
|
|
@@ -2612,6 +2917,7 @@ class Picker:
|
|
|
2612
2917
|
# 4. if self.cancel_is_back (e.g., notification) then we exit
|
|
2613
2918
|
# 4. selecting
|
|
2614
2919
|
|
|
2920
|
+
pass
|
|
2615
2921
|
# Cancel visual de/selection
|
|
2616
2922
|
if self.is_selecting or self.is_deselecting:
|
|
2617
2923
|
self.start_selection = -1
|
|
@@ -2689,41 +2995,51 @@ class Picker:
|
|
|
2689
2995
|
|
|
2690
2996
|
elif self.check_key("mode_next", key, self.keys_dict): # tab key
|
|
2691
2997
|
self.logger.info(f"key_function mode_next")
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2998
|
+
if len(self.modes):
|
|
2999
|
+
prev_mode_index = self.mode_index
|
|
3000
|
+
self.mode_index = (self.mode_index+1)%len(self.modes)
|
|
3001
|
+
mode = self.modes[self.mode_index]
|
|
3002
|
+
for key, val in mode.items():
|
|
3003
|
+
if key == 'filter':
|
|
3004
|
+
if 'filter' in self.modes[prev_mode_index]:
|
|
3005
|
+
self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
|
|
3006
|
+
self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
|
|
3007
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
3008
|
+
|
|
3009
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
|
3010
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
|
3011
|
+
else: new_index = 0
|
|
3012
|
+
self.cursor_pos = new_index
|
|
3013
|
+
# Re-sort self.items after applying filter
|
|
3014
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
|
2709
3015
|
elif self.check_key("mode_prev", key, self.keys_dict): # shift+tab key
|
|
2710
3016
|
self.logger.info(f"key_function mode_prev")
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
3017
|
+
if len(self.modes):
|
|
3018
|
+
prev_mode_index = self.mode_index
|
|
3019
|
+
self.mode_index = (self.mode_index-1)%len(self.modes)
|
|
3020
|
+
mode = self.modes[self.mode_index]
|
|
3021
|
+
for key, val in mode.items():
|
|
3022
|
+
if key == 'filter':
|
|
3023
|
+
if 'filter' in self.modes[prev_mode_index]:
|
|
3024
|
+
self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
|
|
3025
|
+
self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
|
|
3026
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
3027
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
|
3028
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
|
3029
|
+
else: new_index = 0
|
|
3030
|
+
self.cursor_pos = new_index
|
|
3031
|
+
# Re-sort self.items after applying filter
|
|
3032
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
|
3033
|
+
elif self.check_key("file_next", key, self.keys_dict):
|
|
3034
|
+
if len(self.loaded_files):
|
|
3035
|
+
self.loaded_file_index = (self.loaded_file_index + 1) % len(self.loaded_files)
|
|
3036
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
3037
|
+
|
|
3038
|
+
elif self.check_key("file_prev", key, self.keys_dict):
|
|
3039
|
+
if len(self.loaded_files):
|
|
3040
|
+
self.loaded_file_index = (self.loaded_file_index - 1) % len(self.loaded_files)
|
|
3041
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
3042
|
+
|
|
2727
3043
|
elif self.check_key("pipe_input", key, self.keys_dict):
|
|
2728
3044
|
self.logger.info(f"key_function pipe_input")
|
|
2729
3045
|
# usrtxt = "xargs -d '\n' -I{} "
|
|
@@ -3036,7 +3352,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3036
3352
|
""" Parse command line arguments. """
|
|
3037
3353
|
parser = argparse.ArgumentParser(description='Convert table to list of lists.')
|
|
3038
3354
|
# parser.add_argument('filename', type=str, help='The file to process')
|
|
3039
|
-
parser.add_argument('-i', dest='file', help='File containing the table to be converted.')
|
|
3355
|
+
# parser.add_argument('-i', dest='file', help='File containing the table to be converted.')
|
|
3356
|
+
parser.add_argument('-i', dest='file', nargs='+', help='File containing the table to be converted.')
|
|
3040
3357
|
parser.add_argument('--load', '-l', dest='load', type=str, help='Load file from Picker dump.')
|
|
3041
3358
|
parser.add_argument('--stdin', dest='stdin', action='store_true', help='Table passed on stdin')
|
|
3042
3359
|
parser.add_argument('--stdin2', action='store_true', help='Table passed on stdin')
|
|
@@ -3058,7 +3375,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3058
3375
|
}
|
|
3059
3376
|
|
|
3060
3377
|
if args.file:
|
|
3061
|
-
input_arg = args.file
|
|
3378
|
+
input_arg = args.file[0]
|
|
3379
|
+
|
|
3062
3380
|
elif args.stdin:
|
|
3063
3381
|
input_arg = '--stdin'
|
|
3064
3382
|
elif args.stdin2:
|
|
@@ -3096,9 +3414,14 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3096
3414
|
filetype = args.file_type
|
|
3097
3415
|
|
|
3098
3416
|
|
|
3099
|
-
items, header = table_to_list(input_arg, args.delimiter, filetype)
|
|
3417
|
+
items, header, sheets = table_to_list(input_arg, args.delimiter, filetype)
|
|
3100
3418
|
function_data["items"] = items
|
|
3101
3419
|
if header: function_data["header"] = header
|
|
3420
|
+
function_data["sheets"] = sheets
|
|
3421
|
+
if args.file:
|
|
3422
|
+
function_data["loaded_file"] = args.file[0]
|
|
3423
|
+
function_data["loaded_files"] = args.file
|
|
3424
|
+
|
|
3102
3425
|
return args, function_data
|
|
3103
3426
|
|
|
3104
3427
|
def start_curses() -> curses.window:
|
|
@@ -3188,7 +3511,8 @@ def main() -> None:
|
|
|
3188
3511
|
# function_data["infobox_title"] = "Title"
|
|
3189
3512
|
# function_data["footer_string"] = "Title"
|
|
3190
3513
|
function_data["highlights"] = highlights
|
|
3191
|
-
function_data["show_footer"] = False
|
|
3514
|
+
# function_data["show_footer"] = False
|
|
3515
|
+
# function_data["paginate"] = True
|
|
3192
3516
|
# function_data["debug"] = True
|
|
3193
3517
|
# function_data["debug_level"] = 1
|
|
3194
3518
|
stdscr = start_curses()
|
listpick/ui/footer.py
CHANGED
|
@@ -50,32 +50,109 @@ class StandardFooter(Footer):
|
|
|
50
50
|
def draw(self, h, w):
|
|
51
51
|
state = self.get_state()
|
|
52
52
|
# Fill background
|
|
53
|
-
for i in range(self.height):
|
|
54
|
-
self.stdscr.addstr(h-self.height+i, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
|
55
53
|
|
|
54
|
+
|
|
55
|
+
sheets_y=-1
|
|
56
56
|
if state["footer_string"]:
|
|
57
|
-
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
58
57
|
|
|
59
|
-
disp_string = f"{state["footer_string"][:footer_string_width]}"
|
|
60
|
-
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
61
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
62
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
63
58
|
|
|
59
|
+
footer_string_y = h-1
|
|
64
60
|
picker_info_y = h-3
|
|
65
61
|
sort_info_y = h-2
|
|
62
|
+
|
|
66
63
|
self.height = 3
|
|
67
64
|
|
|
68
65
|
else:
|
|
69
66
|
picker_info_y = h-2
|
|
70
67
|
sort_info_y = h-1
|
|
71
|
-
|
|
72
|
-
select_mode = "C"
|
|
73
|
-
if state["is_selecting"]: select_mode = "VS"
|
|
74
|
-
elif state["is_deselecting"]: select_mode = "VDS"
|
|
75
|
-
if state["pin_cursor"]: select_mode = f"{select_mode} "
|
|
76
|
-
self.stdscr.addstr(h - 1, w-35, f"{select_mode:>33} ", curses.color_pair(self.colours_start+20))
|
|
68
|
+
footer_string_y = -1
|
|
77
69
|
self.height = 2
|
|
78
70
|
|
|
71
|
+
if len(state["sheets"]) > 1:
|
|
72
|
+
self.height += 1
|
|
73
|
+
picker_info_y -= 1
|
|
74
|
+
sort_info_y -= 1
|
|
75
|
+
footer_string_y -= 1
|
|
76
|
+
sheets_y = h-1
|
|
77
|
+
|
|
78
|
+
if len(state["loaded_files"]) > 1 and state["loaded_file"] in state["loaded_files"]:
|
|
79
|
+
self.height += 1
|
|
80
|
+
picker_info_y -= 1
|
|
81
|
+
sort_info_y -= 1
|
|
82
|
+
footer_string_y -= 1
|
|
83
|
+
sheets_y -= 1
|
|
84
|
+
|
|
85
|
+
files_y = h-1
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
for i in range(self.height):
|
|
89
|
+
self.stdscr.addstr(h-self.height+i, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
|
90
|
+
|
|
91
|
+
if len(state["loaded_files"]) > 1 and state["loaded_file"] in state["loaded_files"]:
|
|
92
|
+
|
|
93
|
+
sep = "◢ "
|
|
94
|
+
files = [x.split("/")[-1] for x in state["loaded_files"]]
|
|
95
|
+
filename = state["loaded_file"].split("/")[:-1]
|
|
96
|
+
|
|
97
|
+
files_str = sep.join(files)
|
|
98
|
+
files_str = files_str[:w-2]
|
|
99
|
+
|
|
100
|
+
idx = state["loaded_file_index"]
|
|
101
|
+
current_file_x = sum((len(x) for x in files[:idx])) + idx*len(sep)
|
|
102
|
+
current_file_str = state["loaded_file"].split("/")[-1]
|
|
103
|
+
current_file_x_end = current_file_x + len(current_file_str) + 2
|
|
104
|
+
self.stdscr.addstr(files_y, 0, ' '*(w-1), curses.color_pair(self.colours_start+4))
|
|
105
|
+
if current_file_x_end < w:
|
|
106
|
+
|
|
107
|
+
self.stdscr.addstr(files_y, 0, f" {files_str}", curses.color_pair(self.colours_start+4))
|
|
108
|
+
|
|
109
|
+
self.stdscr.addstr(files_y, current_file_x, f" {current_file_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
110
|
+
else:
|
|
111
|
+
files_str = sep.join(files)
|
|
112
|
+
files_str = files_str[current_file_x_end-w:current_file_x_end][:w-2]
|
|
113
|
+
self.stdscr.addstr(files_y, 0, f" {files_str}", curses.color_pair(self.colours_start+4))
|
|
114
|
+
|
|
115
|
+
self.stdscr.addstr(files_y, w - (len(current_file_str)+3), f" {current_file_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
116
|
+
|
|
117
|
+
if len(state["sheets"]) > 1:
|
|
118
|
+
|
|
119
|
+
sep = "◢ "
|
|
120
|
+
sheets = [x.split("/")[-1] for x in state["sheets"]]
|
|
121
|
+
filename = state["sheet_name"].split("/")[:-1]
|
|
122
|
+
|
|
123
|
+
sheets_str = sep.join(sheets)
|
|
124
|
+
sheets_str = sheets_str[:w-2]
|
|
125
|
+
|
|
126
|
+
idx = state["sheet_index"]
|
|
127
|
+
current_sheet_x = sum((len(x) for x in sheets[:idx])) + idx*len(sep)
|
|
128
|
+
current_sheet_str = state["sheet_name"].split("/")[-1]
|
|
129
|
+
current_sheet_x_end = current_sheet_x + len(current_sheet_str) + 2
|
|
130
|
+
self.stdscr.addstr(sheets_y, 0, ' '*(w-1), curses.color_pair(self.colours_start+4))
|
|
131
|
+
if current_sheet_x_end < w:
|
|
132
|
+
|
|
133
|
+
self.stdscr.addstr(sheets_y, 0, f" {sheets_str}", curses.color_pair(self.colours_start+4))
|
|
134
|
+
|
|
135
|
+
self.stdscr.addstr(sheets_y, current_sheet_x, f" {current_sheet_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
136
|
+
else:
|
|
137
|
+
sheets_str = sep.join(sheets)
|
|
138
|
+
sheets_str = sheets_str[current_sheet_x_end-w:current_sheet_x_end][:w-2]
|
|
139
|
+
self.stdscr.addstr(sheets_y, 0, f" {sheets_str}", curses.color_pair(self.colours_start+4))
|
|
140
|
+
|
|
141
|
+
self.stdscr.addstr(sheets_y, w - (len(current_sheet_str)+3), f" {current_sheet_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if state["footer_string"]:
|
|
147
|
+
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
148
|
+
|
|
149
|
+
disp_string = f"{state["footer_string"][:footer_string_width]}"
|
|
150
|
+
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
151
|
+
self.stdscr.addstr(footer_string_y, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
152
|
+
self.stdscr.addstr(footer_string_y, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
79
156
|
|
|
80
157
|
if state["filter_query"]:
|
|
81
158
|
self.stdscr.addstr(h - 2, 2, f" Filter: {state['filter_query']} "[:w-40], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
@@ -93,10 +170,14 @@ class StandardFooter(Footer):
|
|
|
93
170
|
# Cursor & selection info
|
|
94
171
|
selected_count = sum(state["selections"].values())
|
|
95
172
|
if state["paginate"]:
|
|
96
|
-
cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])}
|
|
173
|
+
# cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | Page {state['cursor_pos']//state['items_per_page']}/{len(state['indexed_items'])//state['items_per_page']} | {select_mode}"
|
|
174
|
+
cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | Page {state['cursor_pos']//state['items_per_page']}/{len(state['indexed_items'])//state['items_per_page']} | {select_mode}"
|
|
97
175
|
else:
|
|
98
176
|
cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | {select_mode}"
|
|
99
|
-
|
|
177
|
+
|
|
178
|
+
# Maximum chars that should be displayed
|
|
179
|
+
max_chars = min(len(cursor_disp_str)+2, w)
|
|
180
|
+
self.stdscr.addstr(picker_info_y, w-max_chars, f"{cursor_disp_str:>{max_chars-2}} ", curses.color_pair(self.colours_start+20))
|
|
100
181
|
|
|
101
182
|
|
|
102
183
|
# Sort info
|
|
@@ -105,7 +186,8 @@ class StandardFooter(Footer):
|
|
|
105
186
|
sort_order_info = "Desc." if state["sort_reverse"] else "Asc."
|
|
106
187
|
sort_order_info = "▼" if state["sort_reverse"][state['sort_column']] else "▲"
|
|
107
188
|
sort_disp_str = f" Sort: ({sort_column_info}, {sort_method_info}, {sort_order_info}) "
|
|
108
|
-
|
|
189
|
+
max_chars = min(len(sort_disp_str)+2, w)
|
|
190
|
+
self.stdscr.addstr(sort_info_y, w-max_chars, f"{sort_disp_str:>{max_chars-1}}", curses.color_pair(self.colours_start+20))
|
|
109
191
|
|
|
110
192
|
self.stdscr.refresh()
|
|
111
193
|
|
listpick/ui/keys.py
CHANGED
|
@@ -47,8 +47,8 @@ picker_keys = {
|
|
|
47
47
|
"search_input": [ord('/')],
|
|
48
48
|
"settings_input": [ord('`')],
|
|
49
49
|
"settings_options": [ord('~')],
|
|
50
|
-
"continue_search_forward": [ord('n')
|
|
51
|
-
"continue_search_backward": [ord('N')
|
|
50
|
+
"continue_search_forward": [ord('n')],
|
|
51
|
+
"continue_search_backward": [ord('N')],
|
|
52
52
|
"cancel": [27], # Escape key
|
|
53
53
|
"opts_input": [ord(':')],
|
|
54
54
|
"opts_select": [ord('o')],
|
|
@@ -80,6 +80,7 @@ picker_keys = {
|
|
|
80
80
|
"add_column_after": [ord('+')],
|
|
81
81
|
# "add_row_before": [ord('=')],
|
|
82
82
|
"add_row_after": [ord('=')],
|
|
83
|
+
"info": [ord('i')],
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
|
|
@@ -106,8 +107,8 @@ help_keys = {
|
|
|
106
107
|
"search_input": [ord('/')],
|
|
107
108
|
"settings_input": [ord('`')],
|
|
108
109
|
"settings_options": [ord('~')],
|
|
109
|
-
"continue_search_forward": [ord('n')
|
|
110
|
-
"continue_search_backward": [ord('N')
|
|
110
|
+
"continue_search_forward": [ord('n')],
|
|
111
|
+
"continue_search_backward": [ord('N')],
|
|
111
112
|
"cancel": [27], # Escape key
|
|
112
113
|
"col_select": [ord('0'), ord('1'), ord('2'), ord('3'), ord('4'), ord('5'), ord('6'), ord('7'), ord('8'), ord('9')],
|
|
113
114
|
"col_select_next": [ord('>')],
|
|
@@ -37,10 +37,27 @@ def strip_whitespace(item: Iterable) -> Iterable:
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def table_to_list(
|
|
40
|
+
def table_to_list(
|
|
41
|
+
|
|
42
|
+
input_arg: str,
|
|
43
|
+
delimiter:str='\t',
|
|
44
|
+
file_type:Optional[str]=None,
|
|
45
|
+
sheet_number:int = 0,
|
|
46
|
+
|
|
47
|
+
) -> Tuple[list[list[str]], list[str], list[str]]:
|
|
41
48
|
"""
|
|
42
49
|
Convert data string to list. The input_arg
|
|
43
50
|
Currently accepts: csv, tsv, json, xlsx, ods
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
input_arg: filename
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
returns:
|
|
57
|
+
items: list[list[str]]
|
|
58
|
+
header: list[str]
|
|
59
|
+
sheets: list[str]
|
|
60
|
+
|
|
44
61
|
"""
|
|
45
62
|
logger.info("function: table_to_list (table_to_list_of_lists.py)")
|
|
46
63
|
table_data = []
|
|
@@ -76,10 +93,10 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
76
93
|
table_data = csv_string_to_list(input_data)
|
|
77
94
|
table_data = strip_whitespace(table_data)
|
|
78
95
|
# table_data = parse_csv_like(input_data, ",")
|
|
79
|
-
return table_data, []
|
|
96
|
+
return table_data, [], []
|
|
80
97
|
except Exception as e:
|
|
81
98
|
print(f"Error reading CSV/TSV input: {e}")
|
|
82
|
-
return [], []
|
|
99
|
+
return [], [], []
|
|
83
100
|
|
|
84
101
|
elif file_type == 'tsv':
|
|
85
102
|
try:
|
|
@@ -99,10 +116,10 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
99
116
|
|
|
100
117
|
table_data = parse_csv_like(input_data, delimiter)
|
|
101
118
|
table_data = strip_whitespace(table_data)
|
|
102
|
-
return table_data, []
|
|
119
|
+
return table_data, [], []
|
|
103
120
|
except Exception as e:
|
|
104
121
|
print(f"Error reading CSV/TSV input: {e}")
|
|
105
|
-
return [], []
|
|
122
|
+
return [], [], []
|
|
106
123
|
|
|
107
124
|
elif file_type == 'json':
|
|
108
125
|
try:
|
|
@@ -115,55 +132,53 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
115
132
|
input_data = read_file_content(input_arg)
|
|
116
133
|
|
|
117
134
|
table_data = json.loads(input_data)
|
|
118
|
-
return table_data, []
|
|
135
|
+
return table_data, [], []
|
|
119
136
|
except json.JSONDecodeError as e:
|
|
120
137
|
print(f"Error decoding JSON input: {e}")
|
|
121
|
-
return [], []
|
|
138
|
+
return [], [], []
|
|
122
139
|
except FileNotFoundError as e:
|
|
123
140
|
print(f"File not found: {e}")
|
|
124
|
-
return [], []
|
|
141
|
+
return [], [], []
|
|
125
142
|
|
|
126
143
|
elif file_type == 'xlsx':
|
|
127
|
-
|
|
144
|
+
import pandas as pd
|
|
145
|
+
ef = pd.ExcelFile(input_arg)
|
|
146
|
+
sheets = ef.sheet_names
|
|
147
|
+
if sheet_number < len(sheets):
|
|
148
|
+
df = pd.read_excel(input_arg, sheet_name=sheet_number)
|
|
149
|
+
else:
|
|
150
|
+
df = pd.read_excel(input_arg)
|
|
151
|
+
table_data = df.values.tolist()
|
|
128
152
|
try:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
elif input_arg == '--stdin2':
|
|
134
|
-
input_count = int(sys.stdin.readline())
|
|
135
|
-
input_data = "\n".join([sys.stdin.readline() for i in range(input_count)])
|
|
136
|
-
with open('temp.xlsx', 'wb') as f:
|
|
137
|
-
f.write(input_data.encode())
|
|
138
|
-
else:
|
|
139
|
-
input_data = read_file_content(input_arg)
|
|
140
|
-
with open('temp.xlsx', 'wb') as f:
|
|
141
|
-
f.write(input_data.encode())
|
|
142
|
-
|
|
143
|
-
wb = load_workbook(filename='temp.xlsx')
|
|
144
|
-
sheet = wb.active
|
|
145
|
-
for row in sheet.iter_rows(values_only=True):
|
|
146
|
-
table_data.append(list(row))
|
|
147
|
-
return table_data, []
|
|
148
|
-
except Exception as e:
|
|
149
|
-
print(f"Error loading Excel file: {e}")
|
|
150
|
-
return [], []
|
|
153
|
+
header = list(df.columns)
|
|
154
|
+
except:
|
|
155
|
+
header = []
|
|
156
|
+
return table_data, header, sheets
|
|
151
157
|
|
|
152
158
|
elif file_type == 'ods':
|
|
153
159
|
try:
|
|
154
160
|
import pandas as pd
|
|
155
|
-
|
|
161
|
+
ef = pd.ExcelFile(input_arg)
|
|
162
|
+
sheets = ef.sheet_names
|
|
163
|
+
if sheet_number < len(sheets):
|
|
164
|
+
df = pd.read_excel(input_arg, engine='odf', sheet_name=sheet_number)
|
|
165
|
+
else:
|
|
166
|
+
df = pd.read_excel(input_arg, engine='odf')
|
|
156
167
|
table_data = df.values.tolist()
|
|
157
|
-
|
|
168
|
+
try:
|
|
169
|
+
header = list(df.columns)
|
|
170
|
+
except:
|
|
171
|
+
header = []
|
|
172
|
+
return table_data, header, sheets
|
|
158
173
|
except Exception as e:
|
|
159
174
|
print(f"Error loading ODS file: {e}")
|
|
160
|
-
return [], []
|
|
175
|
+
return [], [], []
|
|
161
176
|
elif file_type == 'pkl':
|
|
162
177
|
with open(os.path.expandvars(os.path.expanduser(input_arg)), 'rb') as f:
|
|
163
178
|
loaded_data = pickle.load(f)
|
|
164
179
|
items = loaded_data["items"] if "items" in loaded_data else []
|
|
165
180
|
header = loaded_data["header"] if "header" in loaded_data else []
|
|
166
|
-
return items, header
|
|
181
|
+
return items, header, []
|
|
167
182
|
|
|
168
183
|
if input_arg == '--stdin':
|
|
169
184
|
input_data = sys.stdin.read()
|
|
@@ -175,7 +190,7 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
175
190
|
|
|
176
191
|
table_data = parse_csv_like(input_data, delimiter)
|
|
177
192
|
|
|
178
|
-
return table_data, []
|
|
193
|
+
return table_data, [], []
|
|
179
194
|
|
|
180
195
|
if __name__ == '__main__':
|
|
181
196
|
parser = argparse.ArgumentParser(description='Convert table to list of lists.')
|
listpick/utils/utils.py
CHANGED
|
@@ -311,7 +311,7 @@ def openFiles(files: list[str]) -> str:
|
|
|
311
311
|
# return result.stderr.read().decode("utf-8").strip()
|
|
312
312
|
return ""
|
|
313
313
|
|
|
314
|
-
def file_picker() -> str:
|
|
314
|
+
def file_picker() -> list[str]:
|
|
315
315
|
""" Run file picker (yazi by default) and return the path of the file picked. If no file is picked an empty string is returned. """
|
|
316
316
|
|
|
317
317
|
logger.info("function: file_picker (utils.py)")
|
|
@@ -320,10 +320,11 @@ def file_picker() -> str:
|
|
|
320
320
|
|
|
321
321
|
lines = tmpfile.readlines()
|
|
322
322
|
if lines:
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
filenames = [line.decode("utf-8").strip() for line in lines]
|
|
324
|
+
# filename = lines[0].decode("utf-8").strip()
|
|
325
|
+
return filenames
|
|
325
326
|
else:
|
|
326
|
-
return
|
|
327
|
+
return []
|
|
327
328
|
|
|
328
329
|
|
|
329
330
|
def dir_picker() -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listpick
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14.1
|
|
4
4
|
Summary: Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.
|
|
5
5
|
Home-page: https://github.com/grimandgreedy/listpick
|
|
6
6
|
Author: Grim
|
|
@@ -26,6 +26,7 @@ Requires-Dist: pandas; extra == "full"
|
|
|
26
26
|
Requires-Dist: pyperclip; extra == "full"
|
|
27
27
|
Requires-Dist: toml; extra == "full"
|
|
28
28
|
Requires-Dist: traitlets; extra == "full"
|
|
29
|
+
Requires-Dist: odfpy; extra == "full"
|
|
29
30
|
Dynamic: author
|
|
30
31
|
Dynamic: author-email
|
|
31
32
|
Dynamic: classifier
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
listpick/__init__.py,sha256=ExXc97-bibodH--wlwpQivl0zCNR5D1hvpvrf7OBofU,154
|
|
2
2
|
listpick/__main__.py,sha256=wkCjDdqw093W27yWwnlC3nG_sMRKaIad7hHHWy0RBgY,193
|
|
3
|
-
listpick/listpick_app.py,sha256
|
|
3
|
+
listpick/listpick_app.py,sha256=-QdN2nv3Aii_vJdt8ROOkL_v-mI2TNQi1q34f5tGAno,181819
|
|
4
4
|
listpick/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
listpick/ui/build_help.py,sha256=_rVKKrX3HfFJtw-pyeNb2lQWbml4-AAw8sZIUYGn97Y,8731
|
|
6
|
-
listpick/ui/footer.py,sha256=
|
|
6
|
+
listpick/ui/footer.py,sha256=85sVZ6zvzZXSc0TwgvCHGuzGElp6KwUVzwzF5VQwLyY,14219
|
|
7
7
|
listpick/ui/help_screen.py,sha256=zbfGIgb-IXtATpl4_Sx7nPbsnRXZ7eiMYlCKGS9EFmw,5608
|
|
8
8
|
listpick/ui/input_field.py,sha256=eyoWHoApdZybjfXcp7Eth7xwb-C-856ZVnq5j_Q3Ojs,30412
|
|
9
|
-
listpick/ui/keys.py,sha256=
|
|
9
|
+
listpick/ui/keys.py,sha256=8LRO0CqX8tn6LM9irwSK7eA_Go-v0nBDztWtQKPFXUk,13042
|
|
10
10
|
listpick/ui/pane_stuff.py,sha256=7GXa4UnV_7YmBv-baRi5moN51wYcuS4p0odl5C3m0Tc,169
|
|
11
11
|
listpick/ui/picker_colours.py,sha256=FLOzvkq83orrN2bL0Mw-6RugWOZyuwUjQCrUFMUnKGY,11563
|
|
12
12
|
listpick/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -21,11 +21,11 @@ listpick/utils/picker_log.py,sha256=SW6GmjxpI7YrSf72fSr4O8Ux0fY_OzaSXUgTFdz6Xo4,
|
|
|
21
21
|
listpick/utils/search_and_filter_utils.py,sha256=XxGfkyDVXO9OAKcftPat8IReMTFIuTH-jorxI4o84tg,3239
|
|
22
22
|
listpick/utils/searching.py,sha256=Xk5UIqamNHL2L90z3ACB_Giqdpi9iRKoAJ6pKaqaD7Q,3093
|
|
23
23
|
listpick/utils/sorting.py,sha256=WZZiVlVA3Zkcpwji3U5SNFlQ14zVEk3cZJtQirBkecQ,5329
|
|
24
|
-
listpick/utils/table_to_list_of_lists.py,sha256=
|
|
25
|
-
listpick/utils/utils.py,sha256=
|
|
26
|
-
listpick-0.1.
|
|
27
|
-
listpick-0.1.
|
|
28
|
-
listpick-0.1.
|
|
29
|
-
listpick-0.1.
|
|
30
|
-
listpick-0.1.
|
|
31
|
-
listpick-0.1.
|
|
24
|
+
listpick/utils/table_to_list_of_lists.py,sha256=Ox_4OWtZcFp5XWcItlMqE6_Q27YiJz7M9w23Y9vfzYQ,7604
|
|
25
|
+
listpick/utils/utils.py,sha256=McOl9uT3jh7l4TIWeSd8ZGjK_e7r0YZF0Gl20yI6fl0,13873
|
|
26
|
+
listpick-0.1.14.1.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
|
|
27
|
+
listpick-0.1.14.1.dist-info/METADATA,sha256=pEzvDtilAXf9X5luuKJXLfL25sHxBiaVAVrirREgt0I,8090
|
|
28
|
+
listpick-0.1.14.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
listpick-0.1.14.1.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
|
|
30
|
+
listpick-0.1.14.1.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
|
|
31
|
+
listpick-0.1.14.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|