listpick 0.1.16.9__py3-none-any.whl → 0.1.16.11__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 +273 -226
- listpick/ui/keys.py +1 -1
- listpick/ui/picker_colours.py +55 -0
- listpick/utils/utils.py +6 -11
- {listpick-0.1.16.9.dist-info → listpick-0.1.16.11.dist-info}/METADATA +1 -1
- {listpick-0.1.16.9.dist-info → listpick-0.1.16.11.dist-info}/RECORD +10 -10
- {listpick-0.1.16.9.dist-info → listpick-0.1.16.11.dist-info}/WHEEL +0 -0
- {listpick-0.1.16.9.dist-info → listpick-0.1.16.11.dist-info}/entry_points.txt +0 -0
- {listpick-0.1.16.9.dist-info → listpick-0.1.16.11.dist-info}/licenses/LICENSE.txt +0 -0
- {listpick-0.1.16.9.dist-info → listpick-0.1.16.11.dist-info}/top_level.txt +0 -0
listpick/listpick_app.py
CHANGED
|
@@ -20,7 +20,7 @@ import json
|
|
|
20
20
|
import threading
|
|
21
21
|
import string
|
|
22
22
|
import logging
|
|
23
|
-
|
|
23
|
+
import copy
|
|
24
24
|
|
|
25
25
|
from listpick.pane.pane_utils import get_file_attributes
|
|
26
26
|
from listpick.pane.left_pane_functions import *
|
|
@@ -96,6 +96,7 @@ class Picker:
|
|
|
96
96
|
options_list: list[str] = [],
|
|
97
97
|
user_settings : str = "",
|
|
98
98
|
separator : str = " ",
|
|
99
|
+
header_separator : str = " │",
|
|
99
100
|
search_query : str = "",
|
|
100
101
|
search_count : int = 0,
|
|
101
102
|
search_index : int = 0,
|
|
@@ -243,6 +244,7 @@ class Picker:
|
|
|
243
244
|
self.options_list = options_list
|
|
244
245
|
self.user_settings = user_settings
|
|
245
246
|
self.separator = separator
|
|
247
|
+
self.header_separator = header_separator
|
|
246
248
|
self.search_query = search_query
|
|
247
249
|
self.search_count = search_count
|
|
248
250
|
self.search_index = search_index
|
|
@@ -376,6 +378,11 @@ class Picker:
|
|
|
376
378
|
self.getting_data.set()
|
|
377
379
|
|
|
378
380
|
def __sizeof__(self):
|
|
381
|
+
"""
|
|
382
|
+
Return the approximate memory footprint of the Picker instance.
|
|
383
|
+
|
|
384
|
+
This includes the size of the instance itself and the sizes of its attributes.
|
|
385
|
+
"""
|
|
379
386
|
|
|
380
387
|
size = super().__sizeof__()
|
|
381
388
|
|
|
@@ -386,7 +393,19 @@ class Picker:
|
|
|
386
393
|
return size
|
|
387
394
|
|
|
388
395
|
def set_config(self, path: str ="~/.config/listpick/config.toml") -> bool:
|
|
389
|
-
""" Set config from toml file.
|
|
396
|
+
""" Set config from toml file.
|
|
397
|
+
|
|
398
|
+
This method reads a configuration file in TOML format, applies settings
|
|
399
|
+
to the Picker, and returns a boolean indicating success or failure.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
path (str): The path to the configuration file.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
bool: True if the configuration was successfully set; False otherwise.
|
|
406
|
+
"""
|
|
407
|
+
self.logger.info(f"function: set_config()")
|
|
408
|
+
|
|
390
409
|
path = os.path.expanduser(os.path.expandvars(path))
|
|
391
410
|
if not os.path.exists(path):
|
|
392
411
|
return False
|
|
@@ -395,7 +414,9 @@ class Picker:
|
|
|
395
414
|
except Exception as e:
|
|
396
415
|
self.logger.error(f"get_config({path}) load error. {e}")
|
|
397
416
|
return False
|
|
417
|
+
|
|
398
418
|
|
|
419
|
+
# Change the global theme if colour_theme_number is in the loaded config
|
|
399
420
|
if "general" in config:
|
|
400
421
|
if "colour_theme_number" in config["general"] and config["general"]["colour_theme_number"] != self.colour_theme_number:
|
|
401
422
|
global COLOURS_SET
|
|
@@ -403,7 +424,7 @@ class Picker:
|
|
|
403
424
|
self.colours_end = set_colours(pick=config["general"]["colour_theme_number"], start=1)
|
|
404
425
|
self.colours = get_colours(config["general"]["colour_theme_number"])
|
|
405
426
|
|
|
406
|
-
|
|
427
|
+
# load the rest of the config options
|
|
407
428
|
if "general" in config:
|
|
408
429
|
for key, val in config["general"].items():
|
|
409
430
|
self.logger.info(f"set_config: key={key}, val={val}.")
|
|
@@ -415,30 +436,58 @@ class Picker:
|
|
|
415
436
|
return True
|
|
416
437
|
|
|
417
438
|
def get_config(self, path: str ="~/.config/listpick/config.toml") -> dict:
|
|
418
|
-
"""
|
|
439
|
+
"""
|
|
440
|
+
Retrieve configuration settings from a specified TOML file.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
path (str): The file path of the configuration file. Default is
|
|
444
|
+
~/.config/listpick/config.toml.
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
dict: A dictionary containing the configuration settings loaded
|
|
448
|
+
from the TOML file. In case of an error, an empty dictionary is returned.
|
|
449
|
+
"""
|
|
450
|
+
|
|
419
451
|
self.logger.info(f"function: get_config()")
|
|
420
452
|
import toml
|
|
421
453
|
with open(os.path.expanduser(path), "r") as f:
|
|
422
454
|
config = toml.load(f)
|
|
423
455
|
return config
|
|
424
456
|
|
|
425
|
-
def update_term_size(self):
|
|
457
|
+
def update_term_size(self) -> None:
|
|
458
|
+
"""
|
|
459
|
+
Update self.term_h, self.term_w the function provided to the Picker.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
None
|
|
463
|
+
"""
|
|
426
464
|
self.term_h, self.term_w = self.screen_size_function(self.stdscr)
|
|
427
465
|
# self.term_h, self.term_w = self.stdscr.getmaxyx()
|
|
428
466
|
# self.term_w, self.term_h = os.get_terminal_size()
|
|
429
467
|
|
|
430
468
|
|
|
431
|
-
def get_term_size(self):
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
469
|
+
def get_term_size(self) -> Tuple[int, int]:
|
|
470
|
+
"""
|
|
471
|
+
Get the current terminal size using the function provided to the Picker.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Tuple[int, int]: A tuple containing the (height, width) of the terminal.
|
|
475
|
+
"""
|
|
476
|
+
return self.screen_size_function(self.stdscr)
|
|
477
|
+
# return self.stdscr.getmaxyx()
|
|
478
|
+
# w, h = os.get_terminal_size()
|
|
479
|
+
# return h, w
|
|
435
480
|
|
|
436
|
-
def calculate_section_sizes(self):
|
|
481
|
+
def calculate_section_sizes(self) -> None:
|
|
437
482
|
"""
|
|
438
483
|
Calculte the following for the Picker:
|
|
439
484
|
self.items_per_page: the number of entry rows displayed
|
|
440
485
|
self.bottom_space: the size of the footer + the bottom buffer space
|
|
441
486
|
self.top_space: the size of the space at the top of the picker: title + modes + header + top_gap
|
|
487
|
+
Calculate and update the sizes of various sections of the Picker.
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
None
|
|
442
491
|
"""
|
|
443
492
|
|
|
444
493
|
self.logger.debug(f"function: calculate_section_sizes()")
|
|
@@ -510,16 +559,27 @@ class Picker:
|
|
|
510
559
|
|
|
511
560
|
|
|
512
561
|
def get_visible_rows(self) -> list[list[str]]:
|
|
562
|
+
"""
|
|
563
|
+
Calculate and return the currently visible rows based on the cursor position and pagination settings.
|
|
513
564
|
|
|
565
|
+
This method determines which rows from the indexed items are visible on the screen,
|
|
566
|
+
accounting for pagination and scrolling. It sets the starting and ending indices
|
|
567
|
+
based on the current cursor position and the number of items per page.
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
list[list[str]]: The currently visible rows as a list of lists, where each inner
|
|
571
|
+
list represents a row of data. If there are no indexed items, it returns the
|
|
572
|
+
items array.
|
|
573
|
+
"""
|
|
514
574
|
self.logger.debug(f"function: get_visible_rows()")
|
|
515
575
|
## Scroll with column select
|
|
516
576
|
if self.paginate:
|
|
517
|
-
start_index = (self.cursor_pos//self.items_per_page) * self.items_per_page
|
|
577
|
+
start_index = (self.cursor_pos // self.items_per_page) * self.items_per_page
|
|
518
578
|
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
|
519
579
|
## Scroll
|
|
520
580
|
else:
|
|
521
|
-
scrolloff = self.items_per_page//2
|
|
522
|
-
start_index = max(0, min(self.cursor_pos - (self.items_per_page-scrolloff), len(self.indexed_items)-self.items_per_page))
|
|
581
|
+
scrolloff = self.items_per_page // 2
|
|
582
|
+
start_index = max(0, min(self.cursor_pos - (self.items_per_page - scrolloff), len(self.indexed_items) - self.items_per_page))
|
|
523
583
|
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
|
524
584
|
if len(self.indexed_items) == 0: start_index, end_index = 0, 0
|
|
525
585
|
|
|
@@ -532,13 +592,15 @@ class Picker:
|
|
|
532
592
|
def initialise_picker_state(self, reset_colours=False) -> None:
|
|
533
593
|
""" Initialise state variables for the picker. These are: debugging and colours. """
|
|
534
594
|
|
|
595
|
+
# Define global curses colours
|
|
535
596
|
if curses.has_colors() and self.colours != None:
|
|
536
|
-
# raise Exception("Terminal does not support color")
|
|
537
597
|
curses.start_color()
|
|
598
|
+
|
|
538
599
|
if reset_colours:
|
|
539
600
|
global COLOURS_SET
|
|
540
601
|
COLOURS_SET = False
|
|
541
602
|
self.colours_end = set_colours(pick=self.colour_theme_number, start=self.colours_start)
|
|
603
|
+
|
|
542
604
|
if curses.COLORS >= 255 and curses.COLOR_PAIRS >= 150:
|
|
543
605
|
self.colours_start = self.colours_start
|
|
544
606
|
self.notification_colours_start = self.colours_start+50
|
|
@@ -555,57 +617,32 @@ class Picker:
|
|
|
555
617
|
self.colours = get_colours(self.colour_theme_number)
|
|
556
618
|
|
|
557
619
|
|
|
620
|
+
# Start logger
|
|
558
621
|
debug_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]
|
|
559
622
|
dbglvl = debug_levels[self.debug_level]
|
|
560
623
|
self.logger = setup_logger(name="picker_log", log_file="picker.log", log_enabled=self.debug, level =dbglvl)
|
|
561
624
|
self.logger.info(f"Initialiasing Picker.")
|
|
625
|
+
|
|
562
626
|
self.update_term_size()
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
# 1 2 3 4 5
|
|
567
|
-
# logger = logging.getLogger(__file__)
|
|
568
|
-
# if self.debug_level == 0:
|
|
569
|
-
# logger = logging.getLogger()
|
|
570
|
-
# logger.disabled = True
|
|
571
|
-
# else:
|
|
572
|
-
#
|
|
573
|
-
# file_handler = logging.FileHandler(f"{self.title}.log", mode='w')
|
|
574
|
-
# formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%m-%d-%Y %H:%M:%S')
|
|
575
|
-
# file_handler.setFormatter(formatter)
|
|
576
|
-
# logger.addHandler(file_handler)
|
|
577
|
-
# logger.setLevel(debug_levels[self.debug_level-1])
|
|
578
|
-
|
|
579
|
-
# logging.basicConfig(
|
|
580
|
-
# level=debug_levels[self.debug_level-1],
|
|
581
|
-
# format='%(asctime)s - %(levelname)s - %(message)s',
|
|
582
|
-
# datefmt='%m-%d-%Y %H:%M:%S',
|
|
583
|
-
# filename=f"{self.title}.log",
|
|
584
|
-
# filemode="w",
|
|
585
|
-
# )
|
|
586
|
-
#
|
|
587
|
-
# self.logger.info(f"Starging log. Log level {logger.getEffectiveLevel()}")
|
|
588
|
-
# self.logger.info(f"Starging log. Log level {repr(debug_levels)}, {self.debug_level}, {debug_levels[self.debug_level-1]}")
|
|
589
|
-
# self.notification(self.stdscr, f"Starging log. Log level {repr(debug_levels)}, {self.debug_level}, {debug_levels[self.debug_level-1]}")
|
|
590
|
-
# self.notification(self.stdscr, f"{__file__}")
|
|
591
|
-
|
|
592
|
-
## Logging level plan
|
|
593
|
-
# DEBUG: loop functions, draw screen, etc.
|
|
594
|
-
# INFO: main functions
|
|
595
|
-
# WARNING: any try-except fails
|
|
596
|
-
|
|
597
|
-
# No set_escdelay function on windows.
|
|
627
|
+
|
|
628
|
+
# The curses implementation for some systems (e.g., windows) does not allow set_escdelay
|
|
598
629
|
try:
|
|
599
630
|
curses.set_escdelay(25)
|
|
600
631
|
except:
|
|
601
632
|
logging.warning("Error trying to set curses.set_escdelay")
|
|
602
633
|
|
|
603
|
-
# self.stdscr.clear()
|
|
604
|
-
# self.stdscr.refresh()
|
|
605
|
-
# self.draw_screen()
|
|
606
|
-
|
|
607
634
|
def initialise_variables(self, get_data: bool = False) -> None:
|
|
608
|
-
"""
|
|
635
|
+
"""
|
|
636
|
+
This method sets up the internal state of the Picker by initialising various attributes,
|
|
637
|
+
getting new data (if get_data is True), and ensuring that the lists used for tracking
|
|
638
|
+
selections, options, and items are correctly of the correct type, size, and shape. If
|
|
639
|
+
filter or sort queries are set then they are applied (or re-applied as the case may be).
|
|
640
|
+
The cursor_pos and selections are retained by tracking the id of the rows (where the id
|
|
641
|
+
is row[self.id_column]).
|
|
642
|
+
|
|
643
|
+
Parameters:
|
|
644
|
+
- get_data (bool): If True, pulls data synchronously and updates tracking variables.
|
|
645
|
+
"""
|
|
609
646
|
|
|
610
647
|
self.logger.info(f"function: initialise_variables()")
|
|
611
648
|
|
|
@@ -613,6 +650,7 @@ class Picker:
|
|
|
613
650
|
|
|
614
651
|
## Get data synchronously
|
|
615
652
|
if get_data and self.refresh_function != None:
|
|
653
|
+
# Track cursor_pos and selections by ther id (row[self.id_column][col])
|
|
616
654
|
if self.track_entries_upon_refresh and len(self.items) > 0:
|
|
617
655
|
tracking = True
|
|
618
656
|
selected_indices = get_selected_indices(self.selections)
|
|
@@ -620,34 +658,35 @@ class Picker:
|
|
|
620
658
|
self.ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
|
|
621
659
|
self.ids_tuples = [(i, item[self.id_column]) for i, item in enumerate(self.items) if i in selected_indices]
|
|
622
660
|
|
|
623
|
-
if len(self.indexed_items) > 0 and len(self.indexed_items)
|
|
661
|
+
if len(self.indexed_items) > 0 and self.cursor_pos < len(self.indexed_items) and len(self.indexed_items[0][1]) >= self.id_column:
|
|
624
662
|
self.cursor_pos_id = self.indexed_items[self.cursor_pos][1][self.id_column]
|
|
625
663
|
self.cursor_pos_prev = self.cursor_pos
|
|
626
|
-
|
|
627
|
-
|
|
628
664
|
|
|
665
|
+
# Set the state of the threading event
|
|
666
|
+
# Though we are getting data synchronously, we ensure the correct state for self.getting_data
|
|
629
667
|
self.getting_data.clear()
|
|
630
668
|
self.refresh_function(self.items, self.header, self.visible_rows_indices, self.getting_data)
|
|
631
669
|
|
|
632
|
-
self.items = pad_lists_to_same_length(self.items)
|
|
633
670
|
|
|
671
|
+
# Ensure that an emtpy items object has the form [[]]
|
|
634
672
|
if self.items == []: self.items = [[]]
|
|
635
|
-
|
|
673
|
+
|
|
674
|
+
# Ensure that items is a List[List[Str]] object
|
|
636
675
|
if len(self.items) > 0 and not isinstance(self.items[0], list):
|
|
637
676
|
self.items = [[item] for item in self.items]
|
|
638
677
|
# self.items = [[str(cell) for cell in row] for row in self.items]
|
|
639
678
|
|
|
679
|
+
# Ensure that the each of the rows of the items are of the same length
|
|
680
|
+
self.items = pad_lists_to_same_length(self.items)
|
|
640
681
|
|
|
641
682
|
# Ensure that header is of the same length as the rows
|
|
642
683
|
if self.header and len(self.items) > 0 and len(self.header) != len(self.items[0]):
|
|
643
684
|
self.header = [str(self.header[i]) if i < len(self.header) else "" for i in range(len(self.items[0]))]
|
|
644
685
|
|
|
645
|
-
# Constants
|
|
646
|
-
# DEFAULT_ITEMS_PER_PAGE = os.get_terminal_size().lines - top_gap*2-2-int(bool(header))
|
|
647
|
-
|
|
648
686
|
self.calculate_section_sizes()
|
|
649
687
|
|
|
650
|
-
|
|
688
|
+
|
|
689
|
+
# Ensure that the selection-tracking variables are the correct shape
|
|
651
690
|
if len(self.selections) != len(self.items):
|
|
652
691
|
self.selections = {i : False if i not in self.selections else bool(self.selections[i]) for i in range(len(self.items))}
|
|
653
692
|
|
|
@@ -658,31 +697,34 @@ class Picker:
|
|
|
658
697
|
self.cell_selections = {}
|
|
659
698
|
self.selected_cells_by_row = {}
|
|
660
699
|
|
|
700
|
+
def extend_list_to_length(lst, length, default_value):
|
|
701
|
+
"""Extend a list to the target length using a default value."""
|
|
702
|
+
if len(lst) < length:
|
|
703
|
+
lst.extend([copy.deepcopy(default_value) for _ in range(length - len(lst))])
|
|
661
704
|
|
|
662
705
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
if
|
|
668
|
-
self.
|
|
669
|
-
|
|
670
|
-
self.
|
|
671
|
-
|
|
672
|
-
self.editable_columns = self.
|
|
673
|
-
|
|
706
|
+
row_count = len(self.items)
|
|
707
|
+
col_count = len(self.items[0]) if row_count else 0
|
|
708
|
+
|
|
709
|
+
# Ensure that the length of the option lists are of the correct length.
|
|
710
|
+
if row_count > 0:
|
|
711
|
+
extend_list_to_length(self.require_option, length=row_count, default_value=self.require_option_default)
|
|
712
|
+
extend_list_to_length(self.option_functions, length=row_count, default_value=self.default_option_function)
|
|
713
|
+
extend_list_to_length(self.columns_sort_method, length=col_count, default_value=0)
|
|
714
|
+
extend_list_to_length(self.sort_reverse, length=col_count, default_value=False)
|
|
715
|
+
extend_list_to_length(self.editable_columns, length=col_count, default_value=self.editable_by_default)
|
|
716
|
+
|
|
717
|
+
if row_count > 0 and len(self.column_indices) < len(self.items[0]):
|
|
674
718
|
self.column_indices = self.column_indices + [i for i in range(len(self.column_indices), len(self.items[0]))]
|
|
675
719
|
|
|
676
720
|
|
|
677
721
|
|
|
678
|
-
#
|
|
679
|
-
# self.indexed_items = list(enumerate(items2))
|
|
722
|
+
# Create an indexed list of the items which will track the visible rows
|
|
680
723
|
if self.items == [[]]: self.indexed_items = []
|
|
681
724
|
else: self.indexed_items = list(enumerate(self.items))
|
|
682
725
|
|
|
683
|
-
#
|
|
726
|
+
# Apply the filter query
|
|
684
727
|
if self.filter_query:
|
|
685
|
-
# prev_index = self.indexed_items[cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
686
728
|
# prev_index = self.indexed_items[cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
687
729
|
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
|
688
730
|
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)
|
|
@@ -698,22 +740,30 @@ class Picker:
|
|
|
698
740
|
)
|
|
699
741
|
if return_val:
|
|
700
742
|
self.cursor_pos, self.search_index, self.search_count, self.highlights = tmp_cursor, tmp_index, tmp_count, tmp_highlights
|
|
701
|
-
|
|
743
|
+
|
|
744
|
+
# Apply the current sort method
|
|
702
745
|
if len(self.indexed_items) > 0:
|
|
703
746
|
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
|
|
704
747
|
|
|
705
748
|
|
|
706
|
-
#
|
|
707
|
-
## Move to a selectable row (if applicable)
|
|
749
|
+
# If we have more unselectable indices than rows, clear the unselectable_indices
|
|
708
750
|
if len(self.items) <= len(self.unselectable_indices): self.unselectable_indices = []
|
|
709
|
-
new_pos = (self.cursor_pos)%len(self.items)
|
|
710
|
-
while new_pos in self.unselectable_indices and new_pos != self.cursor_pos:
|
|
711
|
-
new_pos = (new_pos + 1) % len(self.items)
|
|
712
751
|
|
|
713
|
-
|
|
714
|
-
self.cursor_pos
|
|
752
|
+
# Move cursur to a selectable row if we are currently on an unselectable row)
|
|
753
|
+
if self.cursor_pos * len(self.items) in self.unselectable_indices:
|
|
754
|
+
original_pos = new_pos = (self.cursor_pos)%len(self.items)
|
|
755
|
+
while new_pos in self.unselectable_indices:
|
|
756
|
+
new_pos = (new_pos + 1) % len(self.items)
|
|
757
|
+
|
|
758
|
+
# Break if we loop back to the original position
|
|
759
|
+
if new_pos == original_pos:
|
|
760
|
+
break
|
|
761
|
+
|
|
762
|
+
self.cursor_pos = max(0, min(new_pos, len(self.items)-1))
|
|
763
|
+
|
|
764
|
+
# Initialise sheets
|
|
765
|
+
extend_list_to_length(self.sheet_states, length=len(self.sheets), default_value={})
|
|
715
766
|
|
|
716
|
-
# Sheets and files
|
|
717
767
|
if len(self.sheet_states) < len(self.sheets):
|
|
718
768
|
self.sheet_states += [{} for _ in range(len(self.sheets) - len(self.sheet_states))]
|
|
719
769
|
if len(self.sheets):
|
|
@@ -721,15 +771,16 @@ class Picker:
|
|
|
721
771
|
self.sheet_index = 0
|
|
722
772
|
self.sheet_name = self.sheets[self.sheet_index]
|
|
723
773
|
|
|
724
|
-
|
|
725
|
-
|
|
774
|
+
# Initialise files
|
|
775
|
+
extend_list_to_length(self.loaded_file_states, length=len(self.loaded_files), default_value={})
|
|
726
776
|
if len(self.loaded_files):
|
|
727
777
|
if self.loaded_file_index >= len(self.loaded_files):
|
|
728
778
|
self.loaded_file_index = 0
|
|
729
779
|
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
730
780
|
|
|
731
|
-
|
|
732
|
-
# Ensure that selected indices are
|
|
781
|
+
|
|
782
|
+
# Ensure that the correct cursor_pos and selected indices are reselected
|
|
783
|
+
# if we have fetched new data.
|
|
733
784
|
if self.track_entries_upon_refresh and (self.data_ready or tracking) and len(self.items) > 1:
|
|
734
785
|
selected_indices = []
|
|
735
786
|
all_ids = [item[self.id_column] for item in self.items]
|
|
@@ -752,6 +803,8 @@ class Picker:
|
|
|
752
803
|
|
|
753
804
|
|
|
754
805
|
|
|
806
|
+
# Ensure cursor_pos is set to a valid index
|
|
807
|
+
# If we have fetched new data then we attempt to set cursor_pos to the row with the same id as prev
|
|
755
808
|
if len(self.indexed_items):
|
|
756
809
|
if self.pin_cursor:
|
|
757
810
|
self.cursor_pos = min(self.cursor_pos_prev, len(self.indexed_items)-1)
|
|
@@ -763,7 +816,12 @@ class Picker:
|
|
|
763
816
|
else:
|
|
764
817
|
self.cursor_pos = 0
|
|
765
818
|
|
|
766
|
-
|
|
819
|
+
|
|
820
|
+
# Ensure that the pane indices are within the range of the available panes.
|
|
821
|
+
if len(self.left_panes): self.left_pane_index %= len(self.left_panes)
|
|
822
|
+
else: self.left_pane_index = 0
|
|
823
|
+
if len(self.right_panes): self.right_pane_index %= len(self.right_panes)
|
|
824
|
+
else: self.right_pane_index = 0
|
|
767
825
|
|
|
768
826
|
|
|
769
827
|
|
|
@@ -860,14 +918,11 @@ class Picker:
|
|
|
860
918
|
self.stdscr.erase()
|
|
861
919
|
|
|
862
920
|
self.update_term_size()
|
|
863
|
-
# if self.split_right and len(self.right_panes):
|
|
864
|
-
# proportion = self.right_panes[self.right_pane_index]["proportion"]
|
|
865
|
-
# self.rows_w, self.rows_h = int(self.term_w*proportion), self.term_h
|
|
866
|
-
# else:
|
|
867
|
-
# self.rows_w, self.rows_h = self.term_w, self.term_h
|
|
868
921
|
|
|
869
|
-
#
|
|
922
|
+
# Determine footer size
|
|
870
923
|
self.footer.adjust_sizes(self.term_h,self.term_w)
|
|
924
|
+
|
|
925
|
+
# The height of the footer may need to be adjusted if the file changes.
|
|
871
926
|
self.calculate_section_sizes()
|
|
872
927
|
|
|
873
928
|
# Test if the terminal is of a sufficient size to display the picker
|
|
@@ -885,28 +940,18 @@ class Picker:
|
|
|
885
940
|
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
|
886
941
|
if len(self.indexed_items) == 0: start_index, end_index = 0, 0
|
|
887
942
|
|
|
888
|
-
# 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)
|
|
889
|
-
# Determine widths based only on the currently indexed rows
|
|
890
|
-
# rows = [v[1] for v in self.indexed_items] if len(self.indexed_items) else self.items
|
|
891
|
-
# Determine widths based only on the currently displayed indexed rows
|
|
892
|
-
# rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
|
|
893
943
|
self.get_visible_rows()
|
|
894
944
|
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)
|
|
895
945
|
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
896
946
|
visible_columns_total_width = sum(visible_column_widths) + len(self.separator)*(len(visible_column_widths)-1)
|
|
897
947
|
|
|
898
|
-
# Determine the number of items_per_page, top_size and bottom_size
|
|
899
|
-
# self.calculate_section_sizes()
|
|
900
948
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
## Display title (if applicable)
|
|
949
|
+
## Display title
|
|
904
950
|
if self.title:
|
|
905
951
|
padded_title = f" {self.title.strip()} "
|
|
906
952
|
self.stdscr.addstr(self.top_gap, 0, f"{' ':^{self.term_w}}", curses.color_pair(self.colours_start+16))
|
|
907
953
|
title_x = (self.term_w-wcswidth(padded_title))//2
|
|
908
954
|
self.stdscr.addstr(self.top_gap, title_x, padded_title, curses.color_pair(self.colours_start+16) | curses.A_BOLD)
|
|
909
|
-
# top_space += 1
|
|
910
955
|
|
|
911
956
|
## Display modes
|
|
912
957
|
if self.display_modes and self.modes not in [[{}], []]:
|
|
@@ -928,7 +973,6 @@ class Picker:
|
|
|
928
973
|
else:
|
|
929
974
|
self.stdscr.addstr(self.top_gap+1, xmode, mode_str, curses.color_pair(self.colours_start+15) | curses.A_UNDERLINE)
|
|
930
975
|
xmode += split_space+mode_widths[i]
|
|
931
|
-
# top_space += 1
|
|
932
976
|
|
|
933
977
|
## Display header
|
|
934
978
|
if self.header and self.show_header:
|
|
@@ -946,93 +990,72 @@ class Picker:
|
|
|
946
990
|
|
|
947
991
|
|
|
948
992
|
header_str += f"{col_str:^{self.column_widths[i]-len(number)}}"
|
|
949
|
-
header_str += self.
|
|
993
|
+
header_str += self.header_separator
|
|
950
994
|
header_str_w = min(self.rows_w-self.left_gutter_width, visible_columns_total_width+1, self.term_w-self.startx)
|
|
951
995
|
|
|
952
996
|
header_str = header_str[self.leftmost_char:]
|
|
953
997
|
header_str = header_str[:header_str_w]
|
|
954
998
|
header_ypos = self.top_gap + bool(self.title) + bool(self.display_modes and self.modes)
|
|
999
|
+
|
|
1000
|
+
# Ensure that the full header width is filled--important if the header rows do not fill the terminal width
|
|
955
1001
|
self.stdscr.addstr(header_ypos, self.rows_box_x_i, ' '*self.rows_w, curses.color_pair(self.colours_start+28) | curses.A_BOLD)
|
|
1002
|
+
|
|
1003
|
+
# Draw header string
|
|
956
1004
|
self.stdscr.addstr(header_ypos, self.startx, header_str, curses.color_pair(self.colours_start+4) | curses.A_BOLD)
|
|
957
1005
|
|
|
958
1006
|
# Highlight sort column
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
x_pos = len(up_to_selected_col) - self.leftmost_char + self.startx
|
|
1007
|
+
if self.selected_column != None and self.selected_column not in self.hidden_columns:
|
|
1008
|
+
# start of string is on screen
|
|
1009
|
+
col_width = self.column_widths[self.selected_column]
|
|
1010
|
+
number = f"{self.selected_column}. " if self.number_columns else ""
|
|
1011
|
+
col_str = self.header[self.selected_column][:self.column_widths[self.selected_column]-len(number)]
|
|
1012
|
+
highlighted_col_str = (number+f"{col_str:^{self.column_widths[self.selected_column]-len(number)}}") + self.separator
|
|
1013
|
+
|
|
1014
|
+
if len(self.column_widths) == 1:
|
|
1015
|
+
colour = curses.color_pair(self.colours_start+28) | curses.A_BOLD
|
|
1016
|
+
else:
|
|
1017
|
+
colour = curses.color_pair(self.colours_start+19) | curses.A_BOLD
|
|
1018
|
+
# Start of selected column is on the screen
|
|
1019
|
+
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):
|
|
1020
|
+
x_pos = len(up_to_selected_col) - self.leftmost_char + self.startx
|
|
974
1021
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1022
|
+
# Whole cell of the selected column is on the screen
|
|
1023
|
+
if len(up_to_selected_col)+col_width - self.leftmost_char < self.rows_w-self.left_gutter_width:
|
|
1024
|
+
disp_str = highlighted_col_str
|
|
978
1025
|
|
|
979
|
-
|
|
980
|
-
else:
|
|
981
|
-
overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w - self.left_gutter_width)
|
|
982
|
-
disp_str = highlighted_col_str[:-overflow]
|
|
983
|
-
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
984
|
-
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
985
|
-
|
|
986
|
-
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
987
|
-
# Start of the cell is to the right of the screen
|
|
988
|
-
elif self.leftmost_char+self.rows_w <= len(up_to_selected_col):
|
|
989
|
-
pass
|
|
990
|
-
# The end of the cell is on the screen, the start of the cell is not
|
|
991
|
-
elif 0 <= len(up_to_selected_col)+col_width - self.leftmost_char <= self.rows_w :
|
|
992
|
-
x_pos = self.startx
|
|
993
|
-
beg = self.leftmost_char - len(up_to_selected_col)
|
|
994
|
-
disp_str = highlighted_col_str[beg:]
|
|
995
|
-
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
996
|
-
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
997
|
-
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
998
|
-
# The middle of the cell is on the screen, the start and end of the cell are not
|
|
999
|
-
elif self.leftmost_char <= len(up_to_selected_col) + col_width//2 <= self.leftmost_char+self.rows_w:
|
|
1000
|
-
beg = self.leftmost_char - len(up_to_selected_col)
|
|
1001
|
-
overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w)
|
|
1002
|
-
x_pos = self.startx
|
|
1003
|
-
disp_str = highlighted_col_str[beg:-overflow]
|
|
1004
|
-
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
1005
|
-
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
1006
|
-
|
|
1007
|
-
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
1008
|
-
# The cell is to the left of the screen
|
|
1026
|
+
# Start of the cell is on the screen, but the end of the cell is not
|
|
1009
1027
|
else:
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1028
|
+
overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w - self.left_gutter_width)
|
|
1029
|
+
disp_str = highlighted_col_str[:-overflow]
|
|
1030
|
+
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
1031
|
+
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
1032
|
+
|
|
1033
|
+
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
1034
|
+
# Start of the cell is to the right of the screen
|
|
1035
|
+
elif self.leftmost_char+self.rows_w <= len(up_to_selected_col):
|
|
1036
|
+
pass
|
|
1037
|
+
# The end of the cell is on the screen, the start of the cell is not
|
|
1038
|
+
elif 0 <= len(up_to_selected_col)+col_width - self.leftmost_char <= self.rows_w :
|
|
1039
|
+
x_pos = self.startx
|
|
1040
|
+
beg = self.leftmost_char - len(up_to_selected_col)
|
|
1041
|
+
disp_str = highlighted_col_str[beg:]
|
|
1042
|
+
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
1043
|
+
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
1044
|
+
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
1045
|
+
# The middle of the cell is on the screen, the start and end of the cell are not
|
|
1046
|
+
elif self.leftmost_char <= len(up_to_selected_col) + col_width//2 <= self.leftmost_char+self.rows_w:
|
|
1047
|
+
beg = self.leftmost_char - len(up_to_selected_col)
|
|
1048
|
+
overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w)
|
|
1049
|
+
x_pos = self.startx
|
|
1050
|
+
disp_str = highlighted_col_str[beg:-overflow]
|
|
1051
|
+
disp_str_w = min(len(disp_str), self.term_w-x_pos)
|
|
1052
|
+
disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
|
|
1053
|
+
|
|
1054
|
+
self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
|
|
1055
|
+
|
|
1056
|
+
# The cell is to the left of the focused part of the screen
|
|
1057
|
+
else:
|
|
1058
|
+
pass
|
|
1036
1059
|
|
|
1037
1060
|
# Display row header
|
|
1038
1061
|
if self.show_row_header:
|
|
@@ -1056,41 +1079,34 @@ class Picker:
|
|
|
1056
1079
|
colour = curses.color_pair(self.colours_start+colour_pair_number) | curses.A_BOLD
|
|
1057
1080
|
else:
|
|
1058
1081
|
colour = curses.color_pair(self.colours_start+colour_pair_number)
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
if
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
cell_value = f"{self.indexed_items[row][1][col]:^{cell_width-len(self.separator)}}" + self.separator
|
|
1066
|
-
else:
|
|
1067
|
-
cell_value = self.indexed_items[row][1][col][:self.column_widths[col]] + self.separator
|
|
1068
|
-
# cell_value = cell_value[:min(cell_width, cell_max_width)-len(self.separator)]
|
|
1069
|
-
cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1070
|
-
# cell_value = cell_value + self.separator
|
|
1071
|
-
# cell_value = cell_value
|
|
1072
|
-
cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1073
|
-
if wcswidth(cell_value) + cell_pos > self.term_w:
|
|
1074
|
-
cell_value = truncate_to_display_width(cell_value, self.term_w-cell_pos-10, self.centre_in_cols, self.unicode_char_width)
|
|
1075
|
-
|
|
1076
|
-
self.stdscr.addstr(y, cell_pos, cell_value, colour)
|
|
1077
|
-
# Part of the cell is on screen
|
|
1078
|
-
elif self.startx <= cell_pos+cell_width and cell_pos <= (self.rows_w):
|
|
1079
|
-
s = "max" if cell_max_width <= cell_width else "norm"
|
|
1080
|
-
cell_start = self.startx - cell_pos
|
|
1081
|
-
# self.stdscr.addstr(y, self.startx, ' '*(cell_width-cell_start), curses.color_pair(self.colours_start+colour_pair_number))
|
|
1082
|
-
cell_value = self.indexed_items[row][1][col]
|
|
1083
|
-
cell_value = f"{cell_value:^{self.column_widths[col]}}"
|
|
1084
|
-
|
|
1085
|
-
cell_value = cell_value[cell_start:visible_column_widths[col]][:self.rows_w-self.left_gutter_width]
|
|
1086
|
-
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)
|
|
1087
|
-
cell_value += self.separator
|
|
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
|
-
self.stdscr.addstr(y, self.startx, cell_value, colour)
|
|
1082
|
+
# Start of cell is on screen
|
|
1083
|
+
if self.startx <= cell_pos <= self.rows_w+self.startx:
|
|
1084
|
+
s = "max" if cell_max_width <= cell_width else "norm"
|
|
1085
|
+
self.stdscr.addstr(y, cell_pos, (' '*cell_width)[:cell_max_width], colour)
|
|
1086
|
+
if self.centre_in_cols:
|
|
1087
|
+
cell_value = f"{self.indexed_items[row][1][col]:^{cell_width-len(self.separator)}}" + self.separator
|
|
1090
1088
|
else:
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1089
|
+
cell_value = self.indexed_items[row][1][col][:self.column_widths[col]] + self.separator
|
|
1090
|
+
cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1091
|
+
cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
|
|
1092
|
+
if wcswidth(cell_value) + cell_pos > self.term_w:
|
|
1093
|
+
cell_value = truncate_to_display_width(cell_value, self.term_w-cell_pos-10, self.centre_in_cols, self.unicode_char_width)
|
|
1094
|
+
|
|
1095
|
+
self.stdscr.addstr(y, cell_pos, cell_value, colour)
|
|
1096
|
+
|
|
1097
|
+
# Part of the cell is on screen
|
|
1098
|
+
elif self.startx <= cell_pos+cell_width and cell_pos <= (self.rows_w):
|
|
1099
|
+
s = "max" if cell_max_width <= cell_width else "norm"
|
|
1100
|
+
cell_start = self.startx - cell_pos
|
|
1101
|
+
cell_value = self.indexed_items[row][1][col]
|
|
1102
|
+
cell_value = f"{cell_value:^{self.column_widths[col]}}"
|
|
1103
|
+
|
|
1104
|
+
cell_value = cell_value[cell_start:visible_column_widths[col]][:self.rows_w-self.left_gutter_width]
|
|
1105
|
+
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)
|
|
1106
|
+
cell_value += self.separator
|
|
1107
|
+
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)
|
|
1108
|
+
self.stdscr.addstr(y, self.startx, cell_value, colour)
|
|
1109
|
+
else:
|
|
1094
1110
|
pass
|
|
1095
1111
|
|
|
1096
1112
|
|
|
@@ -2067,6 +2083,7 @@ class Picker:
|
|
|
2067
2083
|
if self.indexed_items[new_pos][0] in self.unselectable_indices: new_pos+=1
|
|
2068
2084
|
else: break
|
|
2069
2085
|
self.cursor_pos = new_pos
|
|
2086
|
+
self.ensure_no_overscroll()
|
|
2070
2087
|
return True
|
|
2071
2088
|
|
|
2072
2089
|
def cursor_up(self, count=1) -> bool:
|
|
@@ -2080,6 +2097,7 @@ class Picker:
|
|
|
2080
2097
|
elif new_pos in self.unselectable_indices: new_pos -= 1
|
|
2081
2098
|
else: break
|
|
2082
2099
|
self.cursor_pos = new_pos
|
|
2100
|
+
self.ensure_no_overscroll()
|
|
2083
2101
|
return True
|
|
2084
2102
|
|
|
2085
2103
|
def remapped_key(self, key: int, val: int, key_remappings: dict) -> bool:
|
|
@@ -2505,23 +2523,53 @@ class Picker:
|
|
|
2505
2523
|
self.split_right = not self.split_right
|
|
2506
2524
|
if self.right_panes[self.right_pane_index]["data"] in [[], None, {}]:
|
|
2507
2525
|
self.right_panes[self.right_pane_index]["data"] = self.right_panes[self.right_pane_index]["get_data"](self.right_panes[self.right_pane_index]["data"], self.get_function_data())
|
|
2526
|
+
self.ensure_no_overscroll()
|
|
2508
2527
|
|
|
2509
2528
|
def toggle_left_pane(self):
|
|
2510
2529
|
if len(self.left_panes):
|
|
2511
2530
|
self.split_left = not self.split_left
|
|
2512
2531
|
if self.left_panes[self.left_pane_index]["data"] in [[], None, {}]:
|
|
2513
2532
|
self.left_panes[self.left_pane_index]["data"] = self.left_panes[self.left_pane_index]["get_data"](self.left_panes[self.left_pane_index]["data"], self.get_function_data())
|
|
2533
|
+
self.ensure_no_overscroll()
|
|
2514
2534
|
|
|
2515
2535
|
|
|
2516
2536
|
def cycle_right_pane(self, increment=1):
|
|
2517
2537
|
if len(self.right_panes) > 1:
|
|
2518
2538
|
self.right_pane_index = (self.right_pane_index+1)%len(self.right_panes)
|
|
2519
2539
|
self.initial_right_split_time -= self.right_panes[self.right_pane_index]["refresh_time"]
|
|
2540
|
+
self.ensure_no_overscroll()
|
|
2520
2541
|
|
|
2521
2542
|
def cycle_left_pane(self, increment=1):
|
|
2522
2543
|
if len(self.left_panes) > 1:
|
|
2523
2544
|
self.left_pane_index = (self.left_pane_index+1)%len(self.left_panes)
|
|
2524
2545
|
self.initial_left_split_time -= self.left_panes[self.left_pane_index]["refresh_time"]
|
|
2546
|
+
self.ensure_no_overscroll()
|
|
2547
|
+
|
|
2548
|
+
def ensure_no_overscroll(self):
|
|
2549
|
+
"""
|
|
2550
|
+
Ensure that we haven't scrolled past the last column.
|
|
2551
|
+
|
|
2552
|
+
This check should be performed after:
|
|
2553
|
+
- Terminal resize event
|
|
2554
|
+
- Scrolling down - i.e., rows with potentially different widths come into view
|
|
2555
|
+
"""
|
|
2556
|
+
self.calculate_section_sizes()
|
|
2557
|
+
self.get_visible_rows()
|
|
2558
|
+
self.column_widths = get_column_widths(
|
|
2559
|
+
self.visible_rows,
|
|
2560
|
+
header=self.header,
|
|
2561
|
+
max_column_width=self.max_column_width,
|
|
2562
|
+
number_columns=self.number_columns,
|
|
2563
|
+
max_total_width=self.rows_w,
|
|
2564
|
+
unicode_char_width=self.unicode_char_width
|
|
2565
|
+
)
|
|
2566
|
+
|
|
2567
|
+
row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
|
|
2568
|
+
if row_width - self.leftmost_char < self.rows_w:
|
|
2569
|
+
if row_width <= self.rows_w - self.left_gutter_width:
|
|
2570
|
+
self.leftmost_char = 0
|
|
2571
|
+
else:
|
|
2572
|
+
self.leftmost_char = row_width - (self.rows_w - self.left_gutter_width) + 5
|
|
2525
2573
|
|
|
2526
2574
|
def run(self) -> Tuple[list[int], str, dict]:
|
|
2527
2575
|
""" Run the picker. """
|
|
@@ -2991,6 +3039,7 @@ class Picker:
|
|
|
2991
3039
|
self.selected_cells_by_row[row] = [col]
|
|
2992
3040
|
|
|
2993
3041
|
self.cursor_down()
|
|
3042
|
+
self.ensure_no_overscroll()
|
|
2994
3043
|
elif self.check_key("select_all", key, self.keys_dict): # Select all (m or ctrl-a)
|
|
2995
3044
|
self.select_all()
|
|
2996
3045
|
|
|
@@ -3005,6 +3054,7 @@ class Picker:
|
|
|
3005
3054
|
if new_pos < len(self.indexed_items):
|
|
3006
3055
|
self.cursor_pos = new_pos
|
|
3007
3056
|
|
|
3057
|
+
self.ensure_no_overscroll()
|
|
3008
3058
|
self.draw_screen()
|
|
3009
3059
|
|
|
3010
3060
|
elif self.check_key("cursor_bottom", key, self.keys_dict):
|
|
@@ -3014,6 +3064,7 @@ class Picker:
|
|
|
3014
3064
|
else: break
|
|
3015
3065
|
if new_pos < len(self.items) and new_pos >= 0:
|
|
3016
3066
|
self.cursor_pos = new_pos
|
|
3067
|
+
self.ensure_no_overscroll()
|
|
3017
3068
|
self.draw_screen()
|
|
3018
3069
|
# current_row = items_per_page - 1
|
|
3019
3070
|
# if current_page + 1 == (len(self.indexed_items) + items_per_page - 1) // items_per_page:
|
|
@@ -3138,6 +3189,7 @@ class Picker:
|
|
|
3138
3189
|
self.leftmost_char = end_of_cell - display_width
|
|
3139
3190
|
|
|
3140
3191
|
self.leftmost_char = max(0, min(column_set_width - display_width + 5, self.leftmost_char))
|
|
3192
|
+
self.ensure_no_overscroll()
|
|
3141
3193
|
|
|
3142
3194
|
elif self.check_key("col_select_prev", key, self.keys_dict):
|
|
3143
3195
|
if len(self.items) > 0 and len(self.items[0]) > 0:
|
|
@@ -3166,6 +3218,7 @@ class Picker:
|
|
|
3166
3218
|
self.leftmost_char = start_of_cell
|
|
3167
3219
|
|
|
3168
3220
|
self.leftmost_char = max(0, min(column_set_width - display_width + 5, self.leftmost_char))
|
|
3221
|
+
self.ensure_no_overscroll()
|
|
3169
3222
|
|
|
3170
3223
|
elif self.check_key("scroll_right", key, self.keys_dict):
|
|
3171
3224
|
self.logger.info(f"key_function scroll_right")
|
|
@@ -3317,14 +3370,8 @@ class Picker:
|
|
|
3317
3370
|
elif key == curses.KEY_RESIZE: # Terminal resize signal
|
|
3318
3371
|
|
|
3319
3372
|
self.calculate_section_sizes()
|
|
3320
|
-
self.
|
|
3373
|
+
self.ensure_no_overscroll()
|
|
3321
3374
|
|
|
3322
|
-
row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
|
|
3323
|
-
if row_width - self.leftmost_char < self.rows_w:
|
|
3324
|
-
if row_width <= self.rows_w - self.left_gutter_width:
|
|
3325
|
-
self.leftmost_char = 0
|
|
3326
|
-
else:
|
|
3327
|
-
self.leftmost_char = row_width - (self.rows_w - self.left_gutter_width) + 5
|
|
3328
3375
|
self.stdscr.clear()
|
|
3329
3376
|
self.stdscr.refresh()
|
|
3330
3377
|
self.draw_screen()
|
listpick/ui/keys.py
CHANGED
|
@@ -51,7 +51,7 @@ picker_keys = {
|
|
|
51
51
|
"continue_search_forward": [ord('n')],
|
|
52
52
|
"continue_search_backward": [ord('N')],
|
|
53
53
|
"cancel": [27], # Escape key
|
|
54
|
-
"opts_input": [
|
|
54
|
+
"opts_input": [keycodes.META_o],
|
|
55
55
|
"opts_select": [ord('o')],
|
|
56
56
|
"mode_next": [9], # Tab key
|
|
57
57
|
"mode_prev": [353], # Shift+Tab key
|
listpick/ui/picker_colours.py
CHANGED
|
@@ -294,6 +294,61 @@ def get_colours(pick:int=0) -> Dict[str, int]:
|
|
|
294
294
|
'active_column_bg': curses.COLOR_BLACK,
|
|
295
295
|
'active_column_fg': curses.COLOR_WHITE,
|
|
296
296
|
},
|
|
297
|
+
### (6) Use default colors for bg
|
|
298
|
+
# {
|
|
299
|
+
# 'background': -1,
|
|
300
|
+
# 'normal_fg': -1,
|
|
301
|
+
# 'unselected_bg': -1,
|
|
302
|
+
# 'unselected_fg': -1,
|
|
303
|
+
# 'cursor_bg': 21,
|
|
304
|
+
# 'cursor_fg': -1,
|
|
305
|
+
# 'selected_bg': 54,
|
|
306
|
+
# 'selected_fg': -1,
|
|
307
|
+
# 'header_bg': 255,
|
|
308
|
+
# 'header_fg': 232,
|
|
309
|
+
# 'error_bg': 232,
|
|
310
|
+
# 'error_fg': curses.COLOR_RED,
|
|
311
|
+
# 'complete_bg': 232,
|
|
312
|
+
# 'complete_fg': 82,
|
|
313
|
+
# 'waiting_bg': 232,
|
|
314
|
+
# 'waiting_fg': curses.COLOR_YELLOW,
|
|
315
|
+
# 'active_bg': 232,
|
|
316
|
+
# 'active_fg': 33,
|
|
317
|
+
# 'paused_bg': -1,
|
|
318
|
+
# 'paused_fg': 244,
|
|
319
|
+
# 'search_bg': 162,
|
|
320
|
+
# 'search_fg': -1,
|
|
321
|
+
# 'active_input_bg': -1,
|
|
322
|
+
# 'active_input_fg': 232,
|
|
323
|
+
# 'modes_selected_bg': 232,
|
|
324
|
+
# 'modes_selected_fg': -1,
|
|
325
|
+
# 'modes_unselected_bg': 255,
|
|
326
|
+
# 'modes_unselected_fg': 232,
|
|
327
|
+
# 'title_bar': 232,
|
|
328
|
+
# 'title_bg': 232,
|
|
329
|
+
# 'title_fg': -1,
|
|
330
|
+
# 'scroll_bar_bg': 247,
|
|
331
|
+
# 'selected_header_column_bg': 232,
|
|
332
|
+
# 'selected_header_column_fg': -1,
|
|
333
|
+
# 'unselected_header_column_bg': -1,
|
|
334
|
+
# 'unselected_header_column_fg': -1,
|
|
335
|
+
# 'footer_bg': 232,
|
|
336
|
+
# 'footer_fg': -1,
|
|
337
|
+
# 'refreshing_bg': 232,
|
|
338
|
+
# 'refreshing_fg': -1,
|
|
339
|
+
# 'refreshing_inactive_bg': 232,
|
|
340
|
+
# 'refreshing_inactive_fg': 232,
|
|
341
|
+
# '40pc_bg': 232,
|
|
342
|
+
# '40pc_fg': 166,
|
|
343
|
+
# 'footer_string_bg': 232,
|
|
344
|
+
# 'footer_string_fg': -1,
|
|
345
|
+
# 'selected_cell_bg': 54,
|
|
346
|
+
# 'selected_cell_fg': -1,
|
|
347
|
+
# 'deselecting_cell_bg': 162,
|
|
348
|
+
# 'deselecting_cell_fg': -1,
|
|
349
|
+
# 'active_column_bg': 234,
|
|
350
|
+
# 'active_column_fg': -1,
|
|
351
|
+
# },
|
|
297
352
|
]
|
|
298
353
|
for colour in colours:
|
|
299
354
|
colour["20pc_bg"] = colour["background"]
|
listpick/utils/utils.py
CHANGED
|
@@ -15,6 +15,7 @@ import os
|
|
|
15
15
|
from typing import Tuple, Dict
|
|
16
16
|
import logging
|
|
17
17
|
import shlex
|
|
18
|
+
from collections import defaultdict
|
|
18
19
|
|
|
19
20
|
logger = logging.getLogger('picker_log')
|
|
20
21
|
|
|
@@ -205,17 +206,11 @@ def get_selected_cells(cell_selections: Dict[Tuple[int, int], bool]) -> list[Tup
|
|
|
205
206
|
def get_selected_cells_by_row(cell_selections: dict[tuple[int, int], bool]) -> dict[int, list[int]]:
|
|
206
207
|
""" {0: [1,2], 9: [1] }"""
|
|
207
208
|
|
|
208
|
-
d =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
d[tup[0]].append(tup[1])
|
|
214
|
-
else:
|
|
215
|
-
d[tup[0]] = [tup[1]]
|
|
216
|
-
except:
|
|
217
|
-
pass
|
|
218
|
-
return d
|
|
209
|
+
d = defaultdict(list)
|
|
210
|
+
for (row, col), selected in cell_selections.items():
|
|
211
|
+
if selected:
|
|
212
|
+
d[row].append(col)
|
|
213
|
+
return dict(d)
|
|
219
214
|
|
|
220
215
|
def get_selected_values(items: list[list[str]], selections: dict[int, bool]) -> list[list[str]]:
|
|
221
216
|
""" Return a list of rows based on wich are True in the selections dictionary. """
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
listpick/__init__.py,sha256=ExXc97-bibodH--wlwpQivl0zCNR5D1hvpvrf7OBofU,154
|
|
2
2
|
listpick/__main__.py,sha256=wkCjDdqw093W27yWwnlC3nG_sMRKaIad7hHHWy0RBgY,193
|
|
3
|
-
listpick/listpick_app.py,sha256=
|
|
3
|
+
listpick/listpick_app.py,sha256=IavN099zeGWv36ogNjbR20vHXtvRYR74-pZfqiSFHng,211476
|
|
4
4
|
listpick/pane/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
listpick/pane/get_data.py,sha256=l12mHIb6qoZWIfW5zZZY8K8EqNcyIcRiHgtRaM2CVGs,2735
|
|
6
6
|
listpick/pane/left_pane_functions.py,sha256=SVIW4Ef8uNPBQRk4hL67mEFL3pfgChSFZSMRz06CVzw,5543
|
|
@@ -13,8 +13,8 @@ listpick/ui/draw_screen.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
13
13
|
listpick/ui/footer.py,sha256=NcdH1uO_ma91m0qCczyQZ3zGrexfkiEnwDf5E4tHSMk,15089
|
|
14
14
|
listpick/ui/help_screen.py,sha256=zbfGIgb-IXtATpl4_Sx7nPbsnRXZ7eiMYlCKGS9EFmw,5608
|
|
15
15
|
listpick/ui/input_field.py,sha256=scJjvmSS0QqeDbCky7_0Zgt35Aki7gezRJkrQROlLg4,30034
|
|
16
|
-
listpick/ui/keys.py,sha256=
|
|
17
|
-
listpick/ui/picker_colours.py,sha256=
|
|
16
|
+
listpick/ui/keys.py,sha256=5wXx7K4zLsKw3yt_Q9_GCpgtcGMvQyChXrBhXtuc8-8,13852
|
|
17
|
+
listpick/ui/picker_colours.py,sha256=PYWtJEbBCsiM-Gbm83vJiFoIwfKuJuP4SGKxpozszKY,15159
|
|
18
18
|
listpick/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
listpick/utils/clipboard_operations.py,sha256=ORdNm2kgGbfs51xJSvgJPERgoSmBgT11axuMkvSoP9A,3133
|
|
20
20
|
listpick/utils/config.py,sha256=MEnAZg2Rhfl38XofEIN0uoVAOY7I0ftc79Evk3fOiVw,1654
|
|
@@ -32,10 +32,10 @@ listpick/utils/searching.py,sha256=Xk5UIqamNHL2L90z3ACB_Giqdpi9iRKoAJ6pKaqaD7Q,3
|
|
|
32
32
|
listpick/utils/sorting.py,sha256=WZZiVlVA3Zkcpwji3U5SNFlQ14zVEk3cZJtQirBkecQ,5329
|
|
33
33
|
listpick/utils/table_to_list_of_lists.py,sha256=XBj7eGBDF15BRME-swnoXyOfZWxXCxrXp0pzsBfcJ5g,12224
|
|
34
34
|
listpick/utils/user_input.py,sha256=L3ylI7nnuFM_TP1XKwpiKpxUSkNb2W5cr7mJjTmv_6E,4582
|
|
35
|
-
listpick/utils/utils.py,sha256=
|
|
36
|
-
listpick-0.1.16.
|
|
37
|
-
listpick-0.1.16.
|
|
38
|
-
listpick-0.1.16.
|
|
39
|
-
listpick-0.1.16.
|
|
40
|
-
listpick-0.1.16.
|
|
41
|
-
listpick-0.1.16.
|
|
35
|
+
listpick/utils/utils.py,sha256=_Z3pGDMPnEgPuhl_1BpEtXYi5tfWbuwRitj8Y5TeRUg,13653
|
|
36
|
+
listpick-0.1.16.11.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
|
|
37
|
+
listpick-0.1.16.11.dist-info/METADATA,sha256=WRPPZnTIxabHjUTi34ZG_CjXad6aDbMqoO-V_7z7eQk,8025
|
|
38
|
+
listpick-0.1.16.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
39
|
+
listpick-0.1.16.11.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
|
|
40
|
+
listpick-0.1.16.11.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
|
|
41
|
+
listpick-0.1.16.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|