listpick 0.1.16.10__py3-none-any.whl → 0.1.16.12__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 +686 -445
- listpick/ui/build_help.py +8 -7
- listpick/ui/keys.py +3 -2
- listpick/ui/picker_colours.py +55 -0
- listpick/utils/generate_data_multiprocessing.py +10 -0
- listpick/utils/generate_data_multithreaded.py +36 -12
- listpick/utils/generate_data_utils.py +43 -0
- listpick/utils/utils.py +9 -11
- {listpick-0.1.16.10.dist-info → listpick-0.1.16.12.dist-info}/METADATA +24 -36
- {listpick-0.1.16.10.dist-info → listpick-0.1.16.12.dist-info}/RECORD +14 -12
- {listpick-0.1.16.10.dist-info → listpick-0.1.16.12.dist-info}/WHEEL +0 -0
- {listpick-0.1.16.10.dist-info → listpick-0.1.16.12.dist-info}/entry_points.txt +0 -0
- {listpick-0.1.16.10.dist-info → listpick-0.1.16.12.dist-info}/licenses/LICENSE.txt +0 -0
- {listpick-0.1.16.10.dist-info → listpick-0.1.16.12.dist-info}/top_level.txt +0 -0
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,
|
|
74
|
-
refresh_function: Optional[Callable] = lambda items, header, visible_rows_indices, getting_data: None,
|
|
75
|
-
get_data_startup: bool =False,
|
|
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,8 +380,18 @@ 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 = []
|
|
364
387
|
|
|
365
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
|
|
394
|
+
|
|
366
395
|
|
|
367
396
|
self.initialise_picker_state(reset_colours=self.reset_colours)
|
|
368
397
|
|
|
@@ -378,6 +407,11 @@ class Picker:
|
|
|
378
407
|
self.getting_data.set()
|
|
379
408
|
|
|
380
409
|
def __sizeof__(self):
|
|
410
|
+
"""
|
|
411
|
+
Return the approximate memory footprint of the Picker instance.
|
|
412
|
+
|
|
413
|
+
This includes the size of the instance itself and the sizes of its attributes.
|
|
414
|
+
"""
|
|
381
415
|
|
|
382
416
|
size = super().__sizeof__()
|
|
383
417
|
|
|
@@ -388,7 +422,19 @@ class Picker:
|
|
|
388
422
|
return size
|
|
389
423
|
|
|
390
424
|
def set_config(self, path: str ="~/.config/listpick/config.toml") -> bool:
|
|
391
|
-
""" Set config from toml file.
|
|
425
|
+
""" Set config from toml file.
|
|
426
|
+
|
|
427
|
+
This method reads a configuration file in TOML format, applies settings
|
|
428
|
+
to the Picker, and returns a boolean indicating success or failure.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
path (str): The path to the configuration file.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
bool: True if the configuration was successfully set; False otherwise.
|
|
435
|
+
"""
|
|
436
|
+
self.logger.info(f"function: set_config()")
|
|
437
|
+
|
|
392
438
|
path = os.path.expanduser(os.path.expandvars(path))
|
|
393
439
|
if not os.path.exists(path):
|
|
394
440
|
return False
|
|
@@ -397,7 +443,9 @@ class Picker:
|
|
|
397
443
|
except Exception as e:
|
|
398
444
|
self.logger.error(f"get_config({path}) load error. {e}")
|
|
399
445
|
return False
|
|
446
|
+
|
|
400
447
|
|
|
448
|
+
# Change the global theme if colour_theme_number is in the loaded config
|
|
401
449
|
if "general" in config:
|
|
402
450
|
if "colour_theme_number" in config["general"] and config["general"]["colour_theme_number"] != self.colour_theme_number:
|
|
403
451
|
global COLOURS_SET
|
|
@@ -405,7 +453,7 @@ class Picker:
|
|
|
405
453
|
self.colours_end = set_colours(pick=config["general"]["colour_theme_number"], start=1)
|
|
406
454
|
self.colours = get_colours(config["general"]["colour_theme_number"])
|
|
407
455
|
|
|
408
|
-
|
|
456
|
+
# load the rest of the config options
|
|
409
457
|
if "general" in config:
|
|
410
458
|
for key, val in config["general"].items():
|
|
411
459
|
self.logger.info(f"set_config: key={key}, val={val}.")
|
|
@@ -417,30 +465,58 @@ class Picker:
|
|
|
417
465
|
return True
|
|
418
466
|
|
|
419
467
|
def get_config(self, path: str ="~/.config/listpick/config.toml") -> dict:
|
|
420
|
-
"""
|
|
468
|
+
"""
|
|
469
|
+
Retrieve configuration settings from a specified TOML file.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
path (str): The file path of the configuration file. Default is
|
|
473
|
+
~/.config/listpick/config.toml.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
dict: A dictionary containing the configuration settings loaded
|
|
477
|
+
from the TOML file. In case of an error, an empty dictionary is returned.
|
|
478
|
+
"""
|
|
479
|
+
|
|
421
480
|
self.logger.info(f"function: get_config()")
|
|
422
481
|
import toml
|
|
423
482
|
with open(os.path.expanduser(path), "r") as f:
|
|
424
483
|
config = toml.load(f)
|
|
425
484
|
return config
|
|
426
485
|
|
|
427
|
-
def update_term_size(self):
|
|
486
|
+
def update_term_size(self) -> None:
|
|
487
|
+
"""
|
|
488
|
+
Update self.term_h, self.term_w the function provided to the Picker.
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
None
|
|
492
|
+
"""
|
|
428
493
|
self.term_h, self.term_w = self.screen_size_function(self.stdscr)
|
|
429
494
|
# self.term_h, self.term_w = self.stdscr.getmaxyx()
|
|
430
495
|
# self.term_w, self.term_h = os.get_terminal_size()
|
|
431
496
|
|
|
432
497
|
|
|
433
|
-
def get_term_size(self):
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
498
|
+
def get_term_size(self) -> Tuple[int, int]:
|
|
499
|
+
"""
|
|
500
|
+
Get the current terminal size using the function provided to the Picker.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
Tuple[int, int]: A tuple containing the (height, width) of the terminal.
|
|
504
|
+
"""
|
|
505
|
+
return self.screen_size_function(self.stdscr)
|
|
506
|
+
# return self.stdscr.getmaxyx()
|
|
507
|
+
# w, h = os.get_terminal_size()
|
|
508
|
+
# return h, w
|
|
437
509
|
|
|
438
|
-
def calculate_section_sizes(self):
|
|
510
|
+
def calculate_section_sizes(self) -> None:
|
|
439
511
|
"""
|
|
440
512
|
Calculte the following for the Picker:
|
|
441
513
|
self.items_per_page: the number of entry rows displayed
|
|
442
514
|
self.bottom_space: the size of the footer + the bottom buffer space
|
|
443
515
|
self.top_space: the size of the space at the top of the picker: title + modes + header + top_gap
|
|
516
|
+
Calculate and update the sizes of various sections of the Picker.
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
None
|
|
444
520
|
"""
|
|
445
521
|
|
|
446
522
|
self.logger.debug(f"function: calculate_section_sizes()")
|
|
@@ -494,8 +570,8 @@ class Picker:
|
|
|
494
570
|
self.top_space += ((self.term_h-(self.top_space+self.bottom_space))-len(self.indexed_items))//2
|
|
495
571
|
|
|
496
572
|
# self.column_widths
|
|
497
|
-
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
498
|
-
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)
|
|
499
575
|
|
|
500
576
|
# self.startx
|
|
501
577
|
self.startx = 1 if self.highlight_full_row else 2
|
|
@@ -512,16 +588,27 @@ class Picker:
|
|
|
512
588
|
|
|
513
589
|
|
|
514
590
|
def get_visible_rows(self) -> list[list[str]]:
|
|
591
|
+
"""
|
|
592
|
+
Calculate and return the currently visible rows based on the cursor position and pagination settings.
|
|
515
593
|
|
|
594
|
+
This method determines which rows from the indexed items are visible on the screen,
|
|
595
|
+
accounting for pagination and scrolling. It sets the starting and ending indices
|
|
596
|
+
based on the current cursor position and the number of items per page.
|
|
597
|
+
|
|
598
|
+
Returns:
|
|
599
|
+
list[list[str]]: The currently visible rows as a list of lists, where each inner
|
|
600
|
+
list represents a row of data. If there are no indexed items, it returns the
|
|
601
|
+
items array.
|
|
602
|
+
"""
|
|
516
603
|
self.logger.debug(f"function: get_visible_rows()")
|
|
517
604
|
## Scroll with column select
|
|
518
605
|
if self.paginate:
|
|
519
|
-
start_index = (self.cursor_pos//self.items_per_page) * self.items_per_page
|
|
606
|
+
start_index = (self.cursor_pos // self.items_per_page) * self.items_per_page
|
|
520
607
|
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
|
521
608
|
## Scroll
|
|
522
609
|
else:
|
|
523
|
-
scrolloff = self.items_per_page//2
|
|
524
|
-
start_index = max(0, min(self.cursor_pos - (self.items_per_page-scrolloff), len(self.indexed_items)-self.items_per_page))
|
|
610
|
+
scrolloff = self.items_per_page // 2
|
|
611
|
+
start_index = max(0, min(self.cursor_pos - (self.items_per_page - scrolloff), len(self.indexed_items) - self.items_per_page))
|
|
525
612
|
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
|
526
613
|
if len(self.indexed_items) == 0: start_index, end_index = 0, 0
|
|
527
614
|
|
|
@@ -534,13 +621,15 @@ class Picker:
|
|
|
534
621
|
def initialise_picker_state(self, reset_colours=False) -> None:
|
|
535
622
|
""" Initialise state variables for the picker. These are: debugging and colours. """
|
|
536
623
|
|
|
624
|
+
# Define global curses colours
|
|
537
625
|
if curses.has_colors() and self.colours != None:
|
|
538
|
-
# raise Exception("Terminal does not support color")
|
|
539
626
|
curses.start_color()
|
|
627
|
+
|
|
540
628
|
if reset_colours:
|
|
541
629
|
global COLOURS_SET
|
|
542
630
|
COLOURS_SET = False
|
|
543
631
|
self.colours_end = set_colours(pick=self.colour_theme_number, start=self.colours_start)
|
|
632
|
+
|
|
544
633
|
if curses.COLORS >= 255 and curses.COLOR_PAIRS >= 150:
|
|
545
634
|
self.colours_start = self.colours_start
|
|
546
635
|
self.notification_colours_start = self.colours_start+50
|
|
@@ -557,64 +646,40 @@ class Picker:
|
|
|
557
646
|
self.colours = get_colours(self.colour_theme_number)
|
|
558
647
|
|
|
559
648
|
|
|
649
|
+
# Start logger
|
|
560
650
|
debug_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]
|
|
561
651
|
dbglvl = debug_levels[self.debug_level]
|
|
562
652
|
self.logger = setup_logger(name="picker_log", log_file="picker.log", log_enabled=self.debug, level =dbglvl)
|
|
563
653
|
self.logger.info(f"Initialiasing Picker.")
|
|
654
|
+
|
|
564
655
|
self.update_term_size()
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
# 1 2 3 4 5
|
|
569
|
-
# logger = logging.getLogger(__file__)
|
|
570
|
-
# if self.debug_level == 0:
|
|
571
|
-
# logger = logging.getLogger()
|
|
572
|
-
# logger.disabled = True
|
|
573
|
-
# else:
|
|
574
|
-
#
|
|
575
|
-
# file_handler = logging.FileHandler(f"{self.title}.log", mode='w')
|
|
576
|
-
# formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%m-%d-%Y %H:%M:%S')
|
|
577
|
-
# file_handler.setFormatter(formatter)
|
|
578
|
-
# logger.addHandler(file_handler)
|
|
579
|
-
# logger.setLevel(debug_levels[self.debug_level-1])
|
|
580
|
-
|
|
581
|
-
# logging.basicConfig(
|
|
582
|
-
# level=debug_levels[self.debug_level-1],
|
|
583
|
-
# format='%(asctime)s - %(levelname)s - %(message)s',
|
|
584
|
-
# datefmt='%m-%d-%Y %H:%M:%S',
|
|
585
|
-
# filename=f"{self.title}.log",
|
|
586
|
-
# filemode="w",
|
|
587
|
-
# )
|
|
588
|
-
#
|
|
589
|
-
# self.logger.info(f"Starging log. Log level {logger.getEffectiveLevel()}")
|
|
590
|
-
# self.logger.info(f"Starging log. Log level {repr(debug_levels)}, {self.debug_level}, {debug_levels[self.debug_level-1]}")
|
|
591
|
-
# self.notification(self.stdscr, f"Starging log. Log level {repr(debug_levels)}, {self.debug_level}, {debug_levels[self.debug_level-1]}")
|
|
592
|
-
# self.notification(self.stdscr, f"{__file__}")
|
|
593
|
-
|
|
594
|
-
## Logging level plan
|
|
595
|
-
# DEBUG: loop functions, draw screen, etc.
|
|
596
|
-
# INFO: main functions
|
|
597
|
-
# WARNING: any try-except fails
|
|
598
|
-
|
|
599
|
-
# No set_escdelay function on windows.
|
|
656
|
+
|
|
657
|
+
# The curses implementation for some systems (e.g., windows) does not allow set_escdelay
|
|
600
658
|
try:
|
|
601
659
|
curses.set_escdelay(25)
|
|
602
660
|
except:
|
|
603
661
|
logging.warning("Error trying to set curses.set_escdelay")
|
|
604
662
|
|
|
605
|
-
# self.stdscr.clear()
|
|
606
|
-
# self.stdscr.refresh()
|
|
607
|
-
# self.draw_screen()
|
|
608
|
-
|
|
609
663
|
def initialise_variables(self, get_data: bool = False) -> None:
|
|
610
|
-
"""
|
|
664
|
+
"""
|
|
665
|
+
This method sets up the internal state of the Picker by initialising various attributes,
|
|
666
|
+
getting new data (if get_data is True), and ensuring that the lists used for tracking
|
|
667
|
+
selections, options, and items are correctly of the correct type, size, and shape. If
|
|
668
|
+
filter or sort queries are set then they are applied (or re-applied as the case may be).
|
|
669
|
+
The cursor_pos and selections are retained by tracking the id of the rows (where the id
|
|
670
|
+
is row[self.id_column]).
|
|
671
|
+
|
|
672
|
+
Parameters:
|
|
673
|
+
- get_data (bool): If True, pulls data synchronously and updates tracking variables.
|
|
674
|
+
"""
|
|
611
675
|
|
|
612
676
|
self.logger.info(f"function: initialise_variables()")
|
|
613
677
|
|
|
614
678
|
tracking = False
|
|
615
679
|
|
|
616
680
|
## Get data synchronously
|
|
617
|
-
if get_data
|
|
681
|
+
if get_data:
|
|
682
|
+
# Track cursor_pos and selections by ther id (row[self.id_column][col])
|
|
618
683
|
if self.track_entries_upon_refresh and len(self.items) > 0:
|
|
619
684
|
tracking = True
|
|
620
685
|
selected_indices = get_selected_indices(self.selections)
|
|
@@ -622,34 +687,41 @@ class Picker:
|
|
|
622
687
|
self.ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
|
|
623
688
|
self.ids_tuples = [(i, item[self.id_column]) for i, item in enumerate(self.items) if i in selected_indices]
|
|
624
689
|
|
|
625
|
-
if len(self.indexed_items) > 0 and len(self.indexed_items)
|
|
690
|
+
if len(self.indexed_items) > 0 and self.cursor_pos < len(self.indexed_items) and len(self.indexed_items[0][1]) >= self.id_column:
|
|
626
691
|
self.cursor_pos_id = self.indexed_items[self.cursor_pos][1][self.id_column]
|
|
627
692
|
self.cursor_pos_prev = self.cursor_pos
|
|
628
|
-
|
|
629
|
-
|
|
630
693
|
|
|
694
|
+
# Set the state of the threading event
|
|
695
|
+
# Though we are getting data synchronously, we ensure the correct state for self.getting_data
|
|
631
696
|
self.getting_data.clear()
|
|
632
|
-
self.refresh_function(
|
|
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
|
+
)
|
|
633
704
|
|
|
634
|
-
self.items = pad_lists_to_same_length(self.items)
|
|
635
705
|
|
|
706
|
+
# Ensure that an emtpy items object has the form [[]]
|
|
636
707
|
if self.items == []: self.items = [[]]
|
|
637
|
-
|
|
708
|
+
|
|
709
|
+
# Ensure that items is a List[List[Str]] object
|
|
638
710
|
if len(self.items) > 0 and not isinstance(self.items[0], list):
|
|
639
711
|
self.items = [[item] for item in self.items]
|
|
640
712
|
# self.items = [[str(cell) for cell in row] for row in self.items]
|
|
641
713
|
|
|
714
|
+
# Ensure that the each of the rows of the items are of the same length
|
|
715
|
+
self.items = pad_lists_to_same_length(self.items)
|
|
642
716
|
|
|
643
717
|
# Ensure that header is of the same length as the rows
|
|
644
718
|
if self.header and len(self.items) > 0 and len(self.header) != len(self.items[0]):
|
|
645
719
|
self.header = [str(self.header[i]) if i < len(self.header) else "" for i in range(len(self.items[0]))]
|
|
646
720
|
|
|
647
|
-
# Constants
|
|
648
|
-
# DEFAULT_ITEMS_PER_PAGE = os.get_terminal_size().lines - top_gap*2-2-int(bool(header))
|
|
649
|
-
|
|
650
721
|
self.calculate_section_sizes()
|
|
651
722
|
|
|
652
|
-
|
|
723
|
+
|
|
724
|
+
# Ensure that the selection-tracking variables are the correct shape
|
|
653
725
|
if len(self.selections) != len(self.items):
|
|
654
726
|
self.selections = {i : False if i not in self.selections else bool(self.selections[i]) for i in range(len(self.items))}
|
|
655
727
|
|
|
@@ -660,31 +732,34 @@ class Picker:
|
|
|
660
732
|
self.cell_selections = {}
|
|
661
733
|
self.selected_cells_by_row = {}
|
|
662
734
|
|
|
735
|
+
def extend_list_to_length(lst, length, default_value):
|
|
736
|
+
"""Extend a list to the target length using a default value."""
|
|
737
|
+
if len(lst) < length:
|
|
738
|
+
lst.extend([copy.deepcopy(default_value) for _ in range(length - len(lst))])
|
|
663
739
|
|
|
664
740
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
if
|
|
670
|
-
self.
|
|
671
|
-
|
|
672
|
-
self.
|
|
673
|
-
|
|
674
|
-
self.editable_columns = self.
|
|
675
|
-
|
|
741
|
+
row_count = len(self.items)
|
|
742
|
+
col_count = len(self.items[0]) if row_count else 0
|
|
743
|
+
|
|
744
|
+
# Ensure that the length of the option lists are of the correct length.
|
|
745
|
+
if row_count > 0:
|
|
746
|
+
extend_list_to_length(self.require_option, length=row_count, default_value=self.require_option_default)
|
|
747
|
+
extend_list_to_length(self.option_functions, length=row_count, default_value=self.default_option_function)
|
|
748
|
+
extend_list_to_length(self.columns_sort_method, length=col_count, default_value=0)
|
|
749
|
+
extend_list_to_length(self.sort_reverse, length=col_count, default_value=False)
|
|
750
|
+
extend_list_to_length(self.editable_columns, length=col_count, default_value=self.editable_by_default)
|
|
751
|
+
|
|
752
|
+
if row_count > 0 and len(self.column_indices) < len(self.items[0]):
|
|
676
753
|
self.column_indices = self.column_indices + [i for i in range(len(self.column_indices), len(self.items[0]))]
|
|
677
754
|
|
|
678
755
|
|
|
679
756
|
|
|
680
|
-
#
|
|
681
|
-
# self.indexed_items = list(enumerate(items2))
|
|
757
|
+
# Create an indexed list of the items which will track the visible rows
|
|
682
758
|
if self.items == [[]]: self.indexed_items = []
|
|
683
759
|
else: self.indexed_items = list(enumerate(self.items))
|
|
684
760
|
|
|
685
|
-
#
|
|
761
|
+
# Apply the filter query
|
|
686
762
|
if self.filter_query:
|
|
687
|
-
# prev_index = self.indexed_items[cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
688
763
|
# prev_index = self.indexed_items[cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
689
764
|
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
|
690
765
|
if self.cursor_pos in [x[0] for x in self.indexed_items]: self.cursor_pos = [x[0] for x in self.indexed_items].index(self.cursor_pos)
|
|
@@ -700,22 +775,30 @@ class Picker:
|
|
|
700
775
|
)
|
|
701
776
|
if return_val:
|
|
702
777
|
self.cursor_pos, self.search_index, self.search_count, self.highlights = tmp_cursor, tmp_index, tmp_count, tmp_highlights
|
|
703
|
-
|
|
778
|
+
|
|
779
|
+
# Apply the current sort method
|
|
704
780
|
if len(self.indexed_items) > 0:
|
|
705
781
|
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
|
|
706
782
|
|
|
707
783
|
|
|
708
|
-
#
|
|
709
|
-
## Move to a selectable row (if applicable)
|
|
784
|
+
# If we have more unselectable indices than rows, clear the unselectable_indices
|
|
710
785
|
if len(self.items) <= len(self.unselectable_indices): self.unselectable_indices = []
|
|
711
|
-
new_pos = (self.cursor_pos)%len(self.items)
|
|
712
|
-
while new_pos in self.unselectable_indices and new_pos != self.cursor_pos:
|
|
713
|
-
new_pos = (new_pos + 1) % len(self.items)
|
|
714
786
|
|
|
715
|
-
|
|
716
|
-
self.cursor_pos
|
|
787
|
+
# Move cursur to a selectable row if we are currently on an unselectable row)
|
|
788
|
+
if self.cursor_pos * len(self.items) in self.unselectable_indices:
|
|
789
|
+
original_pos = new_pos = (self.cursor_pos)%len(self.items)
|
|
790
|
+
while new_pos in self.unselectable_indices:
|
|
791
|
+
new_pos = (new_pos + 1) % len(self.items)
|
|
792
|
+
|
|
793
|
+
# Break if we loop back to the original position
|
|
794
|
+
if new_pos == original_pos:
|
|
795
|
+
break
|
|
796
|
+
|
|
797
|
+
self.cursor_pos = max(0, min(new_pos, len(self.items)-1))
|
|
798
|
+
|
|
799
|
+
# Initialise sheets
|
|
800
|
+
extend_list_to_length(self.sheet_states, length=len(self.sheets), default_value={})
|
|
717
801
|
|
|
718
|
-
# Sheets and files
|
|
719
802
|
if len(self.sheet_states) < len(self.sheets):
|
|
720
803
|
self.sheet_states += [{} for _ in range(len(self.sheets) - len(self.sheet_states))]
|
|
721
804
|
if len(self.sheets):
|
|
@@ -723,15 +806,16 @@ class Picker:
|
|
|
723
806
|
self.sheet_index = 0
|
|
724
807
|
self.sheet_name = self.sheets[self.sheet_index]
|
|
725
808
|
|
|
726
|
-
|
|
727
|
-
|
|
809
|
+
# Initialise files
|
|
810
|
+
extend_list_to_length(self.loaded_file_states, length=len(self.loaded_files), default_value={})
|
|
728
811
|
if len(self.loaded_files):
|
|
729
812
|
if self.loaded_file_index >= len(self.loaded_files):
|
|
730
813
|
self.loaded_file_index = 0
|
|
731
814
|
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
732
815
|
|
|
733
|
-
|
|
734
|
-
# Ensure that selected indices are
|
|
816
|
+
|
|
817
|
+
# Ensure that the correct cursor_pos and selected indices are reselected
|
|
818
|
+
# if we have fetched new data.
|
|
735
819
|
if self.track_entries_upon_refresh and (self.data_ready or tracking) and len(self.items) > 1:
|
|
736
820
|
selected_indices = []
|
|
737
821
|
all_ids = [item[self.id_column] for item in self.items]
|
|
@@ -754,6 +838,8 @@ class Picker:
|
|
|
754
838
|
|
|
755
839
|
|
|
756
840
|
|
|
841
|
+
# Ensure cursor_pos is set to a valid index
|
|
842
|
+
# If we have fetched new data then we attempt to set cursor_pos to the row with the same id as prev
|
|
757
843
|
if len(self.indexed_items):
|
|
758
844
|
if self.pin_cursor:
|
|
759
845
|
self.cursor_pos = min(self.cursor_pos_prev, len(self.indexed_items)-1)
|
|
@@ -765,7 +851,12 @@ class Picker:
|
|
|
765
851
|
else:
|
|
766
852
|
self.cursor_pos = 0
|
|
767
853
|
|
|
768
|
-
|
|
854
|
+
|
|
855
|
+
# Ensure that the pane indices are within the range of the available panes.
|
|
856
|
+
if len(self.left_panes): self.left_pane_index %= len(self.left_panes)
|
|
857
|
+
else: self.left_pane_index = 0
|
|
858
|
+
if len(self.right_panes): self.right_pane_index %= len(self.right_panes)
|
|
859
|
+
else: self.right_pane_index = 0
|
|
769
860
|
|
|
770
861
|
|
|
771
862
|
|
|
@@ -862,14 +953,11 @@ class Picker:
|
|
|
862
953
|
self.stdscr.erase()
|
|
863
954
|
|
|
864
955
|
self.update_term_size()
|
|
865
|
-
# if self.split_right and len(self.right_panes):
|
|
866
|
-
# proportion = self.right_panes[self.right_pane_index]["proportion"]
|
|
867
|
-
# self.rows_w, self.rows_h = int(self.term_w*proportion), self.term_h
|
|
868
|
-
# else:
|
|
869
|
-
# self.rows_w, self.rows_h = self.term_w, self.term_h
|
|
870
956
|
|
|
871
|
-
#
|
|
957
|
+
# Determine footer size
|
|
872
958
|
self.footer.adjust_sizes(self.term_h,self.term_w)
|
|
959
|
+
|
|
960
|
+
# The height of the footer may need to be adjusted if the file changes.
|
|
873
961
|
self.calculate_section_sizes()
|
|
874
962
|
|
|
875
963
|
# Test if the terminal is of a sufficient size to display the picker
|
|
@@ -887,28 +975,18 @@ class Picker:
|
|
|
887
975
|
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
|
888
976
|
if len(self.indexed_items) == 0: start_index, end_index = 0, 0
|
|
889
977
|
|
|
890
|
-
# self.column_widths = get_column_widths(self.items, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=w)
|
|
891
|
-
# Determine widths based only on the currently indexed rows
|
|
892
|
-
# rows = [v[1] for v in self.indexed_items] if len(self.indexed_items) else self.items
|
|
893
|
-
# Determine widths based only on the currently displayed indexed rows
|
|
894
|
-
# rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
|
|
895
978
|
self.get_visible_rows()
|
|
896
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)
|
|
897
|
-
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
898
|
-
visible_columns_total_width = sum(visible_column_widths) + len(self.separator)*(len(visible_column_widths)-1)
|
|
899
|
-
|
|
900
|
-
# Determine the number of items_per_page, top_size and bottom_size
|
|
901
|
-
# self.calculate_section_sizes()
|
|
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)
|
|
902
982
|
|
|
903
|
-
# top_space = self.top_gap
|
|
904
983
|
|
|
905
|
-
## Display title
|
|
984
|
+
## Display title
|
|
906
985
|
if self.title:
|
|
907
986
|
padded_title = f" {self.title.strip()} "
|
|
908
987
|
self.stdscr.addstr(self.top_gap, 0, f"{' ':^{self.term_w}}", curses.color_pair(self.colours_start+16))
|
|
909
988
|
title_x = (self.term_w-wcswidth(padded_title))//2
|
|
910
989
|
self.stdscr.addstr(self.top_gap, title_x, padded_title, curses.color_pair(self.colours_start+16) | curses.A_BOLD)
|
|
911
|
-
# top_space += 1
|
|
912
990
|
|
|
913
991
|
## Display modes
|
|
914
992
|
if self.display_modes and self.modes not in [[{}], []]:
|
|
@@ -930,7 +1008,6 @@ class Picker:
|
|
|
930
1008
|
else:
|
|
931
1009
|
self.stdscr.addstr(self.top_gap+1, xmode, mode_str, curses.color_pair(self.colours_start+15) | curses.A_UNDERLINE)
|
|
932
1010
|
xmode += split_space+mode_widths[i]
|
|
933
|
-
# top_space += 1
|
|
934
1011
|
|
|
935
1012
|
## Display header
|
|
936
1013
|
if self.header and self.show_header:
|
|
@@ -948,93 +1025,75 @@ class Picker:
|
|
|
948
1025
|
|
|
949
1026
|
|
|
950
1027
|
header_str += f"{col_str:^{self.column_widths[i]-len(number)}}"
|
|
951
|
-
|
|
1028
|
+
if i == self.selected_column-1:
|
|
1029
|
+
header_str += self.header_separator_before_selected_column
|
|
1030
|
+
else:
|
|
1031
|
+
header_str += self.header_separator
|
|
952
1032
|
header_str_w = min(self.rows_w-self.left_gutter_width, visible_columns_total_width+1, self.term_w-self.startx)
|
|
953
1033
|
|
|
954
1034
|
header_str = header_str[self.leftmost_char:]
|
|
955
1035
|
header_str = header_str[:header_str_w]
|
|
956
1036
|
header_ypos = self.top_gap + bool(self.title) + bool(self.display_modes and self.modes)
|
|
1037
|
+
|
|
1038
|
+
# Ensure that the full header width is filled--important if the header rows do not fill the terminal width
|
|
957
1039
|
self.stdscr.addstr(header_ypos, self.rows_box_x_i, ' '*self.rows_w, curses.color_pair(self.colours_start+28) | curses.A_BOLD)
|
|
1040
|
+
|
|
1041
|
+
# Draw header string
|
|
958
1042
|
self.stdscr.addstr(header_ypos, self.startx, header_str, curses.color_pair(self.colours_start+4) | curses.A_BOLD)
|
|
959
1043
|
|
|
960
1044
|
# Highlight sort column
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
x_pos = len(up_to_selected_col) - self.leftmost_char + self.startx
|
|
1045
|
+
if self.selected_column != None and self.selected_column not in self.hidden_columns:
|
|
1046
|
+
# start of string is on screen
|
|
1047
|
+
col_width = self.column_widths[self.selected_column]
|
|
1048
|
+
number = f"{self.selected_column}. " if self.number_columns else ""
|
|
1049
|
+
col_str = self.header[self.selected_column][:self.column_widths[self.selected_column]-len(number)]
|
|
1050
|
+
highlighted_col_str = (number+f"{col_str:^{self.column_widths[self.selected_column]-len(number)}}") + self.separator
|
|
1051
|
+
|
|
1052
|
+
if len(self.column_widths) == 1:
|
|
1053
|
+
colour = curses.color_pair(self.colours_start+28) | curses.A_BOLD
|
|
1054
|
+
else:
|
|
1055
|
+
colour = curses.color_pair(self.colours_start+19) | curses.A_BOLD
|
|
1056
|
+
# Start of selected column is on the screen
|
|
1057
|
+
if self.leftmost_char <= len(up_to_selected_col) and self.leftmost_char+self.rows_w-self.left_gutter_width > len(up_to_selected_col):
|
|
1058
|
+
x_pos = len(up_to_selected_col) - self.leftmost_char + self.startx
|
|
976
1059
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1060
|
+
# Whole cell of the selected column is on the screen
|
|
1061
|
+
if len(up_to_selected_col)+col_width - self.leftmost_char < self.rows_w-self.left_gutter_width:
|
|
1062
|
+
disp_str = highlighted_col_str
|
|
980
1063
|
|
|
981
|
-
|
|
982
|
-
else:
|
|
983
|
-
overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w - self.left_gutter_width)
|
|
984
|
-
disp_str = highlighted_col_str[:-overflow]
|
|
985
|
-
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
986
|
-
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
987
|
-
|
|
988
|
-
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
989
|
-
# Start of the cell is to the right of the screen
|
|
990
|
-
elif self.leftmost_char+self.rows_w <= len(up_to_selected_col):
|
|
991
|
-
pass
|
|
992
|
-
# The end of the cell is on the screen, the start of the cell is not
|
|
993
|
-
elif 0 <= len(up_to_selected_col)+col_width - self.leftmost_char <= self.rows_w :
|
|
994
|
-
x_pos = self.startx
|
|
995
|
-
beg = self.leftmost_char - len(up_to_selected_col)
|
|
996
|
-
disp_str = highlighted_col_str[beg:]
|
|
997
|
-
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
998
|
-
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
999
|
-
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
1000
|
-
# The middle of the cell is on the screen, the start and end of the cell are not
|
|
1001
|
-
elif self.leftmost_char <= len(up_to_selected_col) + col_width//2 <= self.leftmost_char+self.rows_w:
|
|
1002
|
-
beg = self.leftmost_char - len(up_to_selected_col)
|
|
1003
|
-
overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w)
|
|
1004
|
-
x_pos = self.startx
|
|
1005
|
-
disp_str = highlighted_col_str[beg:-overflow]
|
|
1006
|
-
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
1007
|
-
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
1008
|
-
|
|
1009
|
-
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
1010
|
-
# The cell is to the left of the screen
|
|
1064
|
+
# Start of the cell is on the screen, but the end of the cell is not
|
|
1011
1065
|
else:
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1066
|
+
overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w - self.left_gutter_width)
|
|
1067
|
+
disp_str = highlighted_col_str[:-overflow]
|
|
1068
|
+
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
1069
|
+
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
1070
|
+
|
|
1071
|
+
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
1072
|
+
# Start of the cell is to the right of the screen
|
|
1073
|
+
elif self.leftmost_char+self.rows_w <= len(up_to_selected_col):
|
|
1074
|
+
pass
|
|
1075
|
+
# The end of the cell is on the screen, the start of the cell is not
|
|
1076
|
+
elif 0 <= len(up_to_selected_col)+col_width - self.leftmost_char <= self.rows_w :
|
|
1077
|
+
x_pos = self.startx
|
|
1078
|
+
beg = self.leftmost_char - len(up_to_selected_col)
|
|
1079
|
+
disp_str = highlighted_col_str[beg:]
|
|
1080
|
+
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
1081
|
+
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
1082
|
+
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
1083
|
+
# The middle of the cell is on the screen, the start and end of the cell are not
|
|
1084
|
+
elif self.leftmost_char <= len(up_to_selected_col) + col_width//2 <= self.leftmost_char+self.rows_w:
|
|
1085
|
+
beg = self.leftmost_char - len(up_to_selected_col)
|
|
1086
|
+
overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w)
|
|
1087
|
+
x_pos = self.startx
|
|
1088
|
+
disp_str = highlighted_col_str[beg:-overflow]
|
|
1089
|
+
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
1090
|
+
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
1091
|
+
|
|
1092
|
+
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
1093
|
+
|
|
1094
|
+
# The cell is to the left of the focused part of the screen
|
|
1095
|
+
else:
|
|
1096
|
+
pass
|
|
1038
1097
|
|
|
1039
1098
|
# Display row header
|
|
1040
1099
|
if self.show_row_header:
|
|
@@ -1058,41 +1117,34 @@ class Picker:
|
|
|
1058
1117
|
colour = curses.color_pair(self.colours_start+colour_pair_number) | curses.A_BOLD
|
|
1059
1118
|
else:
|
|
1060
1119
|
colour = curses.color_pair(self.colours_start+colour_pair_number)
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
cell_value = f"{self.indexed_items[row][1][col]:^{cell_width-len(self.separator)}}" + self.separator
|
|
1068
|
-
else:
|
|
1069
|
-
cell_value = self.indexed_items[row][1][col][:self.column_widths[col]] + self.separator
|
|
1070
|
-
# cell_value = cell_value[:min(cell_width, cell_max_width)-len(self.separator)]
|
|
1071
|
-
cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1072
|
-
# cell_value = cell_value + self.separator
|
|
1073
|
-
# cell_value = cell_value
|
|
1074
|
-
cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1075
|
-
if wcswidth(cell_value) + cell_pos > self.term_w:
|
|
1076
|
-
cell_value = truncate_to_display_width(cell_value, self.term_w-cell_pos-10, self.centre_in_cols, self.unicode_char_width)
|
|
1077
|
-
|
|
1078
|
-
self.stdscr.addstr(y, cell_pos, cell_value, colour)
|
|
1079
|
-
# Part of the cell is on screen
|
|
1080
|
-
elif self.startx <= cell_pos+cell_width and cell_pos <= (self.rows_w):
|
|
1081
|
-
s = "max" if cell_max_width <= cell_width else "norm"
|
|
1082
|
-
cell_start = self.startx - cell_pos
|
|
1083
|
-
# self.stdscr.addstr(y, self.startx, ' '*(cell_width-cell_start), curses.color_pair(self.colours_start+colour_pair_number))
|
|
1084
|
-
cell_value = self.indexed_items[row][1][col]
|
|
1085
|
-
cell_value = f"{cell_value:^{self.column_widths[col]}}"
|
|
1086
|
-
|
|
1087
|
-
cell_value = cell_value[cell_start:visible_column_widths[col]][:self.rows_w-self.left_gutter_width]
|
|
1088
|
-
cell_value = truncate_to_display_width(cell_value, min(wcswidth(cell_value), cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1089
|
-
cell_value += self.separator
|
|
1090
|
-
cell_value = truncate_to_display_width(cell_value, min(wcswidth(cell_value), cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1091
|
-
self.stdscr.addstr(y, self.startx, cell_value, colour)
|
|
1120
|
+
# Start of cell is on screen
|
|
1121
|
+
if self.startx <= cell_pos <= self.rows_w+self.startx:
|
|
1122
|
+
s = "max" if cell_max_width <= cell_width else "norm"
|
|
1123
|
+
self.stdscr.addstr(y, cell_pos, (' '*cell_width)[:cell_max_width], colour)
|
|
1124
|
+
if self.centre_in_cols:
|
|
1125
|
+
cell_value = f"{self.indexed_items[row][1][col]:^{cell_width-len(self.separator)}}" + self.separator
|
|
1092
1126
|
else:
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1127
|
+
cell_value = self.indexed_items[row][1][col][:self.column_widths[col]] + self.separator
|
|
1128
|
+
cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1129
|
+
cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1130
|
+
if wcswidth(cell_value) + cell_pos > self.term_w:
|
|
1131
|
+
cell_value = truncate_to_display_width(cell_value, self.term_w-cell_pos-10, self.centre_in_cols, self.unicode_char_width)
|
|
1132
|
+
|
|
1133
|
+
self.stdscr.addstr(y, cell_pos, cell_value, colour)
|
|
1134
|
+
|
|
1135
|
+
# Part of the cell is on screen
|
|
1136
|
+
elif self.startx <= cell_pos+cell_width and cell_pos <= (self.rows_w):
|
|
1137
|
+
s = "max" if cell_max_width <= cell_width else "norm"
|
|
1138
|
+
cell_start = self.startx - cell_pos
|
|
1139
|
+
cell_value = self.indexed_items[row][1][col]
|
|
1140
|
+
cell_value = f"{cell_value:^{self.column_widths[col]}}"
|
|
1141
|
+
|
|
1142
|
+
cell_value = cell_value[cell_start:visible_column_widths[col]][:self.rows_w-self.left_gutter_width]
|
|
1143
|
+
cell_value = truncate_to_display_width(cell_value, min(wcswidth(cell_value), cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1144
|
+
cell_value += self.separator
|
|
1145
|
+
cell_value = truncate_to_display_width(cell_value, min(wcswidth(cell_value), cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1146
|
+
self.stdscr.addstr(y, self.startx, cell_value, colour)
|
|
1147
|
+
else:
|
|
1096
1148
|
pass
|
|
1097
1149
|
|
|
1098
1150
|
|
|
@@ -1136,10 +1188,11 @@ class Picker:
|
|
|
1136
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)
|
|
1137
1189
|
if not match: continue
|
|
1138
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)
|
|
1139
1192
|
|
|
1140
1193
|
## We want to search the non-centred values but highlight the centred values.
|
|
1141
1194
|
if self.centre_in_cols:
|
|
1142
|
-
tmp = truncate_to_display_width(item[1][highlight["field"]],
|
|
1195
|
+
tmp = truncate_to_display_width(item[1][highlight["field"]], width, self.centre_in_cols, self.unicode_char_width)
|
|
1143
1196
|
field_start += (len(tmp) - len(tmp.lstrip()))
|
|
1144
1197
|
|
|
1145
1198
|
highlight_start = field_start + match.start()
|
|
@@ -1150,7 +1203,7 @@ class Picker:
|
|
|
1150
1203
|
continue
|
|
1151
1204
|
highlight_start -= self.leftmost_char
|
|
1152
1205
|
highlight_end -= self.leftmost_char
|
|
1153
|
-
self.stdscr.addstr(y, max(self.startx, self.startx+highlight_start), row_str[max(highlight_start,0):min(self.rows_w-self.
|
|
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)
|
|
1154
1207
|
except:
|
|
1155
1208
|
pass
|
|
1156
1209
|
|
|
@@ -1166,7 +1219,7 @@ class Picker:
|
|
|
1166
1219
|
l0_highlights, l1_highlights, l2_highlights = sort_highlights(self.highlights)
|
|
1167
1220
|
|
|
1168
1221
|
|
|
1169
|
-
row_width = sum(self.
|
|
1222
|
+
row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
|
|
1170
1223
|
for idx in range(start_index, end_index):
|
|
1171
1224
|
item = self.indexed_items[idx]
|
|
1172
1225
|
y = idx - start_index + self.top_space
|
|
@@ -1186,7 +1239,7 @@ class Picker:
|
|
|
1186
1239
|
# trunc_width = 0
|
|
1187
1240
|
|
|
1188
1241
|
|
|
1189
|
-
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))
|
|
1190
1243
|
|
|
1191
1244
|
row_str = truncate_to_display_width(row_str_left_adj, trunc_width, self.unicode_char_width)
|
|
1192
1245
|
# row_str = truncate_to_display_width(row_str, min(w-self.startx, visible_columns_total_width))[self.leftmost_char:]
|
|
@@ -1197,7 +1250,7 @@ class Picker:
|
|
|
1197
1250
|
|
|
1198
1251
|
## Highlight column
|
|
1199
1252
|
if self.crosshair_cursor:
|
|
1200
|
-
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)
|
|
1201
1254
|
if idx == self.cursor_pos:
|
|
1202
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))
|
|
1203
1256
|
|
|
@@ -1211,21 +1264,21 @@ class Picker:
|
|
|
1211
1264
|
# self.selected_cells_by_row = get_selected_cells_by_row(self.cell_selections)
|
|
1212
1265
|
if item[0] in self.selected_cells_by_row:
|
|
1213
1266
|
for j in self.selected_cells_by_row[item[0]]:
|
|
1214
|
-
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)
|
|
1215
1268
|
|
|
1216
1269
|
# Visually selected
|
|
1217
1270
|
if self.is_selecting:
|
|
1218
1271
|
if self.start_selection <= idx <= self.cursor_pos or self.start_selection >= idx >= self.cursor_pos:
|
|
1219
1272
|
x_interval = range(min(self.start_selection_col, self.selected_column), max(self.start_selection_col, self.selected_column)+1)
|
|
1220
1273
|
for col in x_interval:
|
|
1221
|
-
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)
|
|
1222
1275
|
|
|
1223
1276
|
# Visually deslected
|
|
1224
1277
|
if self.is_deselecting:
|
|
1225
1278
|
if self.start_selection >= idx >= self.cursor_pos or self.start_selection <= idx <= self.cursor_pos:
|
|
1226
1279
|
x_interval = range(min(self.start_selection_col, self.selected_column), max(self.start_selection_col, self.selected_column)+1)
|
|
1227
1280
|
for col in x_interval:
|
|
1228
|
-
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)
|
|
1229
1282
|
# Higlight cursor row and selected rows
|
|
1230
1283
|
elif self.highlight_full_row:
|
|
1231
1284
|
if self.selections[item[0]]:
|
|
@@ -1242,16 +1295,30 @@ class Picker:
|
|
|
1242
1295
|
|
|
1243
1296
|
# Highlight the cursor row and the first char of the selected rows.
|
|
1244
1297
|
else:
|
|
1245
|
-
if self.
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
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]]:
|
|
1250
1313
|
self.stdscr.addstr(y, max(self.startx-2,0), ' ', curses.color_pair(self.colours_start+1))
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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))
|
|
1255
1322
|
|
|
1256
1323
|
if not self.highlights_hide:
|
|
1257
1324
|
draw_highlights(l1_highlights, idx, y, item)
|
|
@@ -1261,7 +1328,7 @@ class Picker:
|
|
|
1261
1328
|
# Draw cursor
|
|
1262
1329
|
if idx == self.cursor_pos:
|
|
1263
1330
|
if self.cell_cursor:
|
|
1264
|
-
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)
|
|
1265
1332
|
else:
|
|
1266
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)
|
|
1267
1334
|
|
|
@@ -1366,6 +1433,21 @@ class Picker:
|
|
|
1366
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
|
|
1367
1434
|
|
|
1368
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()
|
|
1369
1451
|
|
|
1370
1452
|
def infobox(self, stdscr: curses.window, message: str ="", title: str ="Infobox", colours_end: int = 0, duration: int = 4) -> curses.window:
|
|
1371
1453
|
""" Display non-interactive infobox window. """
|
|
@@ -1421,116 +1503,126 @@ class Picker:
|
|
|
1421
1503
|
self.logger.debug(f"function: get_function_data()")
|
|
1422
1504
|
""" Returns a dict of the main variables needed to restore the state of list_pikcer. """
|
|
1423
1505
|
function_data = {
|
|
1424
|
-
"
|
|
1425
|
-
"
|
|
1426
|
-
"
|
|
1427
|
-
"
|
|
1428
|
-
"
|
|
1429
|
-
"
|
|
1430
|
-
"
|
|
1431
|
-
"
|
|
1432
|
-
"
|
|
1433
|
-
"
|
|
1434
|
-
"
|
|
1435
|
-
"
|
|
1436
|
-
"
|
|
1437
|
-
"
|
|
1438
|
-
"
|
|
1439
|
-
"
|
|
1440
|
-
"
|
|
1441
|
-
"
|
|
1442
|
-
"
|
|
1443
|
-
"
|
|
1444
|
-
"
|
|
1445
|
-
"
|
|
1446
|
-
"
|
|
1447
|
-
"
|
|
1448
|
-
"
|
|
1449
|
-
"
|
|
1450
|
-
"
|
|
1451
|
-
"
|
|
1452
|
-
"
|
|
1453
|
-
"
|
|
1454
|
-
"
|
|
1455
|
-
"
|
|
1456
|
-
"
|
|
1457
|
-
"
|
|
1458
|
-
"
|
|
1459
|
-
"
|
|
1460
|
-
"
|
|
1461
|
-
"
|
|
1462
|
-
"
|
|
1463
|
-
"
|
|
1464
|
-
"
|
|
1465
|
-
"
|
|
1466
|
-
"
|
|
1467
|
-
"
|
|
1468
|
-
"
|
|
1469
|
-
"
|
|
1470
|
-
"
|
|
1471
|
-
"
|
|
1472
|
-
"
|
|
1473
|
-
"
|
|
1474
|
-
"
|
|
1475
|
-
"
|
|
1476
|
-
"
|
|
1477
|
-
"
|
|
1478
|
-
"
|
|
1479
|
-
"
|
|
1480
|
-
"
|
|
1481
|
-
"
|
|
1482
|
-
"
|
|
1483
|
-
"
|
|
1484
|
-
"
|
|
1485
|
-
"
|
|
1486
|
-
"
|
|
1487
|
-
"
|
|
1488
|
-
"
|
|
1489
|
-
"
|
|
1490
|
-
"
|
|
1491
|
-
"
|
|
1492
|
-
"
|
|
1493
|
-
"
|
|
1494
|
-
"
|
|
1495
|
-
"
|
|
1496
|
-
"
|
|
1497
|
-
"
|
|
1498
|
-
"
|
|
1499
|
-
"
|
|
1500
|
-
"
|
|
1501
|
-
"
|
|
1502
|
-
"
|
|
1503
|
-
"
|
|
1504
|
-
"
|
|
1505
|
-
"
|
|
1506
|
-
"
|
|
1507
|
-
"
|
|
1508
|
-
"
|
|
1509
|
-
"
|
|
1510
|
-
"
|
|
1511
|
-
"
|
|
1512
|
-
"
|
|
1513
|
-
"
|
|
1514
|
-
"
|
|
1515
|
-
"
|
|
1516
|
-
"
|
|
1517
|
-
"
|
|
1518
|
-
"
|
|
1519
|
-
"
|
|
1520
|
-
"
|
|
1521
|
-
"
|
|
1522
|
-
"
|
|
1523
|
-
"
|
|
1524
|
-
"
|
|
1525
|
-
"
|
|
1526
|
-
"
|
|
1527
|
-
"
|
|
1528
|
-
"
|
|
1529
|
-
"
|
|
1530
|
-
"
|
|
1531
|
-
"
|
|
1532
|
-
"
|
|
1533
|
-
|
|
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,
|
|
1534
1626
|
}
|
|
1535
1627
|
return function_data
|
|
1536
1628
|
|
|
@@ -1583,15 +1675,6 @@ class Picker:
|
|
|
1583
1675
|
self.initialise_picker_state(reset_colours=reset_colours)
|
|
1584
1676
|
|
|
1585
1677
|
self.initialise_variables()
|
|
1586
|
-
# if "colour_theme_number" in function_data:
|
|
1587
|
-
# global COLOURS_SET
|
|
1588
|
-
# COLOURS_SET = False
|
|
1589
|
-
# colours_end = set_colours(pick=self.colour_theme_number, start=self.colours_start)
|
|
1590
|
-
|
|
1591
|
-
# if "items" in function_data: self.items = function_data["items"]
|
|
1592
|
-
# if "header" in function_data: self.header = function_data["header"]
|
|
1593
|
-
# self.indexed_items = function_data["indexed_items"] if "indexed_items" in function_data else []
|
|
1594
|
-
|
|
1595
1678
|
|
|
1596
1679
|
|
|
1597
1680
|
def delete_entries(self) -> None:
|
|
@@ -1665,6 +1748,7 @@ class Picker:
|
|
|
1665
1748
|
"split_left": False,
|
|
1666
1749
|
"cell_cursor": False,
|
|
1667
1750
|
"crosshair_cursor": False,
|
|
1751
|
+
"header_separator": " │",
|
|
1668
1752
|
}
|
|
1669
1753
|
while True:
|
|
1670
1754
|
self.update_term_size()
|
|
@@ -1687,6 +1771,102 @@ class Picker:
|
|
|
1687
1771
|
return {}, "", f
|
|
1688
1772
|
|
|
1689
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
|
|
1690
1870
|
|
|
1691
1871
|
def notification(self, stdscr: curses.window, message: str="", title:str="Notification", colours_end: int=0, duration:int=4) -> None:
|
|
1692
1872
|
|
|
@@ -1729,7 +1909,6 @@ class Picker:
|
|
|
1729
1909
|
"crosshair_cursor": False,
|
|
1730
1910
|
"show_header": False,
|
|
1731
1911
|
"screen_size_function": lambda stdscr: (notification_height, notification_width),
|
|
1732
|
-
|
|
1733
1912
|
}
|
|
1734
1913
|
OptionPicker = Picker(submenu_win, **notification_data)
|
|
1735
1914
|
s, o, f = OptionPicker.run()
|
|
@@ -1933,6 +2112,9 @@ class Picker:
|
|
|
1933
2112
|
self.draw_screen()
|
|
1934
2113
|
self.notification(self.stdscr, message=f"Theme {self.colour_theme_number} applied.")
|
|
1935
2114
|
self.colours = get_colours(self.colour_theme_number)
|
|
2115
|
+
elif setting == "colsel":
|
|
2116
|
+
self.draw_screen()
|
|
2117
|
+
self.select_columns(self.stdscr)
|
|
1936
2118
|
|
|
1937
2119
|
else:
|
|
1938
2120
|
self.user_settings = ""
|
|
@@ -2298,7 +2480,13 @@ class Picker:
|
|
|
2298
2480
|
self.logger.info(f"function: fetch_data()")
|
|
2299
2481
|
tmp_items, tmp_header = [], []
|
|
2300
2482
|
self.getting_data.clear()
|
|
2301
|
-
self.refresh_function(
|
|
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
|
+
)
|
|
2302
2490
|
if self.track_entries_upon_refresh:
|
|
2303
2491
|
selected_indices = get_selected_indices(self.selections)
|
|
2304
2492
|
self.ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
|
|
@@ -2396,10 +2584,6 @@ class Picker:
|
|
|
2396
2584
|
row_len = 1
|
|
2397
2585
|
if self.header: row_len = len(self.header)
|
|
2398
2586
|
elif len(self.items): row_len = len(self.items[0])
|
|
2399
|
-
# if len(self.indexed_items) == 0:
|
|
2400
|
-
# insert_at_pos = 0
|
|
2401
|
-
# else:
|
|
2402
|
-
# insert_at_pos = self.indexed_items[self.cursor_pos][0]
|
|
2403
2587
|
self.items = self.items[:pos] + [["" for x in range(row_len)]] + self.items[pos:]
|
|
2404
2588
|
if pos <= self.cursor_pos:
|
|
2405
2589
|
self.cursor_pos += 1
|
|
@@ -2549,18 +2733,45 @@ class Picker:
|
|
|
2549
2733
|
max_total_width=self.rows_w,
|
|
2550
2734
|
unicode_char_width=self.unicode_char_width
|
|
2551
2735
|
)
|
|
2736
|
+
self.calculate_section_sizes()
|
|
2552
2737
|
|
|
2553
|
-
row_width = sum(self.
|
|
2738
|
+
row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
|
|
2554
2739
|
if row_width - self.leftmost_char < self.rows_w:
|
|
2555
2740
|
if row_width <= self.rows_w - self.left_gutter_width:
|
|
2556
2741
|
self.leftmost_char = 0
|
|
2557
2742
|
else:
|
|
2558
2743
|
self.leftmost_char = row_width - (self.rows_w - self.left_gutter_width) + 5
|
|
2559
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
|
+
|
|
2560
2769
|
def run(self) -> Tuple[list[int], str, dict]:
|
|
2561
2770
|
""" Run the picker. """
|
|
2562
2771
|
self.logger.info(f"function: run()")
|
|
2563
2772
|
|
|
2773
|
+
self.thread_stop_event.clear()
|
|
2774
|
+
|
|
2564
2775
|
if self.get_footer_string_startup and self.footer_string_refresh_function != None:
|
|
2565
2776
|
self.footer_string = " "
|
|
2566
2777
|
self.footer.adjust_sizes(self.term_h, self.term_w)
|
|
@@ -2665,9 +2876,12 @@ class Picker:
|
|
|
2665
2876
|
|
|
2666
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):
|
|
2667
2878
|
self.logger.debug(f"Get new data (refresh).")
|
|
2668
|
-
|
|
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
|
|
2669
2883
|
self.stdscr.refresh()
|
|
2670
|
-
if self.get_new_data
|
|
2884
|
+
if self.get_new_data:
|
|
2671
2885
|
self.refreshing_data = True
|
|
2672
2886
|
|
|
2673
2887
|
t = threading.Thread(target=self.fetch_data)
|
|
@@ -2810,6 +3024,14 @@ class Picker:
|
|
|
2810
3024
|
data["option_functions"] = f"[...] length = {len(data['option_functions'])}"
|
|
2811
3025
|
data["loaded_file_states"] = f"[...] length = {len(data['loaded_file_states'])}"
|
|
2812
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'])}"
|
|
2813
3035
|
info_items += [
|
|
2814
3036
|
["",""],
|
|
2815
3037
|
[" get_function_data()", "-*"*30],
|
|
@@ -2855,6 +3077,7 @@ class Picker:
|
|
|
2855
3077
|
elif self.check_key("exit", key, self.keys_dict):
|
|
2856
3078
|
self.stdscr.clear()
|
|
2857
3079
|
if len(self.loaded_files) <= 1:
|
|
3080
|
+
self.cleanup_threads()
|
|
2858
3081
|
function_data = self.get_function_data()
|
|
2859
3082
|
restore_terminal_settings(tty_fd, self.saved_terminal_state)
|
|
2860
3083
|
return [], "", function_data
|
|
@@ -2876,6 +3099,7 @@ class Picker:
|
|
|
2876
3099
|
self.draw_screen()
|
|
2877
3100
|
|
|
2878
3101
|
elif self.check_key("full_exit", key, self.keys_dict):
|
|
3102
|
+
self.cleanup_threads()
|
|
2879
3103
|
close_curses(self.stdscr)
|
|
2880
3104
|
restore_terminal_settings(tty_fd, self.saved_terminal_state)
|
|
2881
3105
|
exit()
|
|
@@ -2917,11 +3141,12 @@ class Picker:
|
|
|
2917
3141
|
options = []
|
|
2918
3142
|
if len(self.items) > 0:
|
|
2919
3143
|
options += [["cv", "Centre rows vertically"]]
|
|
2920
|
-
options += [["pc", "Pin cursor to row
|
|
3144
|
+
options += [["pc", "Pin cursor to row index during data refresh."]]
|
|
2921
3145
|
options += [["ct", "Centre column-set in terminal"]]
|
|
2922
3146
|
options += [["cc", "Centre values in cells"]]
|
|
2923
3147
|
options += [["!r", "Toggle auto-refresh"]]
|
|
2924
3148
|
options += [["th", "Cycle between themes. (accepts th#)"]]
|
|
3149
|
+
options += [["colsel", "Toggle columns."]]
|
|
2925
3150
|
options += [["nohl", "Toggle highlights"]]
|
|
2926
3151
|
options += [["footer", "Toggle footer"]]
|
|
2927
3152
|
options += [["header", "Toggle header"]]
|
|
@@ -3052,16 +3277,13 @@ class Picker:
|
|
|
3052
3277
|
self.cursor_pos = new_pos
|
|
3053
3278
|
self.ensure_no_overscroll()
|
|
3054
3279
|
self.draw_screen()
|
|
3055
|
-
|
|
3056
|
-
# if current_page + 1 == (len(self.indexed_items) + items_per_page - 1) // items_per_page:
|
|
3057
|
-
#
|
|
3058
|
-
# current_row = (len(self.indexed_items) +items_per_page - 1) % items_per_page
|
|
3059
|
-
# self.draw_screen()
|
|
3280
|
+
|
|
3060
3281
|
elif self.check_key("enter", key, self.keys_dict):
|
|
3061
3282
|
self.logger.info(f"key_function enter")
|
|
3062
3283
|
# Print the selected indices if any, otherwise print the current index
|
|
3063
3284
|
if self.is_selecting or self.is_deselecting: self.handle_visual_selection()
|
|
3064
3285
|
if len(self.items) == 0:
|
|
3286
|
+
self.cleanup_threads()
|
|
3065
3287
|
function_data = self.get_function_data()
|
|
3066
3288
|
restore_terminal_settings(tty_fd, self.saved_terminal_state)
|
|
3067
3289
|
return [], "", function_data
|
|
@@ -3088,6 +3310,7 @@ class Picker:
|
|
|
3088
3310
|
)
|
|
3089
3311
|
|
|
3090
3312
|
if options_sufficient:
|
|
3313
|
+
self.cleanup_threads()
|
|
3091
3314
|
self.user_opts = usrtxt
|
|
3092
3315
|
self.stdscr.clear()
|
|
3093
3316
|
self.stdscr.refresh()
|
|
@@ -3101,15 +3324,7 @@ class Picker:
|
|
|
3101
3324
|
self.cursor_pos = max(0, self.cursor_pos-self.items_per_page)
|
|
3102
3325
|
|
|
3103
3326
|
elif self.check_key("redraw_screen", key, self.keys_dict):
|
|
3104
|
-
self.
|
|
3105
|
-
self.stdscr.clear()
|
|
3106
|
-
self.stdscr.refresh()
|
|
3107
|
-
restrict_curses(self.stdscr)
|
|
3108
|
-
unrestrict_curses(self.stdscr)
|
|
3109
|
-
self.stdscr.clear()
|
|
3110
|
-
self.stdscr.refresh()
|
|
3111
|
-
|
|
3112
|
-
self.draw_screen()
|
|
3327
|
+
self.refresh_and_draw_screen()
|
|
3113
3328
|
|
|
3114
3329
|
elif self.check_key("cycle_sort_method", key, self.keys_dict):
|
|
3115
3330
|
if self.sort_column == self.selected_column:
|
|
@@ -3150,22 +3365,28 @@ class Picker:
|
|
|
3150
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
|
|
3151
3366
|
self.cursor_pos = [row[0] for row in self.indexed_items].index(current_index)
|
|
3152
3367
|
elif self.check_key("col_select_next", key, self.keys_dict):
|
|
3153
|
-
if len(self.items) > 0 and len(self.items[0]) > 0:
|
|
3154
|
-
col_index = (self.selected_column +1) % (len(self.items[0]))
|
|
3155
|
-
self.selected_column = col_index
|
|
3156
|
-
# Flash when we loop back to the first column
|
|
3157
|
-
# if self.selected_column == 0:
|
|
3158
|
-
# curses.flash()
|
|
3159
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()
|
|
3160
3381
|
|
|
3161
3382
|
|
|
3162
3383
|
## Scroll with column select
|
|
3163
3384
|
self.get_visible_rows()
|
|
3164
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)
|
|
3165
|
-
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
3166
|
-
column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
|
|
3167
|
-
start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
|
|
3168
|
-
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)
|
|
3169
3390
|
display_width = self.rows_w-self.left_gutter_width
|
|
3170
3391
|
# If the full column is within the current display then don't do anything
|
|
3171
3392
|
if start_of_cell >= self.leftmost_char and end_of_cell <= self.leftmost_char + display_width:
|
|
@@ -3178,11 +3399,17 @@ class Picker:
|
|
|
3178
3399
|
self.ensure_no_overscroll()
|
|
3179
3400
|
|
|
3180
3401
|
elif self.check_key("col_select_prev", key, self.keys_dict):
|
|
3181
|
-
if len(self.items) > 0 and len(self.items[0]) > 0:
|
|
3182
|
-
col_index = (self.selected_column -1) % (len(self.items[0]))
|
|
3183
|
-
self.selected_column = col_index
|
|
3184
|
-
|
|
3185
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
|
+
|
|
3186
3413
|
# Flash when we loop back to the last column
|
|
3187
3414
|
# if self.selected_column == len(self.column_widths)-1:
|
|
3188
3415
|
# curses.flash()
|
|
@@ -3190,10 +3417,10 @@ class Picker:
|
|
|
3190
3417
|
## Scroll with column select
|
|
3191
3418
|
self.get_visible_rows()
|
|
3192
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)
|
|
3193
|
-
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
3194
|
-
column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
|
|
3195
|
-
start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
|
|
3196
|
-
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)
|
|
3197
3424
|
display_width = self.rows_w-self.left_gutter_width
|
|
3198
3425
|
|
|
3199
3426
|
# If the entire column is within the current display then don't do anything
|
|
@@ -3209,21 +3436,21 @@ class Picker:
|
|
|
3209
3436
|
elif self.check_key("scroll_right", key, self.keys_dict):
|
|
3210
3437
|
self.logger.info(f"key_function scroll_right")
|
|
3211
3438
|
if len(self.indexed_items):
|
|
3212
|
-
row_width = sum(self.
|
|
3439
|
+
row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
|
|
3213
3440
|
if row_width-self.leftmost_char >= self.rows_w-5:
|
|
3214
3441
|
self.leftmost_char += 5
|
|
3215
3442
|
self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w) + self.left_gutter_width+5)
|
|
3216
|
-
if sum(self.
|
|
3443
|
+
if sum(self.visible_column_widths) + len(self.visible_column_widths)*len(self.separator) < self.rows_w:
|
|
3217
3444
|
self.leftmost_char = 0
|
|
3218
3445
|
|
|
3219
3446
|
elif self.check_key("scroll_right_25", key, self.keys_dict):
|
|
3220
3447
|
self.logger.info(f"key_function scroll_right")
|
|
3221
3448
|
if len(self.indexed_items):
|
|
3222
|
-
row_width = sum(self.
|
|
3449
|
+
row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
|
|
3223
3450
|
if row_width-self.leftmost_char+5 >= self.rows_w-25:
|
|
3224
3451
|
self.leftmost_char += 25
|
|
3225
3452
|
self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w) + self.left_gutter_width+5)
|
|
3226
|
-
if sum(self.
|
|
3453
|
+
if sum(self.visible_column_widths) + len(self.visible_column_widths)*len(self.separator) < self.rows_w:
|
|
3227
3454
|
self.leftmost_char = 0
|
|
3228
3455
|
|
|
3229
3456
|
elif self.check_key("scroll_left", key, self.keys_dict):
|
|
@@ -3242,24 +3469,13 @@ class Picker:
|
|
|
3242
3469
|
elif self.check_key("scroll_far_right", key, self.keys_dict):
|
|
3243
3470
|
self.logger.info(f"key_function scroll_far_right")
|
|
3244
3471
|
longest_row_str_len = 0
|
|
3245
|
-
|
|
3246
|
-
# for i in range(len(rows)):
|
|
3247
|
-
# item = rows[i]
|
|
3248
|
-
# row_str = format_row(item, self.hidden_columns, self.column_widths, self.separator, self.centre_in_cols, self.unicode_char_width)
|
|
3249
|
-
# if len(row_str) > longest_row_str_len: longest_row_str_len=len(row_str)
|
|
3250
|
-
longest_row_str_len = sum(self.column_widths) + (len(self.column_widths)-1)*len(self.separator)
|
|
3251
|
-
# for i in range(len(self.indexed_items)):
|
|
3252
|
-
# item = self.indexed_items[i]
|
|
3253
|
-
# row_str = format_row(item[1], self.hidden_columns, self.column_widths, self.separator, self.centre_in_cols)
|
|
3254
|
-
# if len(row_str) > longest_row_str_len: longest_row_str_len=len(row_str)
|
|
3255
|
-
# 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)
|
|
3256
3473
|
if len(self.indexed_items):
|
|
3257
|
-
row_width = sum(self.
|
|
3474
|
+
row_width = sum(self.visible_column_widths) + len(self.separator)*(len(self.visible_column_widths)-1)
|
|
3258
3475
|
self.leftmost_char = row_width - (self.rows_w) + self.left_gutter_width+5
|
|
3259
3476
|
self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w) + self.left_gutter_width+5)
|
|
3260
3477
|
|
|
3261
|
-
longest_row_str_len = sum(self.
|
|
3262
|
-
# 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)
|
|
3263
3479
|
|
|
3264
3480
|
|
|
3265
3481
|
|
|
@@ -3321,16 +3537,6 @@ class Picker:
|
|
|
3321
3537
|
self.selected_column = min(self.selected_column, row_len-2)
|
|
3322
3538
|
self.initialise_variables()
|
|
3323
3539
|
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
# elif self.check_key("increase_lines_per_page", key, self.keys_dict):
|
|
3328
|
-
# self.items_per_page += 1
|
|
3329
|
-
# self.draw_screen()
|
|
3330
|
-
# elif self.check_key("decrease_lines_per_page", key, self.keys_dict):
|
|
3331
|
-
# if self.items_per_page > 1:
|
|
3332
|
-
# self.items_per_page -= 1
|
|
3333
|
-
# self.draw_screen()
|
|
3334
3540
|
elif self.check_key("decrease_column_width", key, self.keys_dict):
|
|
3335
3541
|
self.logger.info(f"key_function decrease_column_width")
|
|
3336
3542
|
if self.max_column_width > 10:
|
|
@@ -3512,12 +3718,6 @@ class Picker:
|
|
|
3512
3718
|
function_data = self.get_function_data()
|
|
3513
3719
|
return [], "escape", function_data
|
|
3514
3720
|
|
|
3515
|
-
|
|
3516
|
-
# else:
|
|
3517
|
-
# self.search_query = ""
|
|
3518
|
-
# self.mode_index = 0
|
|
3519
|
-
# self.highlights = [highlight for highlight in self.highlights if "type" not in highlight or highlight["type"] != "search" ]
|
|
3520
|
-
# continue
|
|
3521
3721
|
self.draw_screen()
|
|
3522
3722
|
|
|
3523
3723
|
elif self.check_key("opts_input", key, self.keys_dict):
|
|
@@ -3733,7 +3933,7 @@ class Picker:
|
|
|
3733
3933
|
|
|
3734
3934
|
elif self.check_key("edit", key, self.keys_dict):
|
|
3735
3935
|
self.logger.info(f"key_function edit")
|
|
3736
|
-
if len(self.indexed_items) > 0 and self.
|
|
3936
|
+
if len(self.indexed_items) > 0 and self.editable_columns[self.selected_column]:
|
|
3737
3937
|
current_val = self.indexed_items[self.cursor_pos][1][self.selected_column]
|
|
3738
3938
|
usrtxt = f"{current_val}"
|
|
3739
3939
|
field_end_f = lambda: self.get_term_size()[1]-38 if self.show_footer else lambda: self.get_term_size()[1]-3
|
|
@@ -3762,6 +3962,47 @@ class Picker:
|
|
|
3762
3962
|
usrtxt = str(eval(usrtxt[3:]))
|
|
3763
3963
|
self.indexed_items[self.cursor_pos][1][self.selected_column] = usrtxt
|
|
3764
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
|
+
|
|
3765
4006
|
|
|
3766
4007
|
elif self.check_key("edit_picker", key, self.keys_dict):
|
|
3767
4008
|
self.logger.info(f"key_function edit_picker")
|
|
@@ -3793,7 +4034,7 @@ class Picker:
|
|
|
3793
4034
|
self.indexed_items[self.cursor_pos][1][self.selected_column] = usrtxt
|
|
3794
4035
|
self.history_edits.append(usrtxt)
|
|
3795
4036
|
elif self.check_key("edit_ipython", key, self.keys_dict):
|
|
3796
|
-
self.logger.info(f"key_function
|
|
4037
|
+
self.logger.info(f"key_function edit_ipython")
|
|
3797
4038
|
import IPython, termios
|
|
3798
4039
|
self.stdscr.clear()
|
|
3799
4040
|
restrict_curses(self.stdscr)
|
|
@@ -3937,7 +4178,7 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3937
4178
|
# input_arg = args.filename
|
|
3938
4179
|
|
|
3939
4180
|
elif args.generate:
|
|
3940
|
-
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)
|
|
3941
4182
|
function_data["get_data_startup"] = True
|
|
3942
4183
|
function_data["get_new_data"] = True
|
|
3943
4184
|
return args, function_data
|
|
@@ -4197,7 +4438,7 @@ def main() -> None:
|
|
|
4197
4438
|
# function_data["debug"] = True
|
|
4198
4439
|
# function_data["debug_level"] = 1
|
|
4199
4440
|
|
|
4200
|
-
function_data["cell_cursor"] = False
|
|
4441
|
+
# function_data["cell_cursor"] = False
|
|
4201
4442
|
|
|
4202
4443
|
function_data["split_right"] = False
|
|
4203
4444
|
function_data["split_left"] = False
|