listpick 0.1.16.11__py3-none-any.whl → 0.1.16.13__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 CHANGED
@@ -18,9 +18,13 @@ from wcwidth import wcswidth
18
18
  from typing import Callable, Optional, Tuple, Dict
19
19
  import json
20
20
  import threading
21
+ import multiprocessing
21
22
  import string
22
23
  import logging
23
24
  import copy
25
+ import tempfile
26
+ import queue
27
+ from listpick.utils.generate_data_utils import ProcessSafePriorityQueue
24
28
 
25
29
  from listpick.pane.pane_utils import get_file_attributes
26
30
  from listpick.pane.left_pane_functions import *
@@ -70,9 +74,9 @@ class Picker:
70
74
  auto_refresh: bool =False,
71
75
  timer: float = 5,
72
76
 
73
- get_new_data: bool =False, # Whether we can get new data
74
- refresh_function: Optional[Callable] = lambda items, header, visible_rows_indices, getting_data: None, # The function with which we get new data
75
- get_data_startup: bool =False, # Whether we should get data at statrup
77
+ get_new_data: bool =False,
78
+ refresh_function: Optional[Callable] = lambda items, header, visible_rows_indices, getting_data: None,
79
+ get_data_startup: bool =False,
76
80
  track_entries_upon_refresh: bool = True,
77
81
  pin_cursor: bool = False,
78
82
  id_column: int = 0,
@@ -95,8 +99,12 @@ class Picker:
95
99
  user_opts : str = "",
96
100
  options_list: list[str] = [],
97
101
  user_settings : str = "",
102
+
98
103
  separator : str = " ",
99
104
  header_separator : str = " │",
105
+ header_separator_before_selected_column : str = " ▐",
106
+
107
+
100
108
  search_query : str = "",
101
109
  search_count : int = 0,
102
110
  search_index : int = 0,
@@ -111,6 +119,10 @@ class Picker:
111
119
  highlight_full_row: bool =False,
112
120
  crosshair_cursor: bool = False,
113
121
  cell_cursor: bool = True,
122
+ selected_char: str = "",
123
+ unselected_char: str = "",
124
+ selecting_char: str = "",
125
+ deselecting_char: str = "",
114
126
 
115
127
  items_per_page : int = -1,
116
128
  sort_method : int = 0,
@@ -197,6 +209,8 @@ class Picker:
197
209
  left_pane_index: int = 0,
198
210
 
199
211
  screen_size_function = lambda stdscr: os.get_terminal_size()[::-1],
212
+ generate_data_for_hidden_columns: bool = False,
213
+
200
214
 
201
215
  # getting_data: threading.Event = threading.Event(),
202
216
 
@@ -245,6 +259,7 @@ class Picker:
245
259
  self.user_settings = user_settings
246
260
  self.separator = separator
247
261
  self.header_separator = header_separator
262
+ self.header_separator_before_selected_column = header_separator_before_selected_column
248
263
  self.search_query = search_query
249
264
  self.search_count = search_count
250
265
  self.search_index = search_index
@@ -259,6 +274,10 @@ class Picker:
259
274
  self.highlight_full_row = highlight_full_row
260
275
  self.crosshair_cursor = crosshair_cursor
261
276
  self.cell_cursor = cell_cursor
277
+ self.selected_char = selected_char
278
+ self.unselected_char = unselected_char
279
+ self.selecting_char = selecting_char
280
+ self.deselecting_char = deselecting_char
262
281
 
263
282
  self.items_per_page = items_per_page
264
283
  self.sort_method = sort_method
@@ -361,7 +380,17 @@ class Picker:
361
380
 
362
381
  self.visible_rows_indices = []
363
382
 
383
+ self.generate_data_for_hidden_columns = generate_data_for_hidden_columns
384
+ self.thread_stop_event = threading.Event()
385
+ self.data_generation_queue = queue.PriorityQueue()
386
+ self.threads = []
387
+
364
388
 
389
+ self.process_manager = multiprocessing.Manager()
390
+ # self.data_generation_queue = ProcessSafePriorityQueue
391
+ self.processes = []
392
+ self.items_sync_loop_event = threading.Event()
393
+ self.items_sync_thread = None
365
394
 
366
395
 
367
396
  self.initialise_picker_state(reset_colours=self.reset_colours)
@@ -541,8 +570,8 @@ class Picker:
541
570
  self.top_space += ((self.term_h-(self.top_space+self.bottom_space))-len(self.indexed_items))//2
542
571
 
543
572
  # self.column_widths
544
- visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
545
- visible_columns_total_width = sum(visible_column_widths) + len(self.separator)*(len(visible_column_widths)-1)
573
+ self.visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
574
+ visible_columns_total_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
546
575
 
547
576
  # self.startx
548
577
  self.startx = 1 if self.highlight_full_row else 2
@@ -649,7 +678,7 @@ class Picker:
649
678
  tracking = False
650
679
 
651
680
  ## Get data synchronously
652
- if get_data and self.refresh_function != None:
681
+ if get_data:
653
682
  # Track cursor_pos and selections by ther id (row[self.id_column][col])
654
683
  if self.track_entries_upon_refresh and len(self.items) > 0:
655
684
  tracking = True
@@ -665,7 +694,13 @@ class Picker:
665
694
  # Set the state of the threading event
666
695
  # Though we are getting data synchronously, we ensure the correct state for self.getting_data
667
696
  self.getting_data.clear()
668
- self.refresh_function(self.items, self.header, self.visible_rows_indices, self.getting_data)
697
+ self.refresh_function(
698
+ self.items,
699
+ self.header,
700
+ self.visible_rows_indices,
701
+ self.getting_data,
702
+ self.get_function_data(),
703
+ )
669
704
 
670
705
 
671
706
  # Ensure that an emtpy items object has the form [[]]
@@ -942,8 +977,8 @@ class Picker:
942
977
 
943
978
  self.get_visible_rows()
944
979
  self.column_widths = get_column_widths(self.visible_rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
945
- visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
946
- visible_columns_total_width = sum(visible_column_widths) + len(self.separator)*(len(visible_column_widths)-1)
980
+ self.visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
981
+ visible_columns_total_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
947
982
 
948
983
 
949
984
  ## Display title
@@ -990,7 +1025,10 @@ class Picker:
990
1025
 
991
1026
 
992
1027
  header_str += f"{col_str:^{self.column_widths[i]-len(number)}}"
993
- header_str += self.header_separator
1028
+ if i == self.selected_column-1:
1029
+ header_str += self.header_separator_before_selected_column
1030
+ else:
1031
+ header_str += self.header_separator
994
1032
  header_str_w = min(self.rows_w-self.left_gutter_width, visible_columns_total_width+1, self.term_w-self.startx)
995
1033
 
996
1034
  header_str = header_str[self.leftmost_char:]
@@ -1150,10 +1188,11 @@ class Picker:
1150
1188
  match = re.search(highlight["match"], truncate_to_display_width(item[1][highlight["field"]], self.column_widths[highlight["field"]], centre=False, unicode_char_width=self.unicode_char_width), re.IGNORECASE)
1151
1189
  if not match: continue
1152
1190
  field_start = sum([width for i, width in enumerate(self.column_widths[:highlight["field"]]) if i not in self.hidden_columns]) + sum([1 for i in range(highlight["field"]) if i not in self.hidden_columns])*wcswidth(self.separator)
1191
+ width = min(self.column_widths[highlight["field"]]-(field_start-self.leftmost_char), self.rows_w-self.left_gutter_width)
1153
1192
 
1154
1193
  ## We want to search the non-centred values but highlight the centred values.
1155
1194
  if self.centre_in_cols:
1156
- tmp = truncate_to_display_width(item[1][highlight["field"]], self.column_widths[highlight["field"]], self.centre_in_cols, self.unicode_char_width)
1195
+ tmp = truncate_to_display_width(item[1][highlight["field"]], width, self.centre_in_cols, self.unicode_char_width)
1157
1196
  field_start += (len(tmp) - len(tmp.lstrip()))
1158
1197
 
1159
1198
  highlight_start = field_start + match.start()
@@ -1164,7 +1203,7 @@ class Picker:
1164
1203
  continue
1165
1204
  highlight_start -= self.leftmost_char
1166
1205
  highlight_end -= self.leftmost_char
1167
- self.stdscr.addstr(y, max(self.startx, self.startx+highlight_start), row_str[max(highlight_start,0):min(self.rows_w-self.startx, highlight_end)], curses.color_pair(self.colours_start+highlight["color"]) | curses.A_BOLD)
1206
+ self.stdscr.addstr(y, max(self.startx, self.startx+highlight_start), row_str[max(highlight_start,0):min(self.rows_w-self.left_gutter_width, highlight_end)], curses.color_pair(self.colours_start+highlight["color"]) | curses.A_BOLD)
1168
1207
  except:
1169
1208
  pass
1170
1209
 
@@ -1180,7 +1219,7 @@ class Picker:
1180
1219
  l0_highlights, l1_highlights, l2_highlights = sort_highlights(self.highlights)
1181
1220
 
1182
1221
 
1183
- row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
1222
+ row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
1184
1223
  for idx in range(start_index, end_index):
1185
1224
  item = self.indexed_items[idx]
1186
1225
  y = idx - start_index + self.top_space
@@ -1200,7 +1239,7 @@ class Picker:
1200
1239
  # trunc_width = 0
1201
1240
 
1202
1241
 
1203
- trunc_width = min(self.rows_w-self.left_gutter_width, row_width, self.term_w - self.startx)
1242
+ trunc_width = max(0, min(self.rows_w-self.left_gutter_width, row_width, self.term_w - self.startx))
1204
1243
 
1205
1244
  row_str = truncate_to_display_width(row_str_left_adj, trunc_width, self.unicode_char_width)
1206
1245
  # row_str = truncate_to_display_width(row_str, min(w-self.startx, visible_columns_total_width))[self.leftmost_char:]
@@ -1211,7 +1250,7 @@ class Picker:
1211
1250
 
1212
1251
  ## Highlight column
1213
1252
  if self.crosshair_cursor:
1214
- highlight_cell(idx, self.selected_column, visible_column_widths, colour_pair_number=27, bold=False, y=y)
1253
+ highlight_cell(idx, self.selected_column, self.visible_column_widths, colour_pair_number=27, bold=False, y=y)
1215
1254
  if idx == self.cursor_pos:
1216
1255
  self.stdscr.addstr(y, self.startx, row_str[:min(self.rows_w-self.startx, visible_columns_total_width)], curses.color_pair(self.colours_start+27))
1217
1256
 
@@ -1225,21 +1264,21 @@ class Picker:
1225
1264
  # self.selected_cells_by_row = get_selected_cells_by_row(self.cell_selections)
1226
1265
  if item[0] in self.selected_cells_by_row:
1227
1266
  for j in self.selected_cells_by_row[item[0]]:
1228
- highlight_cell(idx, j, visible_column_widths, colour_pair_number=25, bold=False, y=y)
1267
+ highlight_cell(idx, j, self.visible_column_widths, colour_pair_number=25, bold=False, y=y)
1229
1268
 
1230
1269
  # Visually selected
1231
1270
  if self.is_selecting:
1232
1271
  if self.start_selection <= idx <= self.cursor_pos or self.start_selection >= idx >= self.cursor_pos:
1233
1272
  x_interval = range(min(self.start_selection_col, self.selected_column), max(self.start_selection_col, self.selected_column)+1)
1234
1273
  for col in x_interval:
1235
- highlight_cell(idx, col, visible_column_widths, colour_pair_number=25, bold=False, y=y)
1274
+ highlight_cell(idx, col, self.visible_column_widths, colour_pair_number=25, bold=False, y=y)
1236
1275
 
1237
1276
  # Visually deslected
1238
1277
  if self.is_deselecting:
1239
1278
  if self.start_selection >= idx >= self.cursor_pos or self.start_selection <= idx <= self.cursor_pos:
1240
1279
  x_interval = range(min(self.start_selection_col, self.selected_column), max(self.start_selection_col, self.selected_column)+1)
1241
1280
  for col in x_interval:
1242
- highlight_cell(idx, col, visible_column_widths, colour_pair_number=26, bold=False,y=y)
1281
+ highlight_cell(idx, col, self.visible_column_widths, colour_pair_number=26, bold=False,y=y)
1243
1282
  # Higlight cursor row and selected rows
1244
1283
  elif self.highlight_full_row:
1245
1284
  if self.selections[item[0]]:
@@ -1256,16 +1295,30 @@ class Picker:
1256
1295
 
1257
1296
  # Highlight the cursor row and the first char of the selected rows.
1258
1297
  else:
1259
- if self.selections[item[0]]:
1260
- self.stdscr.addstr(y, max(self.startx-2,0), ' ', curses.color_pair(self.colours_start+1))
1261
- # Visually selected
1262
- if self.is_selecting:
1263
- if self.start_selection <= idx <= self.cursor_pos or self.start_selection >= idx >= self.cursor_pos:
1298
+ if self.selected_char:
1299
+ if self.selections[item[0]]:
1300
+ self.stdscr.addstr(y, max(self.startx-2,0), self.selected_char, curses.color_pair(self.colours_start+2))
1301
+ else:
1302
+ self.stdscr.addstr(y, max(self.startx-2,0), self.unselected_char, curses.color_pair(self.colours_start+2))
1303
+ # Visually selected
1304
+ if self.is_selecting:
1305
+ if self.start_selection <= idx <= self.cursor_pos or self.start_selection >= idx >= self.cursor_pos:
1306
+ self.stdscr.addstr(y, max(self.startx-2,0), self.selecting_char, curses.color_pair(self.colours_start+2))
1307
+ # Visually deslected
1308
+ if self.is_deselecting:
1309
+ if self.start_selection >= idx >= self.cursor_pos or self.start_selection <= idx <= self.cursor_pos:
1310
+ self.stdscr.addstr(y, max(self.startx-2,0), self.deselecting_char, curses.color_pair(self.colours_start+2))
1311
+ else:
1312
+ if self.selections[item[0]]:
1264
1313
  self.stdscr.addstr(y, max(self.startx-2,0), ' ', curses.color_pair(self.colours_start+1))
1265
- # Visually deslected
1266
- if self.is_deselecting:
1267
- if self.start_selection >= idx >= self.cursor_pos or self.start_selection <= idx <= self.cursor_pos:
1268
- self.stdscr.addstr(y, max(self.startx-2,0), ' ', curses.color_pair(self.colours_start+10))
1314
+ # Visually selected
1315
+ if self.is_selecting:
1316
+ if self.start_selection <= idx <= self.cursor_pos or self.start_selection >= idx >= self.cursor_pos:
1317
+ self.stdscr.addstr(y, max(self.startx-2,0), ' ', curses.color_pair(self.colours_start+1))
1318
+ # Visually deslected
1319
+ if self.is_deselecting:
1320
+ if self.start_selection >= idx >= self.cursor_pos or self.start_selection <= idx <= self.cursor_pos:
1321
+ self.stdscr.addstr(y, max(self.startx-2,0), ' ', curses.color_pair(self.colours_start+10))
1269
1322
 
1270
1323
  if not self.highlights_hide:
1271
1324
  draw_highlights(l1_highlights, idx, y, item)
@@ -1275,7 +1328,7 @@ class Picker:
1275
1328
  # Draw cursor
1276
1329
  if idx == self.cursor_pos:
1277
1330
  if self.cell_cursor:
1278
- highlight_cell(idx, self.selected_column, visible_column_widths, colour_pair_number=5, bold=True, y=y)
1331
+ highlight_cell(idx, self.selected_column, self.visible_column_widths, colour_pair_number=5, bold=True, y=y)
1279
1332
  else:
1280
1333
  self.stdscr.addstr(y, self.startx, row_str[:self.rows_w-self.left_gutter_width], curses.color_pair(self.colours_start+5) | curses.A_BOLD)
1281
1334
 
@@ -1380,6 +1433,21 @@ class Picker:
1380
1433
  # self.stdscr.timeout(2000) # timeout is set to 50 in order to get the infobox to be displayed so here we reset it to 2000
1381
1434
 
1382
1435
 
1436
+ def refresh_and_draw_screen(self):
1437
+ """
1438
+ Clears and refreshes the screen, restricts and unrestricts curses,
1439
+ ensures correct terminal settings, and then draws the screen.
1440
+ """
1441
+
1442
+ self.logger.info(f"key_function redraw_screen")
1443
+ self.stdscr.clear()
1444
+ self.stdscr.refresh()
1445
+ restrict_curses(self.stdscr)
1446
+ unrestrict_curses(self.stdscr)
1447
+ self.stdscr.clear()
1448
+ self.stdscr.refresh()
1449
+
1450
+ self.draw_screen()
1383
1451
 
1384
1452
  def infobox(self, stdscr: curses.window, message: str ="", title: str ="Infobox", colours_end: int = 0, duration: int = 4) -> curses.window:
1385
1453
  """ Display non-interactive infobox window. """
@@ -1435,116 +1503,126 @@ class Picker:
1435
1503
  self.logger.debug(f"function: get_function_data()")
1436
1504
  """ Returns a dict of the main variables needed to restore the state of list_pikcer. """
1437
1505
  function_data = {
1438
- "selections": self.selections,
1439
- "cell_selections": self.cell_selections,
1440
- "selected_cells_by_row": self.selected_cells_by_row,
1441
- "items_per_page": self.items_per_page,
1442
- "current_row": self.current_row,
1443
- "current_page": self.current_page,
1444
- "cursor_pos": self.cursor_pos,
1445
- "colours": self.colours,
1446
- "colour_theme_number": self.colour_theme_number,
1447
- "selected_column": self.selected_column,
1448
- "sort_column": self.sort_column,
1449
- "sort_method": self.sort_method,
1450
- "sort_reverse": self.sort_reverse,
1451
- "SORT_METHODS": self.SORT_METHODS,
1452
- "hidden_columns": self.hidden_columns,
1453
- "is_selecting": self.is_selecting,
1454
- "is_deselecting": self.is_deselecting,
1455
- "user_opts": self.user_opts,
1456
- "options_list": self.options_list,
1457
- "user_settings": self.user_settings,
1458
- "separator": self.separator,
1459
- "search_query": self.search_query,
1460
- "search_count": self.search_count,
1461
- "search_index": self.search_index,
1462
- "filter_query": self.filter_query,
1463
- "indexed_items": self.indexed_items,
1464
- "start_selection": self.start_selection,
1465
- "start_selection_col": self.start_selection_col,
1466
- "end_selection": self.end_selection,
1467
- "highlights": self.highlights,
1468
- "max_column_width": self.max_column_width,
1469
- "column_indices": self.column_indices,
1470
- "mode_index": self.mode_index,
1471
- "modes": self.modes,
1472
- "title": self.title,
1473
- "display_modes": self.display_modes,
1474
- "require_option": self.require_option,
1475
- "require_option_default": self.require_option_default,
1476
- "option_functions": self.option_functions,
1477
- "top_gap": self.top_gap,
1478
- "number_columns": self.number_columns,
1479
- "items": self.items,
1480
- "indexed_items": self.indexed_items,
1481
- "header": self.header,
1482
- "scroll_bar": self.scroll_bar,
1483
- "columns_sort_method": self.columns_sort_method,
1484
- "disabled_keys": self.disabled_keys,
1485
- "show_footer": self.show_footer,
1486
- "footer_string": self.footer_string,
1487
- "footer_string_auto_refresh": self.footer_string_auto_refresh,
1488
- "footer_string_refresh_function": self.footer_string_refresh_function,
1489
- "footer_timer": self.footer_timer,
1490
- "footer_style": self.footer_style,
1491
- "colours_start": self.colours_start,
1492
- "colours_end": self.colours_end,
1493
- "display_only": self.display_only,
1494
- "infobox_items": self.infobox_items,
1495
- "display_infobox": self.display_infobox,
1496
- "infobox_title": self.infobox_title,
1497
- "key_remappings": self.key_remappings,
1498
- "auto_refresh": self.auto_refresh,
1499
- "get_new_data": self.get_new_data,
1500
- "refresh_function": self.refresh_function,
1501
- "timer": self.timer,
1502
- "get_data_startup": self.get_data_startup,
1503
- "get_footer_string_startup": self.get_footer_string_startup,
1504
- "editable_columns": self.editable_columns,
1505
- "last_key": self.last_key,
1506
- "centre_in_terminal": self.centre_in_terminal,
1507
- "centre_in_terminal_vertical": self.centre_in_terminal_vertical,
1508
- "centre_in_cols": self.centre_in_cols,
1509
- "highlight_full_row": self.highlight_full_row,
1510
- "cell_cursor": self.cell_cursor,
1511
- "column_widths": self.column_widths,
1512
- "track_entries_upon_refresh": self.track_entries_upon_refresh,
1513
- "pin_cursor": self.pin_cursor,
1514
- "id_column": self.id_column,
1515
- "startup_notification": self.startup_notification,
1516
- "keys_dict": self.keys_dict,
1517
- "cancel_is_back": self.cancel_is_back,
1518
- "paginate": self.paginate,
1519
- "leftmost_char": self.leftmost_char,
1520
- "history_filter_and_search" : self.history_filter_and_search,
1521
- "history_pipes" : self.history_pipes,
1522
- "history_opts" : self.history_opts,
1523
- "history_edits" : self.history_edits,
1524
- "history_settings": self.history_settings,
1525
- "show_header": self.show_header,
1526
- "show_row_header": self.show_row_header,
1527
- "debug": self.debug,
1528
- "debug_level": self.debug_level,
1529
- "reset_colours": self.reset_colours,
1530
- "unicode_char_width": self.unicode_char_width,
1531
- "command_stack": self.command_stack,
1532
- "loaded_file": self.loaded_file,
1533
- "loaded_files": self.loaded_files,
1534
- "loaded_file_index": self.loaded_file_index,
1535
- "loaded_file_states": self.loaded_file_states,
1536
- "sheet_index": self.sheet_index,
1537
- "sheets": self.sheets,
1538
- "sheet_name": self.sheet_name,
1539
- "sheet_states": self.sheet_states,
1540
- "split_right": self.split_right,
1541
- "right_panes": self.right_panes,
1542
- "right_pane_index": self.right_pane_index,
1543
- "split_left": self.split_left,
1544
- "left_panes": self.left_panes,
1545
- "left_pane_index": self.left_pane_index,
1546
- "crosshair_cursor": self.crosshair_cursor,
1547
-
1506
+ "self": self,
1507
+ "selections": self.selections,
1508
+ "cell_selections": self.cell_selections,
1509
+ "selected_cells_by_row": self.selected_cells_by_row,
1510
+ "items_per_page": self.items_per_page,
1511
+ "current_row": self.current_row,
1512
+ "current_page": self.current_page,
1513
+ "cursor_pos": self.cursor_pos,
1514
+ "colours": self.colours,
1515
+ "colour_theme_number": self.colour_theme_number,
1516
+ "selected_column": self.selected_column,
1517
+ "sort_column": self.sort_column,
1518
+ "sort_method": self.sort_method,
1519
+ "sort_reverse": self.sort_reverse,
1520
+ "SORT_METHODS": self.SORT_METHODS,
1521
+ "hidden_columns": self.hidden_columns,
1522
+ "is_selecting": self.is_selecting,
1523
+ "is_deselecting": self.is_deselecting,
1524
+ "user_opts": self.user_opts,
1525
+ "options_list": self.options_list,
1526
+ "user_settings": self.user_settings,
1527
+ "separator": self.separator,
1528
+ "header_separator": self.header_separator,
1529
+ "header_separator_before_selected_column": self.header_separator_before_selected_column,
1530
+ "search_query": self.search_query,
1531
+ "search_count": self.search_count,
1532
+ "search_index": self.search_index,
1533
+ "filter_query": self.filter_query,
1534
+ "indexed_items": self.indexed_items,
1535
+ "start_selection": self.start_selection,
1536
+ "start_selection_col": self.start_selection_col,
1537
+ "end_selection": self.end_selection,
1538
+ "highlights": self.highlights,
1539
+ "max_column_width": self.max_column_width,
1540
+ "column_indices": self.column_indices,
1541
+ "mode_index": self.mode_index,
1542
+ "modes": self.modes,
1543
+ "title": self.title,
1544
+ "display_modes": self.display_modes,
1545
+ "require_option": self.require_option,
1546
+ "require_option_default": self.require_option_default,
1547
+ "option_functions": self.option_functions,
1548
+ "top_gap": self.top_gap,
1549
+ "number_columns": self.number_columns,
1550
+ "items": self.items,
1551
+ "indexed_items": self.indexed_items,
1552
+ "header": self.header,
1553
+ "scroll_bar": self.scroll_bar,
1554
+ "columns_sort_method": self.columns_sort_method,
1555
+ "disabled_keys": self.disabled_keys,
1556
+ "show_footer": self.show_footer,
1557
+ "footer_string": self.footer_string,
1558
+ "footer_string_auto_refresh": self.footer_string_auto_refresh,
1559
+ "footer_string_refresh_function": self.footer_string_refresh_function,
1560
+ "footer_timer": self.footer_timer,
1561
+ "footer_style": self.footer_style,
1562
+ "colours_start": self.colours_start,
1563
+ "colours_end": self.colours_end,
1564
+ "display_only": self.display_only,
1565
+ "infobox_items": self.infobox_items,
1566
+ "display_infobox": self.display_infobox,
1567
+ "infobox_title": self.infobox_title,
1568
+ "key_remappings": self.key_remappings,
1569
+ "auto_refresh": self.auto_refresh,
1570
+ "get_new_data": self.get_new_data,
1571
+ "refresh_function": self.refresh_function,
1572
+ "timer": self.timer,
1573
+ "get_data_startup": self.get_data_startup,
1574
+ "get_footer_string_startup": self.get_footer_string_startup,
1575
+ "editable_columns": self.editable_columns,
1576
+ "last_key": self.last_key,
1577
+ "centre_in_terminal": self.centre_in_terminal,
1578
+ "centre_in_terminal_vertical": self.centre_in_terminal_vertical,
1579
+ "centre_in_cols": self.centre_in_cols,
1580
+ "highlight_full_row": self.highlight_full_row,
1581
+ "cell_cursor": self.cell_cursor,
1582
+ "column_widths": self.column_widths,
1583
+ "track_entries_upon_refresh": self.track_entries_upon_refresh,
1584
+ "pin_cursor": self.pin_cursor,
1585
+ "id_column": self.id_column,
1586
+ "startup_notification": self.startup_notification,
1587
+ "keys_dict": self.keys_dict,
1588
+ "cancel_is_back": self.cancel_is_back,
1589
+ "paginate": self.paginate,
1590
+ "leftmost_char": self.leftmost_char,
1591
+ "history_filter_and_search" : self.history_filter_and_search,
1592
+ "history_pipes" : self.history_pipes,
1593
+ "history_opts" : self.history_opts,
1594
+ "history_edits" : self.history_edits,
1595
+ "history_settings": self.history_settings,
1596
+ "show_header": self.show_header,
1597
+ "show_row_header": self.show_row_header,
1598
+ "debug": self.debug,
1599
+ "debug_level": self.debug_level,
1600
+ "reset_colours": self.reset_colours,
1601
+ "unicode_char_width": self.unicode_char_width,
1602
+ "command_stack": self.command_stack,
1603
+ "loaded_file": self.loaded_file,
1604
+ "loaded_files": self.loaded_files,
1605
+ "loaded_file_index": self.loaded_file_index,
1606
+ "loaded_file_states": self.loaded_file_states,
1607
+ "sheet_index": self.sheet_index,
1608
+ "sheets": self.sheets,
1609
+ "sheet_name": self.sheet_name,
1610
+ "sheet_states": self.sheet_states,
1611
+ "split_right": self.split_right,
1612
+ "right_panes": self.right_panes,
1613
+ "right_pane_index": self.right_pane_index,
1614
+ "split_left": self.split_left,
1615
+ "left_panes": self.left_panes,
1616
+ "left_pane_index": self.left_pane_index,
1617
+ "crosshair_cursor": self.crosshair_cursor,
1618
+ "generate_data_for_hidden_columns": self.generate_data_for_hidden_columns,
1619
+ "thread_stop_event": self.thread_stop_event,
1620
+ "data_generation_queue": self.data_generation_queue,
1621
+ "process_manager": self.process_manager,
1622
+ "threads": self.threads,
1623
+ "processes": self.processes,
1624
+ "items_sync_loop_event": self.items_sync_loop_event,
1625
+ "items_sync_thread": self.items_sync_thread,
1548
1626
  }
1549
1627
  return function_data
1550
1628
 
@@ -1597,15 +1675,6 @@ class Picker:
1597
1675
  self.initialise_picker_state(reset_colours=reset_colours)
1598
1676
 
1599
1677
  self.initialise_variables()
1600
- # if "colour_theme_number" in function_data:
1601
- # global COLOURS_SET
1602
- # COLOURS_SET = False
1603
- # colours_end = set_colours(pick=self.colour_theme_number, start=self.colours_start)
1604
-
1605
- # if "items" in function_data: self.items = function_data["items"]
1606
- # if "header" in function_data: self.header = function_data["header"]
1607
- # self.indexed_items = function_data["indexed_items"] if "indexed_items" in function_data else []
1608
-
1609
1678
 
1610
1679
 
1611
1680
  def delete_entries(self) -> None:
@@ -1679,6 +1748,7 @@ class Picker:
1679
1748
  "split_left": False,
1680
1749
  "cell_cursor": False,
1681
1750
  "crosshair_cursor": False,
1751
+ "header_separator": " │",
1682
1752
  }
1683
1753
  while True:
1684
1754
  self.update_term_size()
@@ -1701,6 +1771,102 @@ class Picker:
1701
1771
  return {}, "", f
1702
1772
 
1703
1773
 
1774
+ def select_columns(
1775
+ self,
1776
+ stdscr: curses.window,
1777
+ # options: list[list[str]] =[],
1778
+ # title: str = "Choose option",
1779
+ # x:int=0,
1780
+ # y:int=0,
1781
+ # literal:bool=False,
1782
+ # colours_start:int=0,
1783
+ # header: list[str] = [],
1784
+ # require_option:list = [],
1785
+ # option_functions: list = [],
1786
+ ) -> Tuple[dict, str, dict]:
1787
+ """
1788
+ Display input field at x,y
1789
+
1790
+ ---Arguments
1791
+ stdscr: curses screen
1792
+ usrtxt (str): text to be edited by the user
1793
+ title (str): The text to be displayed at the start of the text option picker
1794
+ x (int): prompt begins at (x,y) in the screen given
1795
+ y (int): prompt begins at (x,y) in the screen given
1796
+ colours_start (bool): start index of curses init_pair.
1797
+
1798
+ ---Returns
1799
+ usrtxt, return_code
1800
+ usrtxt: the text inputted by the user
1801
+ return_code:
1802
+ 0: user hit escape
1803
+ 1: user hit return
1804
+ """
1805
+ self.logger.info(f"function: select_columns()")
1806
+
1807
+ cursor = 0
1808
+
1809
+ if self.header:
1810
+ columns = [s for i, s in enumerate(self.header)]
1811
+ else:
1812
+ columns = [f"" for i in range(len(self.column_widths))]
1813
+
1814
+ ## Column info variable
1815
+ columns_set = [[f"{i}", columns[i]] for i in range(len(self.column_widths))]
1816
+ header = ["#", "Column Name"]
1817
+
1818
+ selected = [False if i in self.hidden_columns else True for i in range(len(self.column_widths))]
1819
+ selected = {i: False if i in self.hidden_columns else True for i in range(len(self.column_widths))}
1820
+
1821
+ option_picker_data = {
1822
+ "items": columns_set,
1823
+ "colours": notification_colours,
1824
+ "colours_start": self.notification_colours_start,
1825
+ "title":"Select Columns",
1826
+ "header": header,
1827
+ "hidden_columns":[],
1828
+ # "require_option":require_option,
1829
+ # "keys_dict": options_keys,
1830
+ "selections": selected,
1831
+ "show_footer": False,
1832
+ "cancel_is_back": True,
1833
+ "number_columns": False,
1834
+ "reset_colours": False,
1835
+ "split_right": False,
1836
+ "split_left": False,
1837
+ "cell_cursor": False,
1838
+ "crosshair_cursor": False,
1839
+ "separator": " ",
1840
+ "header_separator": " │",
1841
+ "header_separator_before_selected_column": " ▐",
1842
+ "selected_char": "☒",
1843
+ "unselected_char": "☐",
1844
+ "selecting_char": "☒",
1845
+ "deselecting_char": "☐",
1846
+ }
1847
+ while True:
1848
+ self.update_term_size()
1849
+
1850
+ choose_opts_widths = get_column_widths(columns_set, unicode_char_width=self.unicode_char_width)
1851
+ window_width = min(max(sum(choose_opts_widths) + 6, 50) + 6, self.term_w)
1852
+ window_height = min(self.term_h//2, max(6, len(columns_set)+3))
1853
+
1854
+ submenu_win = curses.newwin(window_height, window_width, (self.term_h-window_height)//2, (self.term_w-window_width)//2)
1855
+ submenu_win.keypad(True)
1856
+ option_picker_data["screen_size_function"] = lambda stdscr: (window_height, window_width)
1857
+ OptionPicker = Picker(submenu_win, **option_picker_data)
1858
+ s, o, f = OptionPicker.run()
1859
+
1860
+ if o == "refresh":
1861
+ self.draw_screen()
1862
+ continue
1863
+ if s:
1864
+ selected_columns = s
1865
+ self.hidden_columns = [i for i in range(len(self.column_widths)) if i not in selected_columns]
1866
+
1867
+ # return {x: options[x] for x in s}, o, f
1868
+ break
1869
+ return {}, "", f
1704
1870
 
1705
1871
  def notification(self, stdscr: curses.window, message: str="", title:str="Notification", colours_end: int=0, duration:int=4) -> None:
1706
1872
 
@@ -1743,7 +1909,6 @@ class Picker:
1743
1909
  "crosshair_cursor": False,
1744
1910
  "show_header": False,
1745
1911
  "screen_size_function": lambda stdscr: (notification_height, notification_width),
1746
-
1747
1912
  }
1748
1913
  OptionPicker = Picker(submenu_win, **notification_data)
1749
1914
  s, o, f = OptionPicker.run()
@@ -1947,6 +2112,9 @@ class Picker:
1947
2112
  self.draw_screen()
1948
2113
  self.notification(self.stdscr, message=f"Theme {self.colour_theme_number} applied.")
1949
2114
  self.colours = get_colours(self.colour_theme_number)
2115
+ elif setting == "colsel":
2116
+ self.draw_screen()
2117
+ self.select_columns(self.stdscr)
1950
2118
 
1951
2119
  else:
1952
2120
  self.user_settings = ""
@@ -2312,7 +2480,13 @@ class Picker:
2312
2480
  self.logger.info(f"function: fetch_data()")
2313
2481
  tmp_items, tmp_header = [], []
2314
2482
  self.getting_data.clear()
2315
- self.refresh_function(tmp_items, tmp_header, self.visible_rows_indices, self.getting_data)
2483
+ self.refresh_function(
2484
+ tmp_items,
2485
+ tmp_header,
2486
+ self.visible_rows_indices,
2487
+ self.getting_data,
2488
+ self.get_function_data(),
2489
+ )
2316
2490
  if self.track_entries_upon_refresh:
2317
2491
  selected_indices = get_selected_indices(self.selections)
2318
2492
  self.ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
@@ -2410,10 +2584,6 @@ class Picker:
2410
2584
  row_len = 1
2411
2585
  if self.header: row_len = len(self.header)
2412
2586
  elif len(self.items): row_len = len(self.items[0])
2413
- # if len(self.indexed_items) == 0:
2414
- # insert_at_pos = 0
2415
- # else:
2416
- # insert_at_pos = self.indexed_items[self.cursor_pos][0]
2417
2587
  self.items = self.items[:pos] + [["" for x in range(row_len)]] + self.items[pos:]
2418
2588
  if pos <= self.cursor_pos:
2419
2589
  self.cursor_pos += 1
@@ -2563,18 +2733,45 @@ class Picker:
2563
2733
  max_total_width=self.rows_w,
2564
2734
  unicode_char_width=self.unicode_char_width
2565
2735
  )
2736
+ self.calculate_section_sizes()
2566
2737
 
2567
- row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
2738
+ row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
2568
2739
  if row_width - self.leftmost_char < self.rows_w:
2569
2740
  if row_width <= self.rows_w - self.left_gutter_width:
2570
2741
  self.leftmost_char = 0
2571
2742
  else:
2572
2743
  self.leftmost_char = row_width - (self.rows_w - self.left_gutter_width) + 5
2573
2744
 
2745
+ def cleanup_processes(self):
2746
+ self.thread_stop_event.set()
2747
+ self.data_generation_queue.clear()
2748
+ # with self.data_generation_queue.mutex:
2749
+ # self.data_generation_queue.queue.clear()
2750
+ function_data = self.get_function_data()
2751
+ for proc in self.processes:
2752
+ if proc.is_alive():
2753
+ proc.terminate()
2754
+ proc.join(timeout=0.01)
2755
+ self.processes = []
2756
+ self.items_sync_loop_event.set()
2757
+ if self.items_sync_thread != None:
2758
+ self.items_sync_thread.join(timeout=1)
2759
+
2760
+ def cleanup_threads(self):
2761
+ self.thread_stop_event.set()
2762
+ with self.data_generation_queue.mutex:
2763
+ self.data_generation_queue.queue.clear()
2764
+ function_data = self.get_function_data()
2765
+ for t in self.threads:
2766
+ if t.is_alive():
2767
+ t.join(timeout=0.01)
2768
+
2574
2769
  def run(self) -> Tuple[list[int], str, dict]:
2575
2770
  """ Run the picker. """
2576
2771
  self.logger.info(f"function: run()")
2577
2772
 
2773
+ self.thread_stop_event.clear()
2774
+
2578
2775
  if self.get_footer_string_startup and self.footer_string_refresh_function != None:
2579
2776
  self.footer_string = " "
2580
2777
  self.footer.adjust_sizes(self.term_h, self.term_w)
@@ -2679,9 +2876,12 @@ class Picker:
2679
2876
 
2680
2877
  elif self.check_key("refresh", key, self.keys_dict) or self.remapped_key(key, curses.KEY_F5, self.key_remappings) or (self.auto_refresh and (time.time() - self.initial_time) >= self.timer):
2681
2878
  self.logger.debug(f"Get new data (refresh).")
2682
- self.stdscr.addstr(0,self.term_w-3,"  ", curses.color_pair(self.colours_start+21) | curses.A_BOLD)
2879
+ try:
2880
+ self.stdscr.addstr(0,self.term_w-3,"  ", curses.color_pair(self.colours_start+21) | curses.A_BOLD)
2881
+ except:
2882
+ pass
2683
2883
  self.stdscr.refresh()
2684
- if self.get_new_data and self.refresh_function:
2884
+ if self.get_new_data:
2685
2885
  self.refreshing_data = True
2686
2886
 
2687
2887
  t = threading.Thread(target=self.fetch_data)
@@ -2824,6 +3024,14 @@ class Picker:
2824
3024
  data["option_functions"] = f"[...] length = {len(data['option_functions'])}"
2825
3025
  data["loaded_file_states"] = f"[...] length = {len(data['loaded_file_states'])}"
2826
3026
  data["sheet_states"] = f"[...] length = {len(data['sheet_states'])}"
3027
+ data["highlights"] = f"[...] length = {len(data['highlights'])}"
3028
+ data["colours"] = f"[...] length = {len(data['colours'])}"
3029
+ data["keys_dict"] = f"[...] length = {len(data['keys_dict'])}"
3030
+ data["history_filter_and_search"] = f"[...] length = {len(data['history_filter_and_search'])}"
3031
+ data["history_opts"] = f"[...] length = {len(data['history_opts'])}"
3032
+ data["history_edits"] = f"[...] length = {len(data['history_edits'])}"
3033
+ data["history_pipes"] = f"[...] length = {len(data['history_pipes'])}"
3034
+ data["history_settings"] = f"[...] length = {len(data['history_settings'])}"
2827
3035
  info_items += [
2828
3036
  ["",""],
2829
3037
  [" get_function_data()", "-*"*30],
@@ -2869,6 +3077,7 @@ class Picker:
2869
3077
  elif self.check_key("exit", key, self.keys_dict):
2870
3078
  self.stdscr.clear()
2871
3079
  if len(self.loaded_files) <= 1:
3080
+ self.cleanup_threads()
2872
3081
  function_data = self.get_function_data()
2873
3082
  restore_terminal_settings(tty_fd, self.saved_terminal_state)
2874
3083
  return [], "", function_data
@@ -2890,6 +3099,7 @@ class Picker:
2890
3099
  self.draw_screen()
2891
3100
 
2892
3101
  elif self.check_key("full_exit", key, self.keys_dict):
3102
+ self.cleanup_threads()
2893
3103
  close_curses(self.stdscr)
2894
3104
  restore_terminal_settings(tty_fd, self.saved_terminal_state)
2895
3105
  exit()
@@ -2931,11 +3141,12 @@ class Picker:
2931
3141
  options = []
2932
3142
  if len(self.items) > 0:
2933
3143
  options += [["cv", "Centre rows vertically"]]
2934
- options += [["pc", "Pin cursor to row number when data refreshes"]]
3144
+ options += [["pc", "Pin cursor to row index during data refresh."]]
2935
3145
  options += [["ct", "Centre column-set in terminal"]]
2936
3146
  options += [["cc", "Centre values in cells"]]
2937
3147
  options += [["!r", "Toggle auto-refresh"]]
2938
3148
  options += [["th", "Cycle between themes. (accepts th#)"]]
3149
+ options += [["colsel", "Toggle columns."]]
2939
3150
  options += [["nohl", "Toggle highlights"]]
2940
3151
  options += [["footer", "Toggle footer"]]
2941
3152
  options += [["header", "Toggle header"]]
@@ -3066,16 +3277,13 @@ class Picker:
3066
3277
  self.cursor_pos = new_pos
3067
3278
  self.ensure_no_overscroll()
3068
3279
  self.draw_screen()
3069
- # current_row = items_per_page - 1
3070
- # if current_page + 1 == (len(self.indexed_items) + items_per_page - 1) // items_per_page:
3071
- #
3072
- # current_row = (len(self.indexed_items) +items_per_page - 1) % items_per_page
3073
- # self.draw_screen()
3280
+
3074
3281
  elif self.check_key("enter", key, self.keys_dict):
3075
3282
  self.logger.info(f"key_function enter")
3076
3283
  # Print the selected indices if any, otherwise print the current index
3077
3284
  if self.is_selecting or self.is_deselecting: self.handle_visual_selection()
3078
3285
  if len(self.items) == 0:
3286
+ self.cleanup_threads()
3079
3287
  function_data = self.get_function_data()
3080
3288
  restore_terminal_settings(tty_fd, self.saved_terminal_state)
3081
3289
  return [], "", function_data
@@ -3102,6 +3310,7 @@ class Picker:
3102
3310
  )
3103
3311
 
3104
3312
  if options_sufficient:
3313
+ self.cleanup_threads()
3105
3314
  self.user_opts = usrtxt
3106
3315
  self.stdscr.clear()
3107
3316
  self.stdscr.refresh()
@@ -3115,15 +3324,7 @@ class Picker:
3115
3324
  self.cursor_pos = max(0, self.cursor_pos-self.items_per_page)
3116
3325
 
3117
3326
  elif self.check_key("redraw_screen", key, self.keys_dict):
3118
- self.logger.info(f"key_function redraw_screen")
3119
- self.stdscr.clear()
3120
- self.stdscr.refresh()
3121
- restrict_curses(self.stdscr)
3122
- unrestrict_curses(self.stdscr)
3123
- self.stdscr.clear()
3124
- self.stdscr.refresh()
3125
-
3126
- self.draw_screen()
3327
+ self.refresh_and_draw_screen()
3127
3328
 
3128
3329
  elif self.check_key("cycle_sort_method", key, self.keys_dict):
3129
3330
  if self.sort_column == self.selected_column:
@@ -3164,22 +3365,28 @@ class Picker:
3164
3365
  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
3165
3366
  self.cursor_pos = [row[0] for row in self.indexed_items].index(current_index)
3166
3367
  elif self.check_key("col_select_next", key, self.keys_dict):
3167
- if len(self.items) > 0 and len(self.items[0]) > 0:
3168
- col_index = (self.selected_column +1) % (len(self.items[0]))
3169
- self.selected_column = col_index
3170
- # Flash when we loop back to the first column
3171
- # if self.selected_column == 0:
3172
- # curses.flash()
3173
3368
  self.logger.info(f"key_function col_select_next {self.selected_column}")
3369
+ if len(self.hidden_columns) != len(self.column_widths):
3370
+ if len(self.items) > 0 and len(self.items[0]) > 0:
3371
+ while True:
3372
+ self.hidden_columns
3373
+ col_index = (self.selected_column +1) % (len(self.items[0]))
3374
+ self.selected_column = col_index
3375
+ if self.selected_column not in self.hidden_columns:
3376
+ break
3377
+
3378
+ # Flash when we loop back to the first column
3379
+ # if self.selected_column == 0:
3380
+ # curses.flash()
3174
3381
 
3175
3382
 
3176
3383
  ## Scroll with column select
3177
3384
  self.get_visible_rows()
3178
3385
  self.column_widths = get_column_widths(self.visible_rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
3179
- visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
3180
- column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
3181
- start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
3182
- end_of_cell = sum(visible_column_widths[:self.selected_column+1])+len(self.separator)*(self.selected_column+1)
3386
+ self.visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
3387
+ column_set_width = sum(self.visible_column_widths)+len(self.separator)*len(self.visible_column_widths)
3388
+ start_of_cell = sum(self.visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
3389
+ end_of_cell = sum(self.visible_column_widths[:self.selected_column+1])+len(self.separator)*(self.selected_column+1)
3183
3390
  display_width = self.rows_w-self.left_gutter_width
3184
3391
  # If the full column is within the current display then don't do anything
3185
3392
  if start_of_cell >= self.leftmost_char and end_of_cell <= self.leftmost_char + display_width:
@@ -3192,11 +3399,17 @@ class Picker:
3192
3399
  self.ensure_no_overscroll()
3193
3400
 
3194
3401
  elif self.check_key("col_select_prev", key, self.keys_dict):
3195
- if len(self.items) > 0 and len(self.items[0]) > 0:
3196
- col_index = (self.selected_column -1) % (len(self.items[0]))
3197
- self.selected_column = col_index
3198
-
3199
3402
  self.logger.info(f"key_function col_select_prev {self.selected_column}")
3403
+
3404
+ if len(self.hidden_columns) != len(self.column_widths):
3405
+ if len(self.items) > 0 and len(self.items[0]) > 0:
3406
+ while True:
3407
+ self.hidden_columns
3408
+ col_index = (self.selected_column -1) % (len(self.items[0]))
3409
+ self.selected_column = col_index
3410
+ if self.selected_column not in self.hidden_columns:
3411
+ break
3412
+
3200
3413
  # Flash when we loop back to the last column
3201
3414
  # if self.selected_column == len(self.column_widths)-1:
3202
3415
  # curses.flash()
@@ -3204,10 +3417,10 @@ class Picker:
3204
3417
  ## Scroll with column select
3205
3418
  self.get_visible_rows()
3206
3419
  self.column_widths = get_column_widths(self.visible_rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
3207
- visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
3208
- column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
3209
- start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
3210
- end_of_cell = sum(visible_column_widths[:self.selected_column+1])+len(self.separator)*(self.selected_column+1)
3420
+ self.visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
3421
+ column_set_width = sum(self.visible_column_widths)+len(self.separator)*len(self.visible_column_widths)
3422
+ start_of_cell = sum(self.visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
3423
+ end_of_cell = sum(self.visible_column_widths[:self.selected_column+1])+len(self.separator)*(self.selected_column+1)
3211
3424
  display_width = self.rows_w-self.left_gutter_width
3212
3425
 
3213
3426
  # If the entire column is within the current display then don't do anything
@@ -3223,21 +3436,21 @@ class Picker:
3223
3436
  elif self.check_key("scroll_right", key, self.keys_dict):
3224
3437
  self.logger.info(f"key_function scroll_right")
3225
3438
  if len(self.indexed_items):
3226
- row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
3439
+ row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
3227
3440
  if row_width-self.leftmost_char >= self.rows_w-5:
3228
3441
  self.leftmost_char += 5
3229
3442
  self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w) + self.left_gutter_width+5)
3230
- if sum(self.column_widths) + len(self.column_widths)*len(self.separator) < self.rows_w:
3443
+ if sum(self.visible_column_widths) + len(self.visible_column_widths)*len(self.separator) < self.rows_w:
3231
3444
  self.leftmost_char = 0
3232
3445
 
3233
3446
  elif self.check_key("scroll_right_25", key, self.keys_dict):
3234
3447
  self.logger.info(f"key_function scroll_right")
3235
3448
  if len(self.indexed_items):
3236
- row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
3449
+ row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
3237
3450
  if row_width-self.leftmost_char+5 >= self.rows_w-25:
3238
3451
  self.leftmost_char += 25
3239
3452
  self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w) + self.left_gutter_width+5)
3240
- if sum(self.column_widths) + len(self.column_widths)*len(self.separator) < self.rows_w:
3453
+ if sum(self.visible_column_widths) + len(self.visible_column_widths)*len(self.separator) < self.rows_w:
3241
3454
  self.leftmost_char = 0
3242
3455
 
3243
3456
  elif self.check_key("scroll_left", key, self.keys_dict):
@@ -3256,24 +3469,13 @@ class Picker:
3256
3469
  elif self.check_key("scroll_far_right", key, self.keys_dict):
3257
3470
  self.logger.info(f"key_function scroll_far_right")
3258
3471
  longest_row_str_len = 0
3259
- # rows = self.get_visible_rows()
3260
- # for i in range(len(rows)):
3261
- # item = rows[i]
3262
- # row_str = format_row(item, self.hidden_columns, self.column_widths, self.separator, self.centre_in_cols, self.unicode_char_width)
3263
- # if len(row_str) > longest_row_str_len: longest_row_str_len=len(row_str)
3264
- longest_row_str_len = sum(self.column_widths) + (len(self.column_widths)-1)*len(self.separator)
3265
- # for i in range(len(self.indexed_items)):
3266
- # item = self.indexed_items[i]
3267
- # row_str = format_row(item[1], self.hidden_columns, self.column_widths, self.separator, self.centre_in_cols)
3268
- # if len(row_str) > longest_row_str_len: longest_row_str_len=len(row_str)
3269
- # self.notification(self.stdscr, f"{longest_row_str_len}")
3472
+ longest_row_str_len = sum(self.visible_column_widths) + (len(self.visible_column_widths)-1)*len(self.separator)
3270
3473
  if len(self.indexed_items):
3271
- row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
3474
+ row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
3272
3475
  self.leftmost_char = row_width - (self.rows_w) + self.left_gutter_width+5
3273
3476
  self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w) + self.left_gutter_width+5)
3274
3477
 
3275
- longest_row_str_len = sum(self.column_widths) + (len(self.column_widths)-1)*len(self.separator)
3276
- # self.leftmost_char = max(0, longest_row_str_len-self.rows_w+2+5)
3478
+ longest_row_str_len = sum(self.visible_column_widths) + (len(self.visible_column_widths)-1)*len(self.separator)
3277
3479
 
3278
3480
 
3279
3481
 
@@ -3335,16 +3537,6 @@ class Picker:
3335
3537
  self.selected_column = min(self.selected_column, row_len-2)
3336
3538
  self.initialise_variables()
3337
3539
 
3338
-
3339
-
3340
-
3341
- # elif self.check_key("increase_lines_per_page", key, self.keys_dict):
3342
- # self.items_per_page += 1
3343
- # self.draw_screen()
3344
- # elif self.check_key("decrease_lines_per_page", key, self.keys_dict):
3345
- # if self.items_per_page > 1:
3346
- # self.items_per_page -= 1
3347
- # self.draw_screen()
3348
3540
  elif self.check_key("decrease_column_width", key, self.keys_dict):
3349
3541
  self.logger.info(f"key_function decrease_column_width")
3350
3542
  if self.max_column_width > 10:
@@ -3526,12 +3718,6 @@ class Picker:
3526
3718
  function_data = self.get_function_data()
3527
3719
  return [], "escape", function_data
3528
3720
 
3529
-
3530
- # else:
3531
- # self.search_query = ""
3532
- # self.mode_index = 0
3533
- # self.highlights = [highlight for highlight in self.highlights if "type" not in highlight or highlight["type"] != "search" ]
3534
- # continue
3535
3721
  self.draw_screen()
3536
3722
 
3537
3723
  elif self.check_key("opts_input", key, self.keys_dict):
@@ -3747,7 +3933,7 @@ class Picker:
3747
3933
 
3748
3934
  elif self.check_key("edit", key, self.keys_dict):
3749
3935
  self.logger.info(f"key_function edit")
3750
- if len(self.indexed_items) > 0 and self.selected_column >=0 and self.editable_columns[self.selected_column]:
3936
+ if len(self.indexed_items) > 0 and self.editable_columns[self.selected_column]:
3751
3937
  current_val = self.indexed_items[self.cursor_pos][1][self.selected_column]
3752
3938
  usrtxt = f"{current_val}"
3753
3939
  field_end_f = lambda: self.get_term_size()[1]-38 if self.show_footer else lambda: self.get_term_size()[1]-3
@@ -3776,6 +3962,47 @@ class Picker:
3776
3962
  usrtxt = str(eval(usrtxt[3:]))
3777
3963
  self.indexed_items[self.cursor_pos][1][self.selected_column] = usrtxt
3778
3964
  self.history_edits.append(usrtxt)
3965
+ elif self.check_key("edit_nvim", key, self.keys_dict):
3966
+
3967
+ def edit_strings_in_nvim(strings: list[str]) -> list[str]:
3968
+ """
3969
+ Opens a list of strings in nvim for editing and returns the modified strings.
3970
+
3971
+ Args:
3972
+ strings (list[str]): The list of strings to edit.
3973
+
3974
+ Returns:
3975
+ list[str]: The updated list of strings after editing in nvim.
3976
+ """
3977
+
3978
+ # Open the strings in a tmpfile for editing
3979
+ with tempfile.NamedTemporaryFile(mode="w+", suffix=".txt", delete=False) as tmp:
3980
+ tmp.write("\n".join(strings))
3981
+ tmp.flush()
3982
+ tmp_name = tmp.name
3983
+
3984
+ subprocess.run(["nvim", tmp_name])
3985
+
3986
+ # Read the modified strings into a list and return them.
3987
+ with open(tmp_name, "r") as tmp:
3988
+ edited_content = tmp.read().splitlines()
3989
+
3990
+ return edited_content
3991
+
3992
+ if len(self.indexed_items) > 0 and self.editable_columns[self.selected_column]:
3993
+
3994
+ selected_cells = [self.items[index][self.selected_column] for index, selected in self.selections.items() if selected ]
3995
+ selected_cells_indices = [(index, self.selected_column) for index, selected in self.selections.items() if selected ]
3996
+
3997
+ edited_cells = edit_strings_in_nvim(selected_cells)
3998
+ count = 0
3999
+ if len(edited_cells) == len(selected_cells_indices):
4000
+ for i, j in selected_cells_indices:
4001
+ self.items[i][j] = edited_cells[count]
4002
+ count += 1
4003
+
4004
+ self.refresh_and_draw_screen()
4005
+
3779
4006
 
3780
4007
  elif self.check_key("edit_picker", key, self.keys_dict):
3781
4008
  self.logger.info(f"key_function edit_picker")
@@ -3807,7 +4034,7 @@ class Picker:
3807
4034
  self.indexed_items[self.cursor_pos][1][self.selected_column] = usrtxt
3808
4035
  self.history_edits.append(usrtxt)
3809
4036
  elif self.check_key("edit_ipython", key, self.keys_dict):
3810
- self.logger.info(f"key_function edit_picker")
4037
+ self.logger.info(f"key_function edit_ipython")
3811
4038
  import IPython, termios
3812
4039
  self.stdscr.clear()
3813
4040
  restrict_curses(self.stdscr)
@@ -3951,7 +4178,7 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3951
4178
  # input_arg = args.filename
3952
4179
 
3953
4180
  elif args.generate:
3954
- function_data["refresh_function"] = lambda items, header, visible_rows_indices, getting_data: generate_picker_data_from_file(args.generate, items, header, visible_rows_indices, getting_data)
4181
+ function_data["refresh_function"] = lambda items, header, visible_rows_indices, getting_data, state: generate_picker_data_from_file(args.generate, items, header, visible_rows_indices, getting_data, state)
3955
4182
  function_data["get_data_startup"] = True
3956
4183
  function_data["get_new_data"] = True
3957
4184
  return args, function_data
@@ -4211,7 +4438,7 @@ def main() -> None:
4211
4438
  # function_data["debug"] = True
4212
4439
  # function_data["debug_level"] = 1
4213
4440
 
4214
- function_data["cell_cursor"] = False
4441
+ # function_data["cell_cursor"] = False
4215
4442
 
4216
4443
  function_data["split_right"] = False
4217
4444
  function_data["split_left"] = False