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 CHANGED
@@ -20,7 +20,7 @@ import json
20
20
  import threading
21
21
  import string
22
22
  import logging
23
- from copy import copy
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
- self.logger.info(f"function: set_config()")
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
- """ Get config from file. """
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
- return self.stdscr.getmaxyx()
433
- w, h = os.get_terminal_size()
434
- return h, w
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
- # self.notification(self.stdscr, message=repr(self.logger))
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
- """ Initialise the variables that keep track of the data. """
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) >= self.cursor_pos and len(self.indexed_items[0][1]) >= self.id_column:
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
- ## Ensure that items is a List[List[Str]] object
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
- # Initial states
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
- if len(self.require_option) < len(self.items):
664
- self.require_option += [self.require_option_default for i in range(len(self.items)-len(self.require_option))]
665
- if len(self.option_functions) < len(self.items):
666
- self.option_functions += [self.default_option_function for i in range(len(self.items)-len(self.option_functions))]
667
- if len(self.items)>0 and len(self.columns_sort_method) < len(self.items[0]):
668
- self.columns_sort_method = self.columns_sort_method + [0 for i in range(len(self.items[0])-len(self.columns_sort_method))]
669
- if len(self.items)>0 and len(self.sort_reverse) < len(self.items[0]):
670
- self.sort_reverse = self.sort_reverse + [False for i in range(len(self.items[0])-len(self.sort_reverse))]
671
- if len(self.items)>0 and len(self.editable_columns) < len(self.items[0]):
672
- self.editable_columns = self.editable_columns + [self.editable_by_default for i in range(len(self.items[0])-len(self.editable_columns))]
673
- if len(self.items)>0 and len(self.column_indices) < len(self.items[0]):
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
- # items2 = [[row[self.column_indices[i]] for i in range(len(row))] for row in self.items]
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
- # If a filter is passed then refilter
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
- # If a sort is passed
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
- # Adjust variables to ensure correctness if errors
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
- assert new_pos < len(self.items)
714
- self.cursor_pos = new_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
- if len(self.loaded_file_states) < len(self.loaded_files):
725
- self.loaded_file_states += [{} for _ in range(len(self.loaded_files) - len(self.loaded_file_states))]
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
- # if tracking and len(self.items) > 1:
732
- # Ensure that selected indices are tracked upon data refresh
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
- self.right_pane_index = max(0, min(self.right_pane_index, len(self.right_panes)-1))
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
- # The height of the footer may need to be adjusted if the file changes.
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
- # top_space = self.top_gap
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.separator
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
- try:
960
- if self.selected_column != None and self.selected_column not in self.hidden_columns:
961
- # start of string is on screen
962
- col_width = self.column_widths[self.selected_column]
963
- number = f"{self.selected_column}. " if self.number_columns else ""
964
- col_str = self.header[self.selected_column][:self.column_widths[self.selected_column]-len(number)]
965
- highlighted_col_str = (number+f"{col_str:^{self.column_widths[self.selected_column]-len(number)}}") + self.separator
966
-
967
- if len(self.column_widths) == 1:
968
- colour = curses.color_pair(self.colours_start+28) | curses.A_BOLD
969
- else:
970
- colour = curses.color_pair(self.colours_start+19) | curses.A_BOLD
971
- # Start of selected column is on the screen
972
- 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):
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
- # Whole cell of the selected column is on the screen
976
- if len(up_to_selected_col)+col_width - self.leftmost_char < self.rows_w-self.left_gutter_width:
977
- disp_str = highlighted_col_str
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
- # Start of the cell is on the screen, but the end of the cell is not
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
- pass
1011
-
1012
- # elif self.leftmost_char:
1013
- # os.system(f"notify-send 'cell is to the right of the screen'")
1014
-
1015
- #
1016
- #
1017
- # if len(self.header) > 1 and (len(up_to_selected_col)-self.leftmost_char) < self.rows_w:
1018
- # number = f"{self.selected_column}. " if self.number_columns else ""
1019
- # # number = f"{intStringToExponentString(self.selected_column)}. " if self.number_columns else ""
1020
- # # self.startx + len(up_to_selected_col) - self.leftmost_char
1021
- # highlighed_col_startx = max(self.startx, self.startx + len(up_to_selected_col) - self.leftmost_char)
1022
- #
1023
- #
1024
- # col_str = self.header[self.selected_column][:self.column_widths[self.selected_column]-len(number)]
1025
- # highlighted_col_str = (number+f"{col_str:^{self.column_widths[self.selected_column]}}") + self.separator
1026
- # end_of_highlighted_col_str = self.rows_w-(highlighed_col_startx+len(highlighted_col_str)) if (highlighed_col_startx+len(highlighted_col_str)) > self.rows_w else len(highlighted_col_str)
1027
- # if (highlighed_col_startx+len(highlighted_col_str)) > self.rows_w:
1028
- # end_of_highlighted_col_str = self.rows_w-(highlighed_col_startx+len(highlighted_col_str))
1029
- # else:
1030
- # end_of_highlighted_col_str = len(highlighted_col_str)
1031
- #
1032
- # start_of_highlighted_col_str = max(self.leftmost_char - len(up_to_selected_col), 0)
1033
- # self.stdscr.addstr(header_ypos, highlighed_col_startx , highlighted_col_str[start_of_highlighted_col_str:end_of_highlighted_col_str][:self.column_widths[self.selected_column]+len(self.separator)], curses.color_pair(self.colours_start+19) | curses.A_BOLD)
1034
- except:
1035
- pass
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
- try:
1060
- # Start of cell is on screen
1061
- if self.startx <= cell_pos <= self.rows_w+self.startx:
1062
- s = "max" if cell_max_width <= cell_width else "norm"
1063
- self.stdscr.addstr(y, cell_pos, (' '*cell_width)[:cell_max_width], colour)
1064
- if self.centre_in_cols:
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
- pass
1092
- # if colour_pair_number == 5:
1093
- except:
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.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=self.rows_w, unicode_char_width=self.unicode_char_width)
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": [ord('e')],
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
@@ -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
- try:
210
- for tup in cell_selections.keys():
211
- if cell_selections[tup]:
212
- if tup[0] in d:
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
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.16.9
3
+ Version: 0.1.16.11
4
4
  Summary: Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.
5
5
  Home-page: https://github.com/grimandgreedy/listpick
6
6
  Author: Grim
@@ -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=t690QmA4_fJ-zi2PhmRrwjMUr2oKOvxEScdDner2mr0,212136
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=FlpdWqX1lTP7K6gqHn9jfH2MmuloigeOA7EMzYhaJV8,13845
17
- listpick/ui/picker_colours.py,sha256=FFsyny_q0mGO6u7B1n7anuReBtP7Jw6LrgX5ycN-MRM,13413
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=nsR6orCBQy3rTXrCweq8cV-RzRVU15v3J9NclPeAOJk,13741
36
- listpick-0.1.16.9.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
37
- listpick-0.1.16.9.dist-info/METADATA,sha256=S_kppW9DHyjThJf01PtkuKOA2EAQQVh3J31Oe8iE_Hc,8024
38
- listpick-0.1.16.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
- listpick-0.1.16.9.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
40
- listpick-0.1.16.9.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
41
- listpick-0.1.16.9.dist-info/RECORD,,
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,,