listpick 0.1.16.8__py3-none-any.whl → 0.1.16.9__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,8 +20,10 @@ import json
20
20
  import threading
21
21
  import string
22
22
  import logging
23
+ from copy import copy
23
24
 
24
25
  from listpick.pane.pane_utils import get_file_attributes
26
+ from listpick.pane.left_pane_functions import *
25
27
  from listpick.ui.picker_colours import get_colours, get_help_colours, get_notification_colours, get_theme_count, get_fallback_colours
26
28
  from listpick.utils.options_selectors import default_option_input, output_file_option_selector, default_option_selector
27
29
  from listpick.utils.table_to_list_of_lists import *
@@ -189,9 +191,17 @@ class Picker:
189
191
  right_panes: list = [],
190
192
  right_pane_index: int = 0,
191
193
 
194
+ split_left: bool = False,
195
+ left_panes: list = [],
196
+ left_pane_index: int = 0,
197
+
198
+ screen_size_function = lambda stdscr: os.get_terminal_size()[::-1],
199
+
192
200
  # getting_data: threading.Event = threading.Event(),
193
201
 
194
202
  ):
203
+
204
+ self.screen_size_function = screen_size_function
195
205
  self.stdscr = stdscr
196
206
  self.items = items
197
207
  self.cursor_pos = cursor_pos
@@ -342,6 +352,11 @@ class Picker:
342
352
  self.split_right = split_right
343
353
  self.right_panes = right_panes
344
354
  self.right_pane_index = right_pane_index
355
+
356
+ self.split_left = split_left
357
+ self.left_panes = left_panes
358
+ self.left_pane_index = left_pane_index
359
+
345
360
  self.visible_rows_indices = []
346
361
 
347
362
 
@@ -408,7 +423,10 @@ class Picker:
408
423
  return config
409
424
 
410
425
  def update_term_size(self):
411
- self.term_h, self.term_w = self.stdscr.getmaxyx()
426
+ self.term_h, self.term_w = self.screen_size_function(self.stdscr)
427
+ # self.term_h, self.term_w = self.stdscr.getmaxyx()
428
+ # self.term_w, self.term_h = os.get_terminal_size()
429
+
412
430
 
413
431
  def get_term_size(self):
414
432
  return self.stdscr.getmaxyx()
@@ -428,13 +446,35 @@ class Picker:
428
446
  # self.bottom_space
429
447
  self.bottom_space = self.footer.height if self.show_footer else 0
430
448
 
449
+ # self.left_gutter_width
450
+ self.left_gutter_width = 1 if self.highlight_full_row else 2
451
+ if self.show_row_header: self.left_gutter_width += len(str(len(self.items))) + 2
452
+
453
+
431
454
  ## self.top_space
432
455
  self.update_term_size()
456
+ self.rows_w, self.rows_h = self.term_w, self.term_h
457
+ self.rows_box_x_i = 0
458
+ self.rows_box_x_f = self.term_w
459
+ self.left_pane_width = self.right_pane_width = 0
433
460
  if self.split_right and len(self.right_panes):
434
461
  proportion = self.right_panes[self.right_pane_index]["proportion"]
435
- self.rows_w, self.rows_h = int(self.term_w*proportion), self.term_h
436
- else:
437
- self.rows_w, self.rows_h = self.term_w, self.term_h
462
+ self.right_pane_width = int(self.term_w*proportion)
463
+ self.rows_w -= self.right_pane_width
464
+ self.rows_box_x_f -= self.right_pane_width
465
+ if self.split_left and len(self.left_panes):
466
+ proportion = self.left_panes[self.left_pane_index]["proportion"]
467
+ self.left_pane_width = int(self.term_w*proportion)
468
+ self.rows_w -= self.left_pane_width
469
+ self.rows_box_x_i += self.left_pane_width
470
+ if self.left_pane_width + self.right_pane_width >= self.term_w-3:
471
+ self.rows_w += 10
472
+ self.left_pane_width -= 5
473
+ self.right_pane_width -= 5
474
+ self.rows_box_x_i -= 5
475
+ self.rows_box_x_f += 5
476
+
477
+
438
478
 
439
479
  self.top_space = self.top_gap
440
480
  if self.title: self.top_space+=1
@@ -460,6 +500,14 @@ class Picker:
460
500
  if self.show_row_header: self.startx += len(str(len(self.items))) + 2
461
501
  if visible_columns_total_width < self.rows_w and self.centre_in_terminal:
462
502
  self.startx += (self.rows_w - visible_columns_total_width) // 2
503
+ self.startx += self.left_pane_width
504
+ # if self.split_left and len(self.left_panes):
505
+ # proportion = self.left_panes[self.left_pane_index]["proportion"]
506
+ # self.startx += int(self.term_w*proportion)
507
+
508
+ self.endx = self.startx+self.rows_w
509
+
510
+
463
511
 
464
512
  def get_visible_rows(self) -> list[list[str]]:
465
513
 
@@ -800,7 +848,8 @@ class Picker:
800
848
  self.draw_screen_(clear)
801
849
  except Exception as e:
802
850
  self.logger.warning(f"self.draw_screen_() error. {e}")
803
- pass
851
+ finally:
852
+ self.stdscr.refresh()
804
853
 
805
854
  def draw_screen_(self, clear: bool = True) -> None:
806
855
  """ Draw Picker screen. """
@@ -811,11 +860,11 @@ class Picker:
811
860
  self.stdscr.erase()
812
861
 
813
862
  self.update_term_size()
814
- if self.split_right and len(self.right_panes):
815
- proportion = self.right_panes[self.right_pane_index]["proportion"]
816
- self.rows_w, self.rows_h = int(self.term_w*proportion), self.term_h
817
- else:
818
- self.rows_w, self.rows_h = self.term_w, self.term_h
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
819
868
 
820
869
  # The height of the footer may need to be adjusted if the file changes.
821
870
  self.footer.adjust_sizes(self.term_h,self.term_w)
@@ -898,11 +947,13 @@ class Picker:
898
947
 
899
948
  header_str += f"{col_str:^{self.column_widths[i]-len(number)}}"
900
949
  header_str += self.separator
950
+ header_str_w = min(self.rows_w-self.left_gutter_width, visible_columns_total_width+1, self.term_w-self.startx)
901
951
 
902
952
  header_str = header_str[self.leftmost_char:]
953
+ header_str = header_str[:header_str_w]
903
954
  header_ypos = self.top_gap + bool(self.title) + bool(self.display_modes and self.modes)
904
- self.stdscr.addstr(header_ypos, 0, ' '*self.rows_w, curses.color_pair(self.colours_start+28) | curses.A_BOLD)
905
- self.stdscr.addstr(header_ypos, self.startx, header_str[:min(self.rows_w-self.startx, visible_columns_total_width+1)], curses.color_pair(self.colours_start+4) | curses.A_BOLD)
955
+ self.stdscr.addstr(header_ypos, self.rows_box_x_i, ' '*self.rows_w, curses.color_pair(self.colours_start+28) | curses.A_BOLD)
956
+ self.stdscr.addstr(header_ypos, self.startx, header_str, curses.color_pair(self.colours_start+4) | curses.A_BOLD)
906
957
 
907
958
  # Highlight sort column
908
959
  try:
@@ -918,17 +969,19 @@ class Picker:
918
969
  else:
919
970
  colour = curses.color_pair(self.colours_start+19) | curses.A_BOLD
920
971
  # Start of selected column is on the screen
921
- if self.leftmost_char <= len(up_to_selected_col) and self.leftmost_char+self.rows_w-self.startx > len(up_to_selected_col):
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):
922
973
  x_pos = len(up_to_selected_col) - self.leftmost_char + self.startx
923
974
 
924
975
  # Whole cell of the selected column is on the screen
925
- if len(up_to_selected_col)+col_width - self.leftmost_char < self.rows_w-self.startx:
976
+ if len(up_to_selected_col)+col_width - self.leftmost_char < self.rows_w-self.left_gutter_width:
926
977
  disp_str = highlighted_col_str
927
978
 
928
979
  # Start of the cell is on the screen, but the end of the cell is not
929
980
  else:
930
- overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w - self.startx)
981
+ overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w - self.left_gutter_width)
931
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)
932
985
 
933
986
  self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
934
987
  # Start of the cell is to the right of the screen
@@ -939,14 +992,18 @@ class Picker:
939
992
  x_pos = self.startx
940
993
  beg = self.leftmost_char - len(up_to_selected_col)
941
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)
942
997
  self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
943
998
  # The middle of the cell is on the screen, the start and end of the cell are not
944
999
  elif self.leftmost_char <= len(up_to_selected_col) + col_width//2 <= self.leftmost_char+self.rows_w:
945
1000
  beg = self.leftmost_char - len(up_to_selected_col)
946
1001
  overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w)
1002
+ x_pos = self.startx
947
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)
948
1006
 
949
- x_pos = self.startx
950
1007
  self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
951
1008
  # The cell is to the left of the screen
952
1009
  else:
@@ -982,17 +1039,18 @@ class Picker:
982
1039
  for idx in range(start_index, end_index):
983
1040
  y = idx - start_index + self.top_space
984
1041
  if idx == self.cursor_pos:
985
- self.stdscr.addstr(y, 0, f" {self.indexed_items[idx][0]} ", curses.color_pair(self.colours_start+19) | curses.A_BOLD)
1042
+ self.stdscr.addstr(y, self.startx-self.left_gutter_width, f" {self.indexed_items[idx][0]} ", curses.color_pair(self.colours_start+19) | curses.A_BOLD)
986
1043
  else:
987
- self.stdscr.addstr(y, 0, f" {self.indexed_items[idx][0]} ", curses.color_pair(self.colours_start+4) | curses.A_BOLD)
1044
+ self.stdscr.addstr(y, self.startx-self.left_gutter_width, f" {self.indexed_items[idx][0]} ", curses.color_pair(self.colours_start+4) | curses.A_BOLD)
988
1045
 
989
1046
 
990
1047
  def highlight_cell(row: int, col:int, visible_column_widths, colour_pair_number: int = 5, bold: bool = False, y:int = 0):
991
1048
 
992
1049
  cell_pos = sum(visible_column_widths[:col])+col*len(self.separator)-self.leftmost_char + self.startx
1050
+ cell_pos_relative = sum(visible_column_widths[:col])+col*len(self.separator)-self.leftmost_char + self.left_gutter_width
993
1051
  # cell_width = self.column_widths[self.selected_column]
994
1052
  cell_width = visible_column_widths[col] + len(self.separator)
995
- cell_max_width = self.rows_w-cell_pos
1053
+ cell_max_width = min(self.rows_w-self.left_gutter_width, self.term_w-cell_pos)
996
1054
 
997
1055
  if bold:
998
1056
  colour = curses.color_pair(self.colours_start+colour_pair_number) | curses.A_BOLD
@@ -1000,7 +1058,8 @@ class Picker:
1000
1058
  colour = curses.color_pair(self.colours_start+colour_pair_number)
1001
1059
  try:
1002
1060
  # Start of cell is on screen
1003
- if self.startx <= cell_pos <= self.rows_w:
1061
+ if self.startx <= cell_pos <= self.rows_w+self.startx:
1062
+ s = "max" if cell_max_width <= cell_width else "norm"
1004
1063
  self.stdscr.addstr(y, cell_pos, (' '*cell_width)[:cell_max_width], colour)
1005
1064
  if self.centre_in_cols:
1006
1065
  cell_value = f"{self.indexed_items[row][1][col]:^{cell_width-len(self.separator)}}" + self.separator
@@ -1011,15 +1070,22 @@ class Picker:
1011
1070
  # cell_value = cell_value + self.separator
1012
1071
  # cell_value = cell_value
1013
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
+
1014
1076
  self.stdscr.addstr(y, cell_pos, cell_value, colour)
1015
1077
  # Part of the cell is on screen
1016
- elif self.startx <= cell_pos+cell_width and cell_pos < (self.rows_w):
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"
1017
1080
  cell_start = self.startx - cell_pos
1018
1081
  # self.stdscr.addstr(y, self.startx, ' '*(cell_width-cell_start), curses.color_pair(self.colours_start+colour_pair_number))
1019
1082
  cell_value = self.indexed_items[row][1][col]
1020
1083
  cell_value = f"{cell_value:^{self.column_widths[col]}}"
1021
1084
 
1022
- cell_value = cell_value[cell_start:visible_column_widths[col]][:self.rows_w-self.startx]
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)
1023
1089
  self.stdscr.addstr(y, self.startx, cell_value, colour)
1024
1090
  else:
1025
1091
  pass
@@ -1098,6 +1164,7 @@ class Picker:
1098
1164
  l0_highlights, l1_highlights, l2_highlights = sort_highlights(self.highlights)
1099
1165
 
1100
1166
 
1167
+ row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
1101
1168
  for idx in range(start_index, end_index):
1102
1169
  item = self.indexed_items[idx]
1103
1170
  y = idx - start_index + self.top_space
@@ -1109,18 +1176,21 @@ class Picker:
1109
1176
  # rowstr off screen
1110
1177
  # if self.leftmost_char > len(row_str_orig):
1111
1178
  # trunc_width = 0
1112
- if self.leftmost_char + (self.rows_w-self.startx) <= len(row_str_orig):
1113
- trunc_width = self.rows_w-self.startx
1114
- elif self.leftmost_char <= len(row_str_orig):
1115
- trunc_width = len(row_str_orig) - self.leftmost_char
1116
- else:
1117
- trunc_width = 0
1179
+ # if self.leftmost_char + (self.rows_w-self.left_gutter_width) <= len(row_str_orig):
1180
+ # trunc_width = self.rows_w-self.startx
1181
+ # elif self.leftmost_char <= len(row_str_orig):
1182
+ # trunc_width = len(row_str_orig) - self.leftmost_char
1183
+ # else:
1184
+ # trunc_width = 0
1185
+
1186
+
1187
+ trunc_width = min(self.rows_w-self.left_gutter_width, row_width, self.term_w - self.startx)
1118
1188
 
1119
1189
  row_str = truncate_to_display_width(row_str_left_adj, trunc_width, self.unicode_char_width)
1120
1190
  # row_str = truncate_to_display_width(row_str, min(w-self.startx, visible_columns_total_width))[self.leftmost_char:]
1121
1191
 
1122
1192
  ## Display the standard row
1123
- self.stdscr.addstr(y, self.startx, row_str[:min(self.rows_w-self.startx, visible_columns_total_width)], curses.color_pair(self.colours_start+2))
1193
+ self.stdscr.addstr(y, self.startx, row_str, curses.color_pair(self.colours_start+2))
1124
1194
 
1125
1195
 
1126
1196
  ## Highlight column
@@ -1157,7 +1227,7 @@ class Picker:
1157
1227
  # Higlight cursor row and selected rows
1158
1228
  elif self.highlight_full_row:
1159
1229
  if self.selections[item[0]]:
1160
- self.stdscr.addstr(y, self.startx, row_str[:min(self.rows_w-self.startx, visible_columns_total_width)], curses.color_pair(self.colours_start+25) | curses.A_BOLD)
1230
+ self.stdscr.addstr(y, self.startx, row_str[:min(self.rows_w-self.left_gutter_width, visible_columns_total_width)], curses.color_pair(self.colours_start+25) | curses.A_BOLD)
1161
1231
 
1162
1232
  # Visually selected
1163
1233
  if self.is_selecting:
@@ -1191,7 +1261,7 @@ class Picker:
1191
1261
  if self.cell_cursor:
1192
1262
  highlight_cell(idx, self.selected_column, visible_column_widths, colour_pair_number=5, bold=True, y=y)
1193
1263
  else:
1194
- self.stdscr.addstr(y, self.startx, row_str[:min(self.rows_w-self.startx, visible_columns_total_width)], curses.color_pair(self.colours_start+5) | curses.A_BOLD)
1264
+ self.stdscr.addstr(y, self.startx, row_str[:self.rows_w-self.left_gutter_width], curses.color_pair(self.colours_start+5) | curses.A_BOLD)
1195
1265
 
1196
1266
  if not self.highlights_hide:
1197
1267
  draw_highlights(l2_highlights, idx, y, item)
@@ -1211,7 +1281,8 @@ class Picker:
1211
1281
  scroll_bar_length = max(1, scroll_bar_length)
1212
1282
  for i in range(scroll_bar_length):
1213
1283
  v = max(self.top_space+int(bool(self.header)), scroll_bar_start-scroll_bar_length//2)
1214
- self.stdscr.addstr(scroll_bar_start+i, self.rows_w-1, ' ', curses.color_pair(self.colours_start+18))
1284
+ # self.stdscr.addstr(scroll_bar_start+i, self.startx+self.rows_w-self.left_gutter_width-2, ' ', curses.color_pair(self.colours_start+18))
1285
+ self.stdscr.addstr(scroll_bar_start+i, self.rows_box_x_f-1, ' ', curses.color_pair(self.colours_start+18))
1215
1286
 
1216
1287
  # Display refresh symbol
1217
1288
  if self.auto_refresh:
@@ -1240,20 +1311,46 @@ class Picker:
1240
1311
 
1241
1312
  if self.split_right and len(self.right_panes):
1242
1313
  # If we need to refresh the data then do so.
1243
- if self.right_panes[self.right_pane_index]["auto_refresh"] and ((time.time() - self.initial_split_time) > self.right_panes[self.right_pane_index]["refresh_time"]):
1244
- get_data = self.right_panes[self.right_pane_index]["get_data"]
1245
- data = self.right_panes[self.right_pane_index]["data"]
1246
- self.right_panes[self.right_pane_index]["data"] = get_data(data, self.get_function_data())
1247
- self.initial_split_time = time.time()
1314
+ pane = self.right_panes[self.right_pane_index]
1315
+ if pane["auto_refresh"] and ((time.time() - self.initial_right_split_time) > pane["refresh_time"]):
1316
+ get_data = pane["get_data"]
1317
+ data = pane["data"]
1318
+ pane["data"] = get_data(data, self.get_function_data())
1319
+ self.initial_right_split_time = time.time()
1248
1320
 
1249
- draw_pane = self.right_panes[self.right_pane_index]["display"]
1250
- data = self.right_panes[self.right_pane_index]["data"]
1321
+ draw_pane = pane["display"]
1322
+ data = pane["data"]
1323
+ # pane_width = int(pane["proportion"]*self.term_w)
1251
1324
 
1252
1325
  draw_pane(
1253
1326
  self.stdscr,
1254
- x = self.rows_w,
1327
+ x = self.rows_w + self.startx - self.left_gutter_width,
1255
1328
  y = self.top_space - int(bool(self.show_header and self.header)),
1256
- w = self.term_w-self.rows_w,
1329
+ w = self.right_pane_width,
1330
+ h = self.items_per_page + int(bool(self.show_header and self.header)),
1331
+ state = self.get_function_data(),
1332
+ row = self.indexed_items[self.cursor_pos] if self.indexed_items else [],
1333
+ cell = self.indexed_items[self.cursor_pos][1][self.selected_column] if self.indexed_items else "",
1334
+ data=data,
1335
+ )
1336
+ if self.split_left and len(self.left_panes):
1337
+ # If we need to refresh the data then do so.
1338
+ pane = self.left_panes[self.left_pane_index]
1339
+ if pane["auto_refresh"] and ((time.time() - self.initial_left_split_time) > pane["refresh_time"]):
1340
+ get_data = pane["get_data"]
1341
+ data = pane["data"]
1342
+ pane["data"] = get_data(data, self.get_function_data())
1343
+ self.initial_left_split_time = time.time()
1344
+
1345
+ draw_pane = pane["display"]
1346
+ data = pane["data"]
1347
+ # pane_width = int(pane["proportion"]*self.term_w)
1348
+
1349
+ draw_pane(
1350
+ self.stdscr,
1351
+ x = 0,
1352
+ y = self.top_space - int(bool(self.show_header and self.header)),
1353
+ w = self.left_pane_width,
1257
1354
  h = self.items_per_page + int(bool(self.show_header and self.header)),
1258
1355
  state = self.get_function_data(),
1259
1356
  row = self.indexed_items[self.cursor_pos] if self.indexed_items else [],
@@ -1261,7 +1358,6 @@ class Picker:
1261
1358
  data=data,
1262
1359
  )
1263
1360
 
1264
- self.stdscr.refresh()
1265
1361
  ## Display infobox
1266
1362
  if self.display_infobox:
1267
1363
  self.infobox(self.stdscr, message=self.infobox_items, title=self.infobox_title)
@@ -1309,6 +1405,7 @@ class Picker:
1309
1405
  "reset_colours": False,
1310
1406
  "cell_cursor": False,
1311
1407
  "split_right": False,
1408
+ "split_left": False,
1312
1409
  "crosshair_cursor": False,
1313
1410
  }
1314
1411
 
@@ -1427,6 +1524,9 @@ class Picker:
1427
1524
  "split_right": self.split_right,
1428
1525
  "right_panes": self.right_panes,
1429
1526
  "right_pane_index": self.right_pane_index,
1527
+ "split_left": self.split_left,
1528
+ "left_panes": self.left_panes,
1529
+ "left_pane_index": self.left_pane_index,
1430
1530
  "crosshair_cursor": self.crosshair_cursor,
1431
1531
 
1432
1532
  }
@@ -1464,6 +1564,9 @@ class Picker:
1464
1564
  "centre_in_cols",
1465
1565
  "centre_in_terminal",
1466
1566
  "split_right",
1567
+ "left_pane_index",
1568
+ "split_left",
1569
+ "left_pane_index",
1467
1570
  ]
1468
1571
 
1469
1572
  for var in variables:
@@ -1557,6 +1660,7 @@ class Picker:
1557
1660
  "number_columns": False,
1558
1661
  "reset_colours": False,
1559
1662
  "split_right": False,
1663
+ "split_left": False,
1560
1664
  "cell_cursor": False,
1561
1665
  "crosshair_cursor": False,
1562
1666
  }
@@ -1569,6 +1673,7 @@ class Picker:
1569
1673
 
1570
1674
  submenu_win = curses.newwin(window_height, window_width, (self.term_h-window_height)//2, (self.term_w-window_width)//2)
1571
1675
  submenu_win.keypad(True)
1676
+ option_picker_data["screen_size_function"] = lambda stdscr: (window_height, window_width)
1572
1677
  OptionPicker = Picker(submenu_win, **option_picker_data)
1573
1678
  s, o, f = OptionPicker.run()
1574
1679
 
@@ -1617,9 +1722,11 @@ class Picker:
1617
1722
  "cancel_is_back": True,
1618
1723
  "reset_colours": False,
1619
1724
  "split_right": False,
1725
+ "split_left": False,
1620
1726
  "cell_cursor": False,
1621
1727
  "crosshair_cursor": False,
1622
1728
  "show_header": False,
1729
+ "screen_size_function": lambda stdscr: (notification_height, notification_width),
1623
1730
 
1624
1731
  }
1625
1732
  OptionPicker = Picker(submenu_win, **notification_data)
@@ -1757,14 +1864,24 @@ class Picker:
1757
1864
  self.footer_style = (self.footer_style+1)%len(self.footer_options)
1758
1865
  self.footer = self.footer_options[self.footer_style]
1759
1866
  self.initialise_variables()
1760
- elif setting == "pane":
1867
+ elif setting == "rpane":
1761
1868
  self.toggle_right_pane()
1762
1869
 
1763
- elif setting == "pane_cycle":
1870
+ elif setting == "rpane_cycle":
1764
1871
  self.cycle_right_pane()
1765
1872
 
1873
+ elif setting == "lpane":
1874
+ self.toggle_left_pane()
1875
+
1876
+ elif setting == "lpane_cycle":
1877
+ self.cycle_left_pane()
1878
+
1766
1879
  elif setting.startswith("cwd="):
1767
1880
  os.chdir(os.path.expandvars(os.path.expanduser(setting[len("cwd="):])))
1881
+ elif setting.startswith("lmc="):
1882
+ rem = setting[4:]
1883
+ if rem.isnumeric():
1884
+ self.leftmost_char = int(rem)
1768
1885
  elif setting.startswith("hl"):
1769
1886
  hl_list = setting.split(",")
1770
1887
  if len(hl_list) > 1:
@@ -2389,11 +2506,22 @@ class Picker:
2389
2506
  if self.right_panes[self.right_pane_index]["data"] in [[], None, {}]:
2390
2507
  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())
2391
2508
 
2509
+ def toggle_left_pane(self):
2510
+ if len(self.left_panes):
2511
+ self.split_left = not self.split_left
2512
+ if self.left_panes[self.left_pane_index]["data"] in [[], None, {}]:
2513
+ 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())
2514
+
2392
2515
 
2393
2516
  def cycle_right_pane(self, increment=1):
2394
2517
  if len(self.right_panes) > 1:
2395
2518
  self.right_pane_index = (self.right_pane_index+1)%len(self.right_panes)
2396
- self.initial_split_time = self.initial_split_time - self.right_panes[self.right_pane_index]["refresh_time"]
2519
+ self.initial_right_split_time -= self.right_panes[self.right_pane_index]["refresh_time"]
2520
+
2521
+ def cycle_left_pane(self, increment=1):
2522
+ if len(self.left_panes) > 1:
2523
+ self.left_pane_index = (self.left_pane_index+1)%len(self.left_panes)
2524
+ self.initial_left_split_time -= self.left_panes[self.left_pane_index]["refresh_time"]
2397
2525
 
2398
2526
  def run(self) -> Tuple[list[int], str, dict]:
2399
2527
  """ Run the picker. """
@@ -2411,7 +2539,8 @@ class Picker:
2411
2539
 
2412
2540
  self.initial_time = time.time()
2413
2541
  self.initial_time_footer = time.time()-self.footer_timer
2414
- self.initial_split_time = time.time()-200
2542
+ self.initial_right_split_time = time.time()-200
2543
+ self.initial_left_split_time = time.time()-200
2415
2544
 
2416
2545
  if self.startup_notification:
2417
2546
  self.notification(self.stdscr, message=self.startup_notification)
@@ -2446,11 +2575,7 @@ class Picker:
2446
2575
  tty_fd, self.saved_terminal_state = open_tty()
2447
2576
 
2448
2577
  self.update_term_size()
2449
- if self.split_right and len(self.right_panes):
2450
- proportion = self.right_panes[self.right_pane_index]["proportion"]
2451
- self.rows_w, self.rows_h = int(self.term_w*proportion), self.term_h
2452
- else:
2453
- self.rows_w, self.rows_h = self.term_w, self.term_h
2578
+ self.calculate_section_sizes()
2454
2579
 
2455
2580
  def terminal_resized(old_w, old_h) -> bool:
2456
2581
  w, h = os.get_terminal_size()
@@ -2486,14 +2611,6 @@ class Picker:
2486
2611
  if self.term_resize_event:
2487
2612
  key = curses.KEY_RESIZE
2488
2613
 
2489
- self.update_term_size()
2490
-
2491
- if self.split_right and len(self.right_panes):
2492
- proportion = self.right_panes[self.right_pane_index]["proportion"]
2493
- self.rows_w, self.rows_h = int(self.term_w*proportion), self.term_h
2494
- else:
2495
- self.rows_w, self.rows_h = self.term_w, self.term_h
2496
-
2497
2614
  if key in self.disabled_keys: continue
2498
2615
  clear_screen=True
2499
2616
 
@@ -2545,11 +2662,17 @@ class Picker:
2545
2662
  self.initial_time_footer = time.time()
2546
2663
  self.draw_screen()
2547
2664
 
2548
- if self.split_right and len(self.right_panes) and self.right_panes[self.right_pane_index]["auto_refresh"] and ((time.time() - self.initial_split_time) > self.right_panes[self.right_pane_index]["refresh_time"]):
2665
+ if self.split_right and len(self.right_panes) and self.right_panes[self.right_pane_index]["auto_refresh"] and ((time.time() - self.initial_right_split_time) > self.right_panes[self.right_pane_index]["refresh_time"]):
2549
2666
  get_data = self.right_panes[self.right_pane_index]["get_data"]
2550
2667
  data = self.right_panes[self.right_pane_index]["data"]
2551
2668
  self.right_panes[self.right_pane_index]["data"] = get_data(data, self.get_function_data())
2552
- self.initial_split_time = time.time()
2669
+ self.initial_right_split_time = time.time()
2670
+
2671
+ if self.split_left and len(self.left_panes) and self.left_panes[self.left_pane_index]["auto_refresh"] and ((time.time() - self.initial_left_split_time) > self.left_panes[self.left_pane_index]["refresh_time"]):
2672
+ get_data = self.left_panes[self.left_pane_index]["get_data"]
2673
+ data = self.left_panes[self.left_pane_index]["data"]
2674
+ self.left_panes[self.right_pane_index]["data"] = get_data(data, self.get_function_data())
2675
+ self.initial_left_split_time = time.time()
2553
2676
 
2554
2677
  if self.check_key("help", key, self.keys_dict):
2555
2678
  self.logger.info(f"key_function help")
@@ -3006,7 +3129,7 @@ class Picker:
3006
3129
  column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
3007
3130
  start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
3008
3131
  end_of_cell = sum(visible_column_widths[:self.selected_column+1])+len(self.separator)*(self.selected_column+1)
3009
- display_width = self.rows_w-self.startx
3132
+ display_width = self.rows_w-self.left_gutter_width
3010
3133
  # If the full column is within the current display then don't do anything
3011
3134
  if start_of_cell >= self.leftmost_char and end_of_cell <= self.leftmost_char + display_width:
3012
3135
  pass
@@ -3033,7 +3156,7 @@ class Picker:
3033
3156
  column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
3034
3157
  start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
3035
3158
  end_of_cell = sum(visible_column_widths[:self.selected_column+1])+len(self.separator)*(self.selected_column+1)
3036
- display_width = self.rows_w-self.startx
3159
+ display_width = self.rows_w-self.left_gutter_width
3037
3160
 
3038
3161
  # If the entire column is within the current display then don't do anything
3039
3162
  if start_of_cell >= self.leftmost_char and end_of_cell <= self.leftmost_char + display_width:
@@ -3048,9 +3171,9 @@ class Picker:
3048
3171
  self.logger.info(f"key_function scroll_right")
3049
3172
  if len(self.indexed_items):
3050
3173
  row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
3051
- if row_width-self.leftmost_char >= self.rows_w-self.startx-5:
3174
+ if row_width-self.leftmost_char >= self.rows_w-5:
3052
3175
  self.leftmost_char += 5
3053
- self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w - self.startx) + 5)
3176
+ self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w) + self.left_gutter_width+5)
3054
3177
  if sum(self.column_widths) + len(self.column_widths)*len(self.separator) < self.rows_w:
3055
3178
  self.leftmost_char = 0
3056
3179
 
@@ -3058,9 +3181,9 @@ class Picker:
3058
3181
  self.logger.info(f"key_function scroll_right")
3059
3182
  if len(self.indexed_items):
3060
3183
  row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
3061
- if row_width-self.leftmost_char >= self.rows_w-self.startx-25:
3184
+ if row_width-self.leftmost_char+5 >= self.rows_w-25:
3062
3185
  self.leftmost_char += 25
3063
- self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w - self.startx) + 5)
3186
+ self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w) + self.left_gutter_width+5)
3064
3187
  if sum(self.column_widths) + len(self.column_widths)*len(self.separator) < self.rows_w:
3065
3188
  self.leftmost_char = 0
3066
3189
 
@@ -3091,7 +3214,16 @@ class Picker:
3091
3214
  # row_str = format_row(item[1], self.hidden_columns, self.column_widths, self.separator, self.centre_in_cols)
3092
3215
  # if len(row_str) > longest_row_str_len: longest_row_str_len=len(row_str)
3093
3216
  # self.notification(self.stdscr, f"{longest_row_str_len}")
3094
- self.leftmost_char = max(0, longest_row_str_len-self.rows_w+2+self.startx+5)
3217
+ if len(self.indexed_items):
3218
+ row_width = sum(self.column_widths) + len(self.separator)*(len(self.column_widths)-1)
3219
+ self.leftmost_char = row_width - (self.rows_w) + self.left_gutter_width+5
3220
+ self.leftmost_char = min(self.leftmost_char, row_width - (self.rows_w) + self.left_gutter_width+5)
3221
+
3222
+ longest_row_str_len = sum(self.column_widths) + (len(self.column_widths)-1)*len(self.separator)
3223
+ # self.leftmost_char = max(0, longest_row_str_len-self.rows_w+2+5)
3224
+
3225
+
3226
+
3095
3227
  if len(self.items):
3096
3228
  self.selected_column = len(self.items[0])-1
3097
3229
 
@@ -3186,6 +3318,13 @@ class Picker:
3186
3318
 
3187
3319
  self.calculate_section_sizes()
3188
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)
3321
+
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
3189
3328
  self.stdscr.clear()
3190
3329
  self.stdscr.refresh()
3191
3330
  self.draw_screen()
@@ -3442,6 +3581,12 @@ class Picker:
3442
3581
  elif self.check_key("cycle_right_pane", key, self.keys_dict):
3443
3582
  self.cycle_right_pane()
3444
3583
 
3584
+ elif self.check_key("toggle_left_pane", key, self.keys_dict):
3585
+ self.toggle_left_pane()
3586
+
3587
+ elif self.check_key("cycle_left_pane", key, self.keys_dict):
3588
+ self.cycle_left_pane()
3589
+
3445
3590
  elif self.check_key("pipe_input", key, self.keys_dict):
3446
3591
  self.logger.info(f"key_function pipe_input")
3447
3592
  # usrtxt = "xargs -d '\n' -I{} "
@@ -4003,7 +4148,7 @@ def main() -> None:
4003
4148
  # function_data["cell_cursor"] = True
4004
4149
  # function_data["display_modes"] = True
4005
4150
  # function_data["centre_in_cols"] = True
4006
- # function_data["show_row_header"] = True
4151
+ function_data["show_row_header"] = True
4007
4152
  # function_data["keys_dict"] = picker_keys
4008
4153
  # function_data["id_column"] = -1
4009
4154
  # function_data["track_entries_upon_refresh"] = True
@@ -4019,10 +4164,23 @@ def main() -> None:
4019
4164
  # function_data["debug"] = True
4020
4165
  # function_data["debug_level"] = 1
4021
4166
 
4167
+ function_data["cell_cursor"] = False
4168
+
4022
4169
  function_data["split_right"] = False
4170
+ function_data["split_left"] = False
4023
4171
  function_data["right_pane_index"] = 2
4172
+ function_data["left_pane_index"] = 0
4024
4173
 
4025
4174
  function_data["right_panes"] = [
4175
+ # Nopane
4176
+ {
4177
+ "proportion": 1/3,
4178
+ "auto_refresh": False,
4179
+ "get_data": lambda data, state: [],
4180
+ "display": left_start_pane,
4181
+ "data": ["Files", []],
4182
+ "refresh_time": 1,
4183
+ },
4026
4184
  # Graph or random numbers generated each second
4027
4185
  {
4028
4186
  "proportion": 1/2,
@@ -4034,7 +4192,7 @@ def main() -> None:
4034
4192
  },
4035
4193
  # list of numbers
4036
4194
  {
4037
- "proportion": 2/3,
4195
+ "proportion": 1/3,
4038
4196
  "auto_refresh": False,
4039
4197
  "get_data": data_refresh_randint_title,
4040
4198
  "display": right_split_display_list,
@@ -4043,7 +4201,7 @@ def main() -> None:
4043
4201
  },
4044
4202
  # File attributes
4045
4203
  {
4046
- "proportion": 2/3,
4204
+ "proportion": 1/3,
4047
4205
  "auto_refresh": False,
4048
4206
  "get_data": lambda data, state: [],
4049
4207
  "display": right_split_file_attributes,
@@ -4052,7 +4210,7 @@ def main() -> None:
4052
4210
  },
4053
4211
  # File attributes dynamic
4054
4212
  {
4055
- "proportion": 2/3,
4213
+ "proportion": 1/3,
4056
4214
  "auto_refresh": True,
4057
4215
  "get_data": update_file_attributes,
4058
4216
  "display": right_split_file_attributes_dynamic,
@@ -4078,7 +4236,72 @@ def main() -> None:
4078
4236
  "refresh_time": 1,
4079
4237
  },
4080
4238
  ]
4081
- function_data["require_option"] = [True for _ in function_data["items"]]
4239
+ function_data["left_panes"] = [
4240
+ # Nopane
4241
+ {
4242
+ "proportion": 1/3,
4243
+ "auto_refresh": False,
4244
+ "get_data": lambda data, state: [],
4245
+ "display": left_start_pane,
4246
+ "data": ["Files", []],
4247
+ "refresh_time": 1,
4248
+ },
4249
+ # Graph or random numbers generated each second
4250
+ {
4251
+ "proportion": 1/2,
4252
+ "auto_refresh": True,
4253
+ "get_data": data_refresh_randint,
4254
+ "display": left_split_graph,
4255
+ "data": [],
4256
+ "refresh_time": 1.0,
4257
+ },
4258
+ # list of numbers
4259
+ {
4260
+ "proportion": 1/3,
4261
+ "auto_refresh": False,
4262
+ "get_data": data_refresh_randint_title,
4263
+ "display": left_split_display_list,
4264
+ "data": ["Files", [str(x) for x in range(100)]],
4265
+ "refresh_time": 1.0,
4266
+ },
4267
+ # File attributes
4268
+ {
4269
+ "proportion": 1/3,
4270
+ "auto_refresh": False,
4271
+ "get_data": lambda data, state: [],
4272
+ "display": left_split_file_attributes,
4273
+ "data": [],
4274
+ "refresh_time": 1.0,
4275
+ },
4276
+ # File attributes dynamic
4277
+ {
4278
+ "proportion": 1/3,
4279
+ "auto_refresh": True,
4280
+ "get_data": update_file_attributes,
4281
+ "display": left_split_file_attributes_dynamic,
4282
+ "data": [],
4283
+ "refresh_time": 2.0,
4284
+ },
4285
+ # List of random numbers generated each second
4286
+ {
4287
+ "proportion": 1/2,
4288
+ "auto_refresh": True,
4289
+ "get_data": data_refresh_randint_title,
4290
+ "display": left_split_display_list,
4291
+ "data": ["Files", []],
4292
+ "refresh_time": 2,
4293
+ },
4294
+ # Nopane
4295
+ {
4296
+ "proportion": 1/3,
4297
+ "auto_refresh": False,
4298
+ "get_data": lambda data, state: [],
4299
+ "display": lambda scr, x, y, w, h, state, row, cell, data: [],
4300
+ "data": ["Files", []],
4301
+ "refresh_time": 1,
4302
+ },
4303
+ ]
4304
+ # function_data["require_option"] = [True for _ in function_data["items"]]
4082
4305
 
4083
4306
  stdscr = start_curses()
4084
4307
  try:
@@ -0,0 +1,198 @@
1
+ #!/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ pane_functions.py
5
+ Functions which are run by a listpick Picker to display data in a pane.
6
+
7
+ Author: GrimAndGreedy
8
+ License: MIT
9
+ """
10
+
11
+ import curses
12
+ import os
13
+ from listpick.pane.pane_utils import get_file_attributes, get_graph_string, escape_ansi
14
+ from listpick.pane.get_data import update_file_attributes
15
+
16
+ def left_start_pane(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
17
+ """
18
+ Display file attributes in right pane.
19
+ """
20
+ if test: return True
21
+
22
+ # Title
23
+ for i in range(h):
24
+ s = '*'*w
25
+ stdscr.addstr(y+i, x, s)
26
+
27
+
28
+ stdscr.addstr(y, x, "+")
29
+ stdscr.addstr(y+h-1, x, "+")
30
+ stdscr.addstr(y, x+w-1, "+")
31
+ stdscr.addstr(y+h-1, x+w-1, "+")
32
+
33
+
34
+ stdscr.addstr(y+1, x, f"{w},{h}")
35
+
36
+ return []
37
+
38
+
39
+ def left_split_file_attributes(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
40
+ """
41
+ Display file attributes in right pane.
42
+ """
43
+ if test: return True
44
+
45
+ # Title
46
+ title = "File attributes"
47
+ if len(title) < w: title = f"{title:^{w}}"
48
+ stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
49
+
50
+ # Separator
51
+ for j in range(h):
52
+ stdscr.addstr(j+y, x+w-1, ' ', curses.color_pair(state["colours_start"]+16))
53
+
54
+ # Display pane count
55
+ pane_count = len(state["right_panes"])
56
+ pane_index = state["right_pane_index"]
57
+ if pane_count > 1:
58
+ s = f" {pane_index+1}/{pane_count} "
59
+ stdscr.addstr(y+h-1, x, s, curses.color_pair(state["colours_start"]+20))
60
+
61
+ # Filename/cursor cell value
62
+ stdscr.addstr(y+2, x+2, cell[:w-3])
63
+
64
+ attributes = get_file_attributes(cell)
65
+ for i, attr in enumerate(attributes):
66
+ stdscr.addstr(y+3+i, x+4, attr[:w-5])
67
+
68
+ return []
69
+
70
+
71
+ def left_split_file_attributes_dynamic(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
72
+ """
73
+ Display file attributes in right pane.
74
+ """
75
+ if test: return True
76
+
77
+ # Title
78
+ title = "File attributes"
79
+ if len(title) < w: title = f"{title:^{w}}"
80
+ stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
81
+
82
+ # Separator
83
+ for j in range(h):
84
+ stdscr.addstr(j+y, x+w-1, ' ', curses.color_pair(state["colours_start"]+16))
85
+
86
+ # Display pane count
87
+ pane_count = len(state["right_panes"])
88
+ pane_index = state["right_pane_index"]
89
+ if pane_count > 1:
90
+ s = f" {pane_index+1}/{pane_count} "
91
+ stdscr.addstr(y+h-1, x, s, curses.color_pair(state["colours_start"]+20))
92
+
93
+ if len(state["indexed_items"]) == 0:
94
+ return []
95
+
96
+ # Filename/cursor cell value
97
+ stdscr.addstr(y+2, x+2, cell[:w-3])
98
+
99
+ # If the cursor-hovered file is different then reload the data
100
+ if data[1] != cell:
101
+ data[:] = update_file_attributes(data, state)
102
+
103
+ # attributes = get_file_attributes(cell)
104
+ if len(data) == 0: return []
105
+ attributes = data[0]
106
+ for i, attr in enumerate(attributes):
107
+ stdscr.addstr(y+3+i, x+4, attr[:w-5])
108
+
109
+ return []
110
+
111
+ def left_split_graph(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
112
+ """
113
+ Display a graph of the data in right pane.
114
+
115
+ data[0] = x_vals
116
+ data[1] = y_vals
117
+ data[2] = id
118
+ """
119
+ if test: return True
120
+
121
+ # Title
122
+ title = "Graph"
123
+ if len(title) < w: title = f"{title:^{w}}"
124
+ stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
125
+
126
+ # Separator
127
+ for j in range(h):
128
+ stdscr.addstr(j+y, x+w-1, ' ', curses.color_pair(state["colours_start"]+16))
129
+
130
+
131
+ # Display pane count
132
+ pane_count = len(state["right_panes"])
133
+ pane_index = state["right_pane_index"]
134
+ if pane_count > 1:
135
+ s = f" {pane_index+1}/{pane_count} "
136
+ stdscr.addstr(y+h-1, x, s, curses.color_pair(state["colours_start"]+20))
137
+
138
+ try:
139
+ import plotille as plt
140
+ except:
141
+ s = f"No module named 'plotille'"
142
+ stdscr.addstr(y+2, x+1, s[:w-2])
143
+ return None
144
+
145
+
146
+
147
+ # x_vals, y_vals = list(range(100)), [x**2 for x in range(100)]
148
+ if data in [[], {}, None]:
149
+ return None
150
+ x_vals, y_vals = data[0], data[1]
151
+ graph_str = get_graph_string(x_vals, y_vals, width=w-3-10, height=h-3)
152
+ for i, s in enumerate(graph_str.split("\n")):
153
+ s = escape_ansi(s)
154
+ stdscr.addstr(y+2+i, x+1, s[:w-2])
155
+
156
+ return []
157
+
158
+
159
+
160
+
161
+ def left_split_display_list(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
162
+ """
163
+ data[0]:str = title
164
+ data[1]:list[str] = list of strings to display
165
+ """
166
+ if test: return True
167
+
168
+ # Title
169
+ title = data[0]
170
+ if len(title) < w: title = f"{title:^{w}}"
171
+ stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
172
+
173
+ # Separator
174
+ for j in range(h):
175
+ stdscr.addstr(j+y, x+w-1, ' ', curses.color_pair(state["colours_start"]+16))
176
+
177
+
178
+ # Display pane count
179
+ pane_count = len(state["right_panes"])
180
+ pane_index = state["right_pane_index"]
181
+ if pane_count > 1:
182
+ s = f" {pane_index+1}/{pane_count} "
183
+ stdscr.addstr(y+h-1, x, s, curses.color_pair(state["colours_start"]+20))
184
+
185
+ if data in [[], {}, None]:
186
+ return None
187
+
188
+ items = data[1]
189
+ number_to_display = min(len(items), h-3)
190
+ for i in range(number_to_display):
191
+ s = items[i]
192
+ stdscr.addstr(y+1+i, x+2, s[:w-2])
193
+
194
+ if number_to_display < len(items):
195
+ stdscr.addstr(y+1+number_to_display, x+2, f" ... {len(items)-number_to_display} more"[:w-2])
196
+
197
+
198
+ return []
@@ -0,0 +1,175 @@
1
+ #!/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ pane_functions.py
5
+ Functions which are run by a listpick Picker to display data in a pane.
6
+
7
+ Author: GrimAndGreedy
8
+ License: MIT
9
+ """
10
+
11
+ import curses
12
+ import os
13
+ from listpick.pane.pane_utils import get_file_attributes, get_graph_string, escape_ansi
14
+ from listpick.pane.get_data import update_file_attributes
15
+
16
+ def right_split_file_attributes(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
17
+ """
18
+ Display file attributes in right pane.
19
+ """
20
+ if test: return True
21
+
22
+ # Title
23
+ title = "File attributes"
24
+ if len(title) < w: title = f"{title:^{w}}"
25
+ stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
26
+
27
+ # Separator
28
+ for j in range(h):
29
+ stdscr.addstr(j+y, x, ' ', curses.color_pair(state["colours_start"]+16))
30
+
31
+ # Display pane count
32
+ pane_count = len(state["right_panes"])
33
+ pane_index = state["right_pane_index"]
34
+ if pane_count > 1:
35
+ s = f" {pane_index+1}/{pane_count} "
36
+ stdscr.addstr(y+h-1, x+w-len(s)-1, s, curses.color_pair(state["colours_start"]+20))
37
+
38
+ # Filename/cursor cell value
39
+ stdscr.addstr(y+2, x+2, cell[:w-3])
40
+
41
+ attributes = get_file_attributes(cell)
42
+ for i, attr in enumerate(attributes):
43
+ stdscr.addstr(y+3+i, x+4, attr[:w-5])
44
+
45
+ return []
46
+
47
+
48
+ def right_split_file_attributes_dynamic(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
49
+ """
50
+ Display file attributes in right pane.
51
+ """
52
+ if test: return True
53
+
54
+ # Title
55
+ title = "File attributes"
56
+ if len(title) < w: title = f"{title:^{w}}"
57
+ stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
58
+
59
+ # Separator
60
+ for j in range(h):
61
+ stdscr.addstr(j+y, x, ' ', curses.color_pair(state["colours_start"]+16))
62
+
63
+ # Display pane count
64
+ pane_count = len(state["right_panes"])
65
+ pane_index = state["right_pane_index"]
66
+ if pane_count > 1:
67
+ s = f" {pane_index+1}/{pane_count} "
68
+ stdscr.addstr(y+h-1, x+w-len(s)-1, s, curses.color_pair(state["colours_start"]+20))
69
+
70
+ if len(state["indexed_items"]) == 0:
71
+ return []
72
+
73
+ # Filename/cursor cell value
74
+ stdscr.addstr(y+2, x+2, cell[:w-3])
75
+
76
+ # If the cursor-hovered file is different then reload the data
77
+ if data[1] != cell:
78
+ data[:] = update_file_attributes(data, state)
79
+
80
+ # attributes = get_file_attributes(cell)
81
+ if len(data) == 0: return []
82
+ attributes = data[0]
83
+ for i, attr in enumerate(attributes):
84
+ stdscr.addstr(y+3+i, x+4, attr[:w-5])
85
+
86
+ return []
87
+
88
+ def right_split_graph(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
89
+ """
90
+ Display a graph of the data in right pane.
91
+
92
+ data[0] = x_vals
93
+ data[1] = y_vals
94
+ data[2] = id
95
+ """
96
+ if test: return True
97
+
98
+ # Title
99
+ title = "Graph"
100
+ if len(title) < w: title = f"{title:^{w}}"
101
+ stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
102
+
103
+ # Separator
104
+ for j in range(h):
105
+ stdscr.addstr(j+y, x, ' ', curses.color_pair(state["colours_start"]+16))
106
+
107
+
108
+ # Display pane count
109
+ pane_count = len(state["right_panes"])
110
+ pane_index = state["right_pane_index"]
111
+ if pane_count > 1:
112
+ s = f" {pane_index+1}/{pane_count} "
113
+ stdscr.addstr(y+h-1, x+w-len(s)-1, s, curses.color_pair(state["colours_start"]+20))
114
+
115
+ try:
116
+ import plotille as plt
117
+ except:
118
+ s = f"No module named 'plotille'"
119
+ stdscr.addstr(y+2, x+2, s[:w-2])
120
+ return None
121
+
122
+
123
+
124
+ # x_vals, y_vals = list(range(100)), [x**2 for x in range(100)]
125
+ if data in [[], {}, None]:
126
+ return None
127
+ x_vals, y_vals = data[0], data[1]
128
+ graph_str = get_graph_string(x_vals, y_vals, width=w-3-10, height=h-3)
129
+ for i, s in enumerate(graph_str.split("\n")):
130
+ s = escape_ansi(s)
131
+ stdscr.addstr(y+2+i, x+2, s[:w-2])
132
+
133
+ return []
134
+
135
+
136
+
137
+
138
+ def right_split_display_list(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
139
+ """
140
+ data[0]:str = title
141
+ data[1]:list[str] = list of strings to display
142
+ """
143
+ if test: return True
144
+
145
+ # Title
146
+ title = data[0]
147
+ if len(title) < w: title = f"{title:^{w}}"
148
+ stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
149
+
150
+ # Separator
151
+ for j in range(h):
152
+ stdscr.addstr(j+y, x, ' ', curses.color_pair(state["colours_start"]+16))
153
+
154
+
155
+ # Display pane count
156
+ pane_count = len(state["right_panes"])
157
+ pane_index = state["right_pane_index"]
158
+ if pane_count > 1:
159
+ s = f" {pane_index+1}/{pane_count} "
160
+ stdscr.addstr(y+h-1, x+w-len(s)-1, s, curses.color_pair(state["colours_start"]+20))
161
+
162
+ if data in [[], {}, None]:
163
+ return None
164
+
165
+ items = data[1]
166
+ number_to_display = min(len(items), h-3)
167
+ for i in range(number_to_display):
168
+ s = items[i]
169
+ stdscr.addstr(y+1+i, x+2, s[:w-2])
170
+
171
+ if number_to_display < len(items):
172
+ stdscr.addstr(y+1+number_to_display, x+2, f" ... {len(items)-number_to_display} more"[:w-2])
173
+
174
+
175
+ return []
listpick/ui/build_help.py CHANGED
@@ -145,12 +145,14 @@ def build_help_rows(keys_dict: dict, debug: bool = False) -> list[list[str]]:
145
145
  "sheet_next": "Go to the next sheet.",
146
146
  "sheet_prev": "Go to the previous sheet.",
147
147
  "toggle_right_pane": "Toggle the right pane",
148
- "cycle_right_pane": "Cycle through right panes",
148
+ "cycle_right_pane": "Cycle through right pane views",
149
+ "toggle_left_pane": "Toggle the left pane",
150
+ "cycle_left_pane": "Cycle through left pane views",
149
151
  }
150
152
  sections = {
151
153
  "Navigation:": [ "cursor_down", "cursor_up", "half_page_up", "half_page_down", "page_up", "page_down", "cursor_bottom", "cursor_top", "five_up", "five_down", "scroll_right", "scroll_left", "scroll_right_25", "scroll_left_25", "scroll_far_right", "scroll_far_left" ],
152
154
  "Selection:": [ "toggle_select", "select_all", "select_none", "visual_selection_toggle", "visual_deselection_toggle", "enter" ],
153
- "UI:": [ "toggle_footer", "redraw_screen", "decrease_lines_per_page", "increase_lines_per_page", "increase_column_width", "decrease_column_width", "notification_toggle", "toggle_right_pane", "cycle_right_pane"],
155
+ "UI:": [ "toggle_footer", "redraw_screen", "decrease_lines_per_page", "increase_lines_per_page", "increase_column_width", "decrease_column_width", "notification_toggle", "toggle_right_pane", "cycle_right_pane", "toggle_left_pane", "cycle_left_pane"],
154
156
  "Sort:": [ "cycle_sort_method", "cycle_sort_method_reverse", "cycle_sort_order", ] ,
155
157
  "Data manipulation:": [ "delete", "delete_column", "edit", "edit_picker", "edit_ipython", "add_column_before", "add_column_after", "add_row_before", "add_row_after"],
156
158
  "Filter and search:": [ "filter_input", "search_input", "continue_search_forward", "continue_search_backward", ] ,
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(':')],
54
+ "opts_input": [ord('e')],
55
55
  "opts_select": [ord('o')],
56
56
  "mode_next": [9], # Tab key
57
57
  "mode_prev": [353], # Shift+Tab key
@@ -90,6 +90,8 @@ picker_keys = {
90
90
  # "sheet_prev": [],
91
91
  "toggle_right_pane": [ord("'")],
92
92
  "cycle_right_pane": [ord('"')],
93
+ "toggle_left_pane": [ord(";")],
94
+ "cycle_left_pane": [ord(':')],
93
95
  }
94
96
 
95
97
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.16.8
3
+ Version: 0.1.16.9
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
@@ -41,7 +41,7 @@ Dynamic: requires-dist
41
41
  Dynamic: requires-python
42
42
  Dynamic: summary
43
43
 
44
- # listpick
44
+ qq# listpick
45
45
 
46
46
  listpick is a TUI tool which displays a tabulated list of rows and allows the user to operate upon these rows--select, copy, pipe. A very simple concept but also, I hope, a powerful tool that will make it easier for people to develop TUI apps.
47
47
 
@@ -102,31 +102,32 @@ The application allows you to:
102
102
  ## Examples
103
103
 
104
104
 
105
- ### Identify video duplicates (./examples/data_generation//video_duplicates.toml):
106
- ```python
107
- listpick -g ./examples/data_generation/video_duplicates.toml
108
- ```
109
- - From the list of commands in the toml file we generate the properties we will use to identify the duplicates.
110
105
 
111
- - In the example file we set the directory and get the files with a simle `eza` (`ls`) command. We could also use `find` or `cat` from a list of files.
106
+ ### Aria2TUI
112
107
 
108
+ [Aria2TUI](https://github.com/grimandgreedy/Aria2TUI) is implemented using listpick. This is a good example of how listpick can be used for menus, data viewing, and active data retrieval.
113
109
 
114
- - We get the SHA1 hash to identify identical files; we also get the size, duration, resolution, and bitrate so that we can identify a video duplicate that may have the same duration but a lower resolution.
110
+ <div align="center"> <img src="assets/aria2tui_graph_screenshot.png" alt="Aria2TUI" width="70%"> </div>
115
111
 
116
- <div align="center"> <img src="assets/file_compare.png" alt="Video Compare" width="70%"> </div>
112
+ ### lpfman
113
+ [lpfman](https://github.com/grimandgreedy/lpfman) is a terminal file manager with extensive column support.
117
114
 
115
+ <div align="center"> <img src="https://github.com/grimandgreedy/lpfman/blob/master/assets/lpfman_image_preview.png?raw=true" alt="lpfman" width="70%"> </div>
118
116
 
119
- ### Aria2TUI
120
117
 
121
- [Aria2TUI](https://github.com/grimandgreedy/Aria2TUI) is implemented using listpick. This is a good example of how listpick can be used for menus, data viewing, and active data retrieval.
118
+ ### Data generation from toml file
119
+
120
+ ```python
121
+ listpick -g ./examples/data_generation/video_duplicates.toml
122
+ ```
123
+ - From the list of commands in the toml file we generate the properties we will use to identify the duplicates.
122
124
 
123
- <div align="center"> <img src="assets/aria2tui_screenshot.png" alt="Aria2TUI" width="70%"> </div>
125
+ - In the example file we set the directory and get the files with a simle `eza` (`ls`) command. We could also use `find` or `cat` from a list of files.
124
126
 
125
- ### lpfman
126
- [lpfman](https://github.com/grimandgreedy/lpfman) is a basic file manager created for the purposes of illustrating how easy TUI apps can be developed with the use of listpick. In 20 minutes and <100 lines of code we made a very basic file manager.
127
127
 
128
- <div align="center"> <img src="assets/lpfman.png" alt="lpfman" width="70%"> </div>
128
+ - We get the SHA1 hash to identify identical files; we also get the size, duration, resolution, and bitrate so that we can identify a video duplicate that may have the same duration but a lower resolution.
129
129
 
130
+ <div align="center"> <img src="assets/file_compare.png" alt="Video Compare" width="70%"> </div>
130
131
 
131
132
 
132
133
  ## Description
@@ -1,17 +1,19 @@
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=oFkMaWdoqd6fId1hBSavY2Nsoql6EcVppd4d7mgVVGo,201956
3
+ listpick/listpick_app.py,sha256=t690QmA4_fJ-zi2PhmRrwjMUr2oKOvxEScdDner2mr0,212136
4
4
  listpick/pane/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  listpick/pane/get_data.py,sha256=l12mHIb6qoZWIfW5zZZY8K8EqNcyIcRiHgtRaM2CVGs,2735
6
+ listpick/pane/left_pane_functions.py,sha256=SVIW4Ef8uNPBQRk4hL67mEFL3pfgChSFZSMRz06CVzw,5543
6
7
  listpick/pane/pane_functions.py,sha256=_dL9jHpd3sT0enL9H_bMcUsBlMELXdtP9dtKFSC2KPQ,5117
8
+ listpick/pane/pane_functions_1.py,sha256=_dL9jHpd3sT0enL9H_bMcUsBlMELXdtP9dtKFSC2KPQ,5117
7
9
  listpick/pane/pane_utils.py,sha256=cnuzBH52wdWoKrHR6iMBF4N-uhwpXYpHDnrglk21pqg,2539
8
10
  listpick/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- listpick/ui/build_help.py,sha256=FNKshZpYJfgF-17ad5Mq0IGeBEo7y52u3CcpQ8ajlOs,10774
11
+ listpick/ui/build_help.py,sha256=DJm_WxgdfrI6nJ8yjdraBrI6bECvNrXp1vU-kHxCczo,10961
10
12
  listpick/ui/draw_screen.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
13
  listpick/ui/footer.py,sha256=NcdH1uO_ma91m0qCczyQZ3zGrexfkiEnwDf5E4tHSMk,15089
12
14
  listpick/ui/help_screen.py,sha256=zbfGIgb-IXtATpl4_Sx7nPbsnRXZ7eiMYlCKGS9EFmw,5608
13
15
  listpick/ui/input_field.py,sha256=scJjvmSS0QqeDbCky7_0Zgt35Aki7gezRJkrQROlLg4,30034
14
- listpick/ui/keys.py,sha256=rOOhvxfrQ1LRuLJYf_6pAQcuugxHo-M9TQn7Qow5uIg,13716
16
+ listpick/ui/keys.py,sha256=FlpdWqX1lTP7K6gqHn9jfH2MmuloigeOA7EMzYhaJV8,13845
15
17
  listpick/ui/picker_colours.py,sha256=FFsyny_q0mGO6u7B1n7anuReBtP7Jw6LrgX5ycN-MRM,13413
16
18
  listpick/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
19
  listpick/utils/clipboard_operations.py,sha256=ORdNm2kgGbfs51xJSvgJPERgoSmBgT11axuMkvSoP9A,3133
@@ -31,9 +33,9 @@ listpick/utils/sorting.py,sha256=WZZiVlVA3Zkcpwji3U5SNFlQ14zVEk3cZJtQirBkecQ,532
31
33
  listpick/utils/table_to_list_of_lists.py,sha256=XBj7eGBDF15BRME-swnoXyOfZWxXCxrXp0pzsBfcJ5g,12224
32
34
  listpick/utils/user_input.py,sha256=L3ylI7nnuFM_TP1XKwpiKpxUSkNb2W5cr7mJjTmv_6E,4582
33
35
  listpick/utils/utils.py,sha256=nsR6orCBQy3rTXrCweq8cV-RzRVU15v3J9NclPeAOJk,13741
34
- listpick-0.1.16.8.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
35
- listpick-0.1.16.8.dist-info/METADATA,sha256=ywGEK-Ljulg5NRWTW-AKhbZSCDOcASjOK5UdOSmWeo4,8128
36
- listpick-0.1.16.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
- listpick-0.1.16.8.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
38
- listpick-0.1.16.8.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
39
- listpick-0.1.16.8.dist-info/RECORD,,
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,,