listpick 0.1.14.8__tar.gz → 0.1.14.9__tar.gz

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.

Files changed (61) hide show
  1. {listpick-0.1.14.8 → listpick-0.1.14.9}/CHANGELOG.md +1 -0
  2. {listpick-0.1.14.8/src/listpick.egg-info → listpick-0.1.14.9}/PKG-INFO +1 -1
  3. {listpick-0.1.14.8 → listpick-0.1.14.9}/setup.py +1 -1
  4. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/listpick_app.py +19 -44
  5. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/ui/input_field.py +101 -105
  6. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/ui/keys.py +4 -2
  7. listpick-0.1.14.9/src/listpick/utils/keycodes.py +88 -0
  8. listpick-0.1.14.9/src/listpick/utils/user_input.py +104 -0
  9. {listpick-0.1.14.8 → listpick-0.1.14.9/src/listpick.egg-info}/PKG-INFO +1 -1
  10. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick.egg-info/SOURCES.txt +2 -1
  11. listpick-0.1.14.8/src/listpick/ui/keycodes.py +0 -70
  12. {listpick-0.1.14.8 → listpick-0.1.14.9}/.gitignore +0 -0
  13. {listpick-0.1.14.8 → listpick-0.1.14.9}/LICENSE.txt +0 -0
  14. {listpick-0.1.14.8 → listpick-0.1.14.9}/README.md +0 -0
  15. {listpick-0.1.14.8 → listpick-0.1.14.9}/TODO.md +0 -0
  16. {listpick-0.1.14.8 → listpick-0.1.14.9}/assets/aria2tui_screenshot.png +0 -0
  17. {listpick-0.1.14.8 → listpick-0.1.14.9}/assets/file_compare.png +0 -0
  18. {listpick-0.1.14.8 → listpick-0.1.14.9}/assets/lpfman.png +0 -0
  19. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/data_generation/list_files.toml +0 -0
  20. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/data_generation/list_files_empty.toml +0 -0
  21. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/data_generation/video_duplicates.toml +0 -0
  22. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/data_generation/video_mediainfo.toml +0 -0
  23. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/input_files/polynomials.tsv +0 -0
  24. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/input_guides/gnuplot_graph.md +0 -0
  25. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/picker/auxiallary_files/2024-25_Premier_League.pkl +0 -0
  26. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/picker/footer_string_example.py +0 -0
  27. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/picker/picker_example.py +0 -0
  28. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/picker/template.py +0 -0
  29. {listpick-0.1.14.8 → listpick-0.1.14.9}/examples/picker/wikipedia_table.py +0 -0
  30. {listpick-0.1.14.8 → listpick-0.1.14.9}/listpick.py +0 -0
  31. {listpick-0.1.14.8 → listpick-0.1.14.9}/requirements.txt +0 -0
  32. {listpick-0.1.14.8 → listpick-0.1.14.9}/setup.cfg +0 -0
  33. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/__init__.py +0 -0
  34. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/__main__.py +0 -0
  35. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/ui/__init__.py +0 -0
  36. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/ui/build_help.py +0 -0
  37. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/ui/footer.py +0 -0
  38. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/ui/git-bugreport-2025-08-16-1438.txt +0 -0
  39. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/ui/help_screen.py +0 -0
  40. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/ui/pane_stuff.py +0 -0
  41. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/ui/picker_colours.py +0 -0
  42. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/__init__.py +0 -0
  43. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/clipboard_operations.py +0 -0
  44. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/config.py +0 -0
  45. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/dump.py +0 -0
  46. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/filtering.py +0 -0
  47. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/generate_data.py +0 -0
  48. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/options_selectors.py +0 -0
  49. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/paste_operations.py +0 -0
  50. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/picker_log.py +0 -0
  51. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/search_and_filter_utils.py +0 -0
  52. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/searching.py +0 -0
  53. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/sorting.py +0 -0
  54. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/table_to_list_of_lists.py +0 -0
  55. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick/utils/utils.py +0 -0
  56. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick.egg-info/dependency_links.txt +0 -0
  57. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick.egg-info/entry_points.txt +0 -0
  58. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick.egg-info/requires.txt +0 -0
  59. {listpick-0.1.14.8 → listpick-0.1.14.9}/src/listpick.egg-info/top_level.txt +0 -0
  60. {listpick-0.1.14.8 → listpick-0.1.14.9}/tests/kitty_control.sh +0 -0
  61. {listpick-0.1.14.8 → listpick-0.1.14.9}/tests/sorting_dates.csv +0 -0
@@ -24,6 +24,7 @@ Note that the changes between 0.1.11.0 and 1.1.12.0 are listed under 0.1.11
24
24
  - meta+key (input_field)
25
25
  - Added column number to footer
26
26
  - Improved splitting of whitespace separated data passed on stdin.
27
+ - Main picker is now refreshed after a resize when there is a notification.
27
28
 
28
29
  ## [0.1.14] 2025-08-20
29
30
  - Fixed bug when cells are centred vertically.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.14.8
3
+ Version: 0.1.14.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
@@ -16,7 +16,7 @@ with open("README.md", "r", encoding = "utf-8") as fh:
16
16
 
17
17
  setuptools.setup(
18
18
  name = "listpick",
19
- version = "0.1.14.8",
19
+ version = "0.1.14.9",
20
20
  author = "Grim",
21
21
  author_email = "grimandgreedy@protonmail.com",
22
22
  description = "Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.",
@@ -40,6 +40,7 @@ from listpick.utils.dump import dump_state, load_state, dump_data
40
40
  from listpick.ui.build_help import build_help_rows
41
41
  from listpick.ui.footer import StandardFooter, CompactFooter, NoFooter
42
42
  from listpick.utils.picker_log import setup_logger
43
+ from listpick.utils.user_input import get_char, open_tty
43
44
 
44
45
 
45
46
  try:
@@ -187,6 +188,8 @@ class Picker:
187
188
  sheet_index = 0,
188
189
  sheet_states = [{}],
189
190
 
191
+ redraw_screen_accessory: Callable = lambda : None,
192
+
190
193
  ):
191
194
  self.stdscr = stdscr
192
195
  self.items = items
@@ -335,6 +338,7 @@ class Picker:
335
338
  self.sheet_states = sheet_states
336
339
  self.sheets = sheets
337
340
 
341
+ self.redraw_screen_accessory = redraw_screen_accessory
338
342
  self.initialise_picker_state(reset_colours=self.reset_colours)
339
343
 
340
344
  # Note: We have to set the footer after initialising the picker state so that the footer can use the get_function_data method
@@ -747,6 +751,8 @@ class Picker:
747
751
 
748
752
  def draw_screen_(self, indexed_items: list[Tuple[int, list[str]]], highlights: list[dict] = [{}], clear: bool = True) -> None:
749
753
  """ Draw Picker screen. """
754
+
755
+ self.redraw_screen_accessory()
750
756
  self.logger.debug("Draw screen.")
751
757
 
752
758
  if clear:
@@ -1236,6 +1242,7 @@ class Picker:
1236
1242
  "sheets": self.sheets,
1237
1243
  "sheet_name": self.sheet_name,
1238
1244
  "sheet_states": self.sheet_states,
1245
+ "redraw_screen_accessory": self.redraw_screen_accessory,
1239
1246
  }
1240
1247
  return function_data
1241
1248
 
@@ -1270,6 +1277,7 @@ class Picker:
1270
1277
  "centre_in_terminal_vertical",
1271
1278
  "centre_in_cols",
1272
1279
  "centre_in_terminal",
1280
+ "redraw_screen_accessory",
1273
1281
  ]
1274
1282
 
1275
1283
  for var in variables:
@@ -1409,6 +1417,11 @@ class Picker:
1409
1417
 
1410
1418
  submenu_win = curses.newwin(notification_height, notification_width, 3, w - (notification_width+4))
1411
1419
  # submenu_win = self.stdscr.subwin(notification_height, notification_width, 3, w - (notification_width+4))
1420
+ def update_after_resize():
1421
+ h, w = self.stdscr.getmaxyx()
1422
+ submenu_win.mvwin(3, w - (notification_width+4))
1423
+ self.draw_screen(self.indexed_items, self.highlights)
1424
+
1412
1425
  notification_data = {
1413
1426
  "items": submenu_items,
1414
1427
  "title": title,
@@ -1430,9 +1443,14 @@ class Picker:
1430
1443
  "loaded_file": "",
1431
1444
  "loaded_file_index": 0,
1432
1445
  "cell_cursor": False,
1446
+ # "redraw_screen_accessory": lambda : self.draw_screen(self.indexed_items, self.highlights),
1447
+ "redraw_screen_accessory": update_after_resize,
1448
+ "get_new_data": False,
1449
+ # "key_remappings": notification_remap_keys,
1433
1450
  }
1434
1451
  OptionPicker = Picker(submenu_win, **notification_data)
1435
1452
  s, o, f = OptionPicker.run()
1453
+ os.system(f"notify-send resizing")
1436
1454
 
1437
1455
  if o != "refresh": break
1438
1456
  submenu_win.clear()
@@ -1441,6 +1459,7 @@ class Picker:
1441
1459
  stdscr.clear()
1442
1460
  stdscr.refresh()
1443
1461
  self.draw_screen(self.indexed_items, self.highlights)
1462
+
1444
1463
  # set_colours(colours=get_colours(0))
1445
1464
 
1446
1465
  def toggle_column_visibility(self, col_index:int) -> None:
@@ -3580,50 +3599,6 @@ def unrestrict_curses(stdscr: curses.window) -> None:
3580
3599
  curses.curs_set(False)
3581
3600
 
3582
3601
 
3583
- def open_tty():
3584
- """ Return a file descriptor for the tty that we are opening"""
3585
- tty_fd = os.open('/dev/tty', os.O_RDONLY)
3586
- tty.setraw(tty_fd)
3587
- return tty_fd
3588
-
3589
- def get_char(tty_fd, timeout: float = 0.2, secondary: bool = False) -> int:
3590
- """ Get character from a tty_fd with a timeout. """
3591
- rlist, _, _ = select.select([tty_fd], [], [], timeout)
3592
- if rlist:
3593
- # key = ord(tty_fd.read(1))
3594
- key = ord(os.read(tty_fd, 1))
3595
- if not secondary:
3596
- if key == 27:
3597
- key2 = get_char(tty_fd, timeout=0.01, secondary=True)
3598
- key3 = get_char(tty_fd, timeout=0.01, secondary=True)
3599
- key4 = get_char(tty_fd, timeout=0.01, secondary=True)
3600
- key5 = get_char(tty_fd, timeout=0.01, secondary=True)
3601
- if key2 == ord('O') and key3 == ord('B'):
3602
- key = curses.KEY_DOWN
3603
- elif key2 == ord('O') and key3 == ord('A'):
3604
- key = curses.KEY_UP
3605
- elif key2 == ord('O') and key3 == ord('D'):
3606
- key = curses.KEY_LEFT
3607
- elif key2 == ord('O') and key3 == ord('C'):
3608
- key = curses.KEY_RIGHT
3609
- elif key2 == ord('[') and key3 == ord('Z'):
3610
- key = 353
3611
- elif key2 == ord('O') and key3 == ord('F'):
3612
- key = curses.KEY_END
3613
- elif key2 == ord('O') and key3 == ord('H'):
3614
- key = curses.KEY_HOME
3615
- elif key2 == ord('[') and key3 == ord('3') and key4 == ord('~'):
3616
- key = curses.KEY_DC
3617
- elif key2 == ord('[') and key3 == ord('3') and key4 == ord('~'):
3618
- key = curses.KEY_DC
3619
- elif key2 == ord('O') and key3 == ord('P'):
3620
- key = curses.KEY_F1
3621
- elif key2 == ord('[') and key3 == ord('1') and key4 == ord('5') and key5 == ord('~'):
3622
- key = curses.KEY_F5
3623
-
3624
- else:
3625
- key = -1
3626
- return key
3627
3602
 
3628
3603
  def main() -> None:
3629
3604
  """ Main function when listpick is executed. Deals with command line arguments and starts a Picker. """
@@ -18,22 +18,24 @@ import logging
18
18
  logger = logging.getLogger('picker_log')
19
19
  import select
20
20
  import tty
21
-
22
- def open_tty():
23
- """ Return a file descriptor for the tty that we are opening"""
24
- tty_fd = os.open('/dev/tty', os.O_RDONLY)
25
- tty.setraw(tty_fd)
26
- return tty_fd
27
-
28
- def get_char(tty_fd, timeout: float = 0.2) -> int:
29
- """ Get character from a tty_fd with a timeout. """
30
- rlist, _, _ = select.select([tty_fd], [], [], timeout)
31
- if rlist:
32
- # key = ord(tty_fd.read(1))
33
- key = ord(os.read(tty_fd, 1))
34
- else:
35
- key = -1
36
- return key
21
+ from listpick.utils.user_input import get_char, open_tty
22
+ from listpick.utils import keycodes
23
+
24
+ # def open_tty():
25
+ # """ Return a file descriptor for the tty that we are opening"""
26
+ # tty_fd = os.open('/dev/tty', os.O_RDONLY)
27
+ # tty.setraw(tty_fd)
28
+ # return tty_fd
29
+ #
30
+ # def get_char(tty_fd, timeout: float = 0.2) -> int:
31
+ # """ Get character from a tty_fd with a timeout. """
32
+ # rlist, _, _ = select.select([tty_fd], [], [], timeout)
33
+ # if rlist:
34
+ # # key = ord(tty_fd.read(1))
35
+ # key = ord(os.read(tty_fd, 1))
36
+ # else:
37
+ # key = -1
38
+ # return key
37
39
 
38
40
  def input_field(
39
41
  stdscr: curses.window,
@@ -230,100 +232,94 @@ def input_field(
230
232
  key = get_char(tty_fd, timeout=0.5)
231
233
  # key = stdscr.getch()
232
234
 
233
- if key in [27, 7]: # ESC/ALT key or Ctrl+g
234
- # For Alt-key combinations: set nodelay and get the second key
235
- # stdscr.nodelay(True)
236
- # key2 = stdscr.getch()
237
- key2 = get_char(tty_fd, timeout=0.05)
238
-
239
- if key2 == -1: # ESCAPE key (no key-combination)
240
- stdscr.nodelay(False)
241
- return "", False
242
- elif key2 == curses.KEY_BACKSPACE:
243
- # Delete to backslash or space (word_separator_chars)
244
- search_txt = usrtxt[:-cursor] if cursor > 0 else usrtxt
245
- index = -1
246
- for word_separator_char in word_separator_chars:
247
- tmp_index = search_txt[::-1].find(word_separator_char)
248
- if tmp_index > -1:
249
- if index == -1:
250
- index = tmp_index
251
- else:
252
- index = min(index, tmp_index)
253
-
254
- if index == -1:
255
- if cursor == 0:
256
- kill_ring.append(usrtxt)
257
- usrtxt = ""
258
- else:
259
- kill_ring.append(usrtxt[:-(cursor+1)])
260
- usrtxt = usrtxt[-(cursor+1):]
261
- cursor = len(usrtxt)
262
- else:
263
- if index == 0:
264
- kill_ring.append(search_txt[-1:])
265
- usrtxt = search_txt[:-1] + usrtxt[len(search_txt):]
235
+
236
+ if key in [27, 7]: # ESC/ALT key or Ctrl+g
237
+ return "", False
238
+
239
+ elif key == keycodes.META_BS:
240
+ # Delete to backslash or space (word_separator_chars)
241
+ search_txt = usrtxt[:-cursor] if cursor > 0 else usrtxt
242
+ index = -1
243
+ for word_separator_char in word_separator_chars:
244
+ tmp_index = search_txt[::-1].find(word_separator_char)
245
+ if tmp_index > -1:
246
+ if index == -1:
247
+ index = tmp_index
266
248
  else:
267
- kill_ring.append(search_txt[-index:])
268
- usrtxt = search_txt[:-index] + usrtxt[len(search_txt):]
249
+ index = min(index, tmp_index)
269
250
 
270
- potential_path = usrtxt
271
- kill_ring_active = False
272
-
273
- elif key2 == ord('f'):
274
- # Forward word
275
- search_txt = usrtxt[-cursor:]
276
- index = -1
277
- for word_separator_char in word_separator_chars:
278
- tmp_index = search_txt.find(word_separator_char)
279
-
280
- if tmp_index > -1:
281
- if index == -1:
282
- index = tmp_index
283
- else:
284
- index = min(index, tmp_index)
285
-
286
- if index == -1:
287
- cursor = 0
251
+ if index == -1:
252
+ if cursor == 0:
253
+ kill_ring.append(usrtxt)
254
+ usrtxt = ""
288
255
  else:
289
- cursor -= index + 1
290
- cursor = max(cursor, 0)
291
- kill_ring_active = False
292
-
293
- elif key2 == ord('b'):
294
- # Backwards word
295
- search_txt = usrtxt[:-cursor] if cursor > 0 else usrtxt
296
- index = -1
297
- for word_separator_char in word_separator_chars:
298
- tmp_index = search_txt[::-1].find(word_separator_char)
299
-
300
- if tmp_index == 0:
301
- tmp_index = search_txt[:-1][::-1].find(word_separator_char)
302
-
303
- if tmp_index > -1:
304
- if index == -1:
305
- index = tmp_index
306
- else:
307
- index = min(index, tmp_index)
308
-
309
- if index == -1:
310
- cursor = len(usrtxt)
256
+ kill_ring.append(usrtxt[:-(cursor+1)])
257
+ usrtxt = usrtxt[-(cursor+1):]
258
+ cursor = len(usrtxt)
259
+ else:
260
+ if index == 0:
261
+ kill_ring.append(search_txt[-1:])
262
+ usrtxt = search_txt[:-1] + usrtxt[len(search_txt):]
311
263
  else:
312
- cursor += index + 1
313
- kill_ring_active = False
314
-
315
- elif key2 == ord('y'):
316
- prev_kill_ring_index = kill_ring_index
317
- kill_ring_index = (kill_ring_index + 1)%len(kill_ring)
318
- if kill_ring_active and len(kill_ring):
319
- if cursor == 0:
320
- usrtxt = usrtxt[:-len(kill_ring[prev_kill_ring_index])]
321
- usrtxt += kill_ring[kill_ring_index]
264
+ kill_ring.append(search_txt[-index:])
265
+ usrtxt = search_txt[:-index] + usrtxt[len(search_txt):]
266
+
267
+ potential_path = usrtxt
268
+ kill_ring_active = False
269
+
270
+ elif key == keycodes.META_f:
271
+ # Forward word
272
+ search_txt = usrtxt[-cursor:]
273
+ index = -1
274
+ for word_separator_char in word_separator_chars:
275
+ tmp_index = search_txt.find(word_separator_char)
276
+
277
+ if tmp_index > -1:
278
+ if index == -1:
279
+ index = tmp_index
280
+ else:
281
+ index = min(index, tmp_index)
282
+
283
+ if index == -1:
284
+ cursor = 0
285
+ else:
286
+ cursor -= index + 1
287
+ cursor = max(cursor, 0)
288
+ kill_ring_active = False
289
+
290
+ elif key == keycodes.META_b:
291
+ # Backwards word
292
+ search_txt = usrtxt[:-cursor] if cursor > 0 else usrtxt
293
+ index = -1
294
+ for word_separator_char in word_separator_chars:
295
+ tmp_index = search_txt[::-1].find(word_separator_char)
296
+
297
+ if tmp_index == 0:
298
+ tmp_index = search_txt[:-1][::-1].find(word_separator_char)
299
+
300
+ if tmp_index > -1:
301
+ if index == -1:
302
+ index = tmp_index
322
303
  else:
323
- usrtxt = usrtxt[-cursor:-(cursor+len(kill_ring[prev_kill_ring_index]))]
324
- usrtxt = usrtxt[:-cursor] + kill_ring[kill_ring_index] + usrtxt[-cursor:]
325
- # Return to delayed getch
326
- stdscr.nodelay(False)
304
+ index = min(index, tmp_index)
305
+
306
+ if index == -1:
307
+ cursor = len(usrtxt)
308
+ else:
309
+ cursor += index + 1
310
+ kill_ring_active = False
311
+
312
+ elif key == keycodes.META_y:
313
+ # Kill ring
314
+ prev_kill_ring_index = kill_ring_index
315
+ kill_ring_index = (kill_ring_index + 1)%len(kill_ring)
316
+ if kill_ring_active and len(kill_ring):
317
+ if cursor == 0:
318
+ usrtxt = usrtxt[:-len(kill_ring[prev_kill_ring_index])]
319
+ usrtxt += kill_ring[kill_ring_index]
320
+ else:
321
+ usrtxt = usrtxt[-cursor:-(cursor+len(kill_ring[prev_kill_ring_index]))]
322
+ usrtxt = usrtxt[:-cursor] + kill_ring[kill_ring_index] + usrtxt[-cursor:]
327
323
 
328
324
  elif key == 3: # ctrl+c
329
325
  # Immediate exit
@@ -9,6 +9,7 @@ License: MIT
9
9
  """
10
10
 
11
11
  import curses
12
+ from listpick.utils import keycodes
12
13
 
13
14
  picker_keys = {
14
15
  "refresh": [curses.KEY_F5],
@@ -25,8 +26,8 @@ picker_keys = {
25
26
  "page_down": [curses.KEY_NPAGE, 6], # Ctrl+f
26
27
  "cursor_bottom": [ord('G'), curses.KEY_END],
27
28
  "cursor_top": [ord('g'), curses.KEY_HOME],
28
- "five_up": [ord('K')],
29
- "five_down": [ord('J')],
29
+ "five_up": [ord('K'), keycodes.META_k],
30
+ "five_down": [ord('J'), keycodes.META_j],
30
31
  "toggle_select": [ord(' ')],
31
32
  "select_all": [ord('m'), 1], # Ctrl-a
32
33
  "select_none": [ord('M'), 18], # Ctrl-r
@@ -135,6 +136,7 @@ notification_keys = {
135
136
  "five_up": [ord('K')],
136
137
  "five_down": [ord('J')],
137
138
  "redraw_screen": [12], # Ctrl-l
139
+ "refresh": [curses.KEY_F5, curses.KEY_RESIZE],
138
140
  }
139
141
 
140
142
 
@@ -0,0 +1,88 @@
1
+ # Set a base value to ensure that we have no collisions with any unicode characters
2
+ BASE_VALUE = 2**32
3
+
4
+ # Define constants for alt+a to alt+z
5
+ META_A = BASE_VALUE + 1000
6
+ META_B = BASE_VALUE + 1001
7
+ META_C = BASE_VALUE + 1002
8
+ META_D = BASE_VALUE + 1003
9
+ META_E = BASE_VALUE + 1004
10
+ META_F = BASE_VALUE + 1005
11
+ META_G = BASE_VALUE + 1006
12
+ META_H = BASE_VALUE + 1007
13
+ META_I = BASE_VALUE + 1008
14
+ META_J = BASE_VALUE + 1009
15
+ META_K = BASE_VALUE + 1010
16
+ META_L = BASE_VALUE + 1011
17
+ META_M = BASE_VALUE + 1012
18
+ META_N = BASE_VALUE + 1013
19
+ META_O = BASE_VALUE + 1014
20
+ META_P = BASE_VALUE + 1015
21
+ META_Q = BASE_VALUE + 1016
22
+ META_R = BASE_VALUE + 1017
23
+ META_S = BASE_VALUE + 1018
24
+ META_T = BASE_VALUE + 1019
25
+ META_U = BASE_VALUE + 1020
26
+ META_V = BASE_VALUE + 1021
27
+ META_W = BASE_VALUE + 1022
28
+ META_X = BASE_VALUE + 1023
29
+ META_Y = BASE_VALUE + 1024
30
+ META_Z = BASE_VALUE + 1025
31
+
32
+ # Define constants for alt+A to alt+Z (using uppercase)
33
+ META_a = BASE_VALUE + 1050
34
+ META_b = BASE_VALUE + 1051
35
+ META_c = BASE_VALUE + 1052
36
+ META_d = BASE_VALUE + 1053
37
+ META_e = BASE_VALUE + 1054
38
+ META_f = BASE_VALUE + 1055
39
+ META_g = BASE_VALUE + 1056
40
+ META_h = BASE_VALUE + 1057
41
+ META_i = BASE_VALUE + 1058
42
+ META_j = BASE_VALUE + 1059
43
+ META_k = BASE_VALUE + 1060
44
+ META_l = BASE_VALUE + 1061
45
+ META_m = BASE_VALUE + 1062
46
+ META_n = BASE_VALUE + 1063
47
+ META_o = BASE_VALUE + 1064
48
+ META_p = BASE_VALUE + 1065
49
+ META_q = BASE_VALUE + 1066
50
+ META_r = BASE_VALUE + 1067
51
+ META_s = BASE_VALUE + 1068
52
+ META_t = BASE_VALUE + 1069
53
+ META_u = BASE_VALUE + 1070
54
+ META_v = BASE_VALUE + 1071
55
+ META_w = BASE_VALUE + 1072
56
+ META_x = BASE_VALUE + 1073
57
+ META_y = BASE_VALUE + 1074
58
+ META_z = BASE_VALUE + 1075
59
+
60
+
61
+ META_0 = BASE_VALUE + 1090
62
+ META_1 = BASE_VALUE + 1091
63
+ META_2 = BASE_VALUE + 1092
64
+ META_3 = BASE_VALUE + 1093
65
+ META_4 = BASE_VALUE + 1094
66
+ META_5 = BASE_VALUE + 1095
67
+ META_6 = BASE_VALUE + 1096
68
+ META_7 = BASE_VALUE + 1097
69
+ META_8 = BASE_VALUE + 1098
70
+ META_9 = BASE_VALUE + 1099
71
+
72
+
73
+ META_BS = BASE_VALUE + 1100
74
+
75
+ # Dictionary to map character to its constant value
76
+ META_KEY_MAP = {
77
+ 'a': META_a, 'b': META_b, 'c': META_c, 'd': META_d, 'e': META_e,
78
+ 'f': META_f, 'g': META_g, 'h': META_h, 'i': META_i, 'j': META_j,
79
+ 'k': META_k, 'l': META_l, 'm': META_m, 'n': META_n, 'o': META_o,
80
+ 'p': META_p, 'q': META_q, 'r': META_r, 's': META_s, 't': META_t,
81
+ 'u': META_u, 'v': META_v, 'w': META_w, 'x': META_x, 'y': META_y,
82
+ 'z': META_z, 'A': META_A, 'B': META_B, 'C': META_C, 'D': META_D,
83
+ 'E': META_E, 'F': META_F, 'G': META_G, 'H': META_H, 'I': META_I,
84
+ 'J': META_J, 'K': META_K, 'L': META_L, 'M': META_M, 'N': META_N,
85
+ 'O': META_O, 'P': META_P, 'Q': META_Q, 'R': META_R, 'S': META_S,
86
+ 'T': META_T, 'U': META_U, 'V': META_V, 'W': META_W, 'X': META_X,
87
+ 'Y': META_Y, 'Z': META_Z
88
+ }
@@ -0,0 +1,104 @@
1
+ from listpick.utils import keycodes
2
+ import os, tty, select, curses
3
+
4
+ def open_tty():
5
+ """ Return a file descriptor for the tty that we are opening"""
6
+ tty_fd = os.open('/dev/tty', os.O_RDONLY)
7
+ tty.setraw(tty_fd)
8
+ return tty_fd
9
+
10
+ def get_char(tty_fd, timeout: float = 0.2, secondary: bool = False) -> int:
11
+ """ Get character from a tty_fd with a timeout. """
12
+ rlist, _, _ = select.select([tty_fd], [], [], timeout)
13
+ if rlist:
14
+ # key = ord(tty_fd.read(1))
15
+ key = ord(os.read(tty_fd, 1))
16
+ if not secondary:
17
+ if key == 27:
18
+ key2 = get_char(tty_fd, timeout=0.01, secondary=True)
19
+ key3 = get_char(tty_fd, timeout=0.01, secondary=True)
20
+ key4 = get_char(tty_fd, timeout=0.01, secondary=True)
21
+ key5 = get_char(tty_fd, timeout=0.01, secondary=True)
22
+ key6 = get_char(tty_fd, timeout=0.01, secondary=True)
23
+
24
+ keys = [key2, key3, key4, key5, key6]
25
+
26
+ key_str = "".join([chr(k) for k in keys if k != -1])
27
+
28
+ ## Arrow Keys
29
+ if key2 == ord('O') and key3 == ord('B'):
30
+ key = curses.KEY_DOWN
31
+ elif key2 == ord('O') and key3 == ord('A'):
32
+ key = curses.KEY_UP
33
+ elif key2 == ord('O') and key3 == ord('D'):
34
+ key = curses.KEY_LEFT
35
+ elif key2 == ord('O') and key3 == ord('C'):
36
+ key = curses.KEY_RIGHT
37
+
38
+ ## Shift+ Tab
39
+ elif key2 == ord('[') and key3 == ord('Z'):
40
+ key = 353
41
+
42
+ ## Home, End, Pgup, Pgdn
43
+ elif key2 == ord('O') and key3 == ord('F'):
44
+ key = curses.KEY_END
45
+ elif key2 == ord('O') and key3 == ord('H'):
46
+ key = curses.KEY_HOME
47
+ elif key2 == ord('[') and key3 == ord('6') and key4 == ord("~"):
48
+ key = curses.KEY_NPAGE
49
+ elif key2 == ord('[') and key3 == ord('5') and key4 == ord("~"):
50
+ key = curses.KEY_PPAGE
51
+
52
+
53
+ # Delete key
54
+ elif key_str == "[3~": ## Delete
55
+ key = curses.KEY_DC
56
+ elif key_str == "[3;2~": ## Shift+Delete
57
+ key = 383
58
+
59
+ ## Function Keys
60
+ elif key2 == ord('O') and key3 == ord('P'):
61
+ key = curses.KEY_F1
62
+ elif key_str == "OQ":
63
+ key = curses.KEY_F2
64
+ elif key_str == "OR":
65
+ key = curses.KEY_F3
66
+ elif key_str == "OS":
67
+ key = curses.KEY_F4
68
+ elif key_str == "[15~":
69
+ key = curses.KEY_F5
70
+ elif key_str == "[17~":
71
+ key = curses.KEY_F6
72
+ elif key_str == "[17~":
73
+ key = curses.KEY_F7
74
+ elif key_str == "[19~":
75
+ key = curses.KEY_F8
76
+ elif key_str == "[20~":
77
+ key = curses.KEY_F9
78
+ elif key_str == "[21~":
79
+ key = curses.KEY_F10
80
+ elif key_str == "[23~":
81
+ key = curses.KEY_F11
82
+ elif key_str == "[24~":
83
+ key = curses.KEY_F12
84
+
85
+ ## Alt+KEY
86
+ elif key2 >= ord('a') and key2 <= ord('z') and key3 == -1: ## Alt+[a-zA-Z]
87
+ key = keycodes.META_a + (key2 - ord('a'))
88
+ elif key2 >= ord('A') and key2 <= ord('Z') and key3 == -1: ## Alt+[a-zA-Z]
89
+ key = keycodes.META_A + (key2 - ord('A'))
90
+ elif key2 == ord('0') and key3 == -1: ## Alt+0
91
+ key = keycodes.META_0
92
+ elif key2 >= ord('1') and key2 <= ord('9') and key3 == -1: ## Alt+1-9
93
+ key = keycodes.META_1 + (key2 - ord('1'))
94
+ elif key2 == 127: ## Alt+BS
95
+ key = keycodes.META_BS
96
+
97
+ # If it is an unknown key with an escape sequence then return -1.
98
+ elif key2 != -1:
99
+ key = -1
100
+
101
+
102
+ else:
103
+ key = -1
104
+ return key
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.14.8
3
+ Version: 0.1.14.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
@@ -35,7 +35,6 @@ src/listpick/ui/footer.py
35
35
  src/listpick/ui/git-bugreport-2025-08-16-1438.txt
36
36
  src/listpick/ui/help_screen.py
37
37
  src/listpick/ui/input_field.py
38
- src/listpick/ui/keycodes.py
39
38
  src/listpick/ui/keys.py
40
39
  src/listpick/ui/pane_stuff.py
41
40
  src/listpick/ui/picker_colours.py
@@ -45,6 +44,7 @@ src/listpick/utils/config.py
45
44
  src/listpick/utils/dump.py
46
45
  src/listpick/utils/filtering.py
47
46
  src/listpick/utils/generate_data.py
47
+ src/listpick/utils/keycodes.py
48
48
  src/listpick/utils/options_selectors.py
49
49
  src/listpick/utils/paste_operations.py
50
50
  src/listpick/utils/picker_log.py
@@ -52,6 +52,7 @@ src/listpick/utils/search_and_filter_utils.py
52
52
  src/listpick/utils/searching.py
53
53
  src/listpick/utils/sorting.py
54
54
  src/listpick/utils/table_to_list_of_lists.py
55
+ src/listpick/utils/user_input.py
55
56
  src/listpick/utils/utils.py
56
57
  tests/kitty_control.sh
57
58
  tests/sorting_dates.csv
@@ -1,70 +0,0 @@
1
- # Define constants for alt+a to alt+z
2
- META_A = 1000
3
- META_B = 1001
4
- META_C = 1002
5
- META_D = 1003
6
- META_E = 1004
7
- META_F = 1005
8
- META_G = 1006
9
- META_H = 1007
10
- META_I = 1008
11
- META_J = 1009
12
- META_K = 1010
13
- META_L = 1011
14
- META_M = 1012
15
- META_N = 1013
16
- META_O = 1014
17
- META_P = 1015
18
- META_Q = 1016
19
- META_R = 1017
20
- META_S = 1018
21
- META_T = 1019
22
- META_U = 1020
23
- META_V = 1021
24
- META_W = 1022
25
- META_X = 1023
26
- META_Y = 1024
27
- META_Z = 1025
28
-
29
- # Define constants for alt+A to alt+Z (using uppercase)
30
- META_a = 1050
31
- META_b = 1051
32
- META_c = 1052
33
- META_d = 1053
34
- META_e = 1054
35
- META_f = 1055
36
- META_g = 1056
37
- META_h = 1057
38
- META_i = 1058
39
- META_j = 1059
40
- META_k = 1060
41
- META_l = 1061
42
- META_m = 1062
43
- META_n = 1063
44
- META_o = 1064
45
- META_p = 1065
46
- META_q = 1066
47
- META_r = 1067
48
- META_s = 1068
49
- META_t = 1069
50
- META_u = 1070
51
- META_v = 1071
52
- META_w = 1072
53
- META_x = 1073
54
- META_y = 1074
55
- META_z = 1075
56
-
57
- # Dictionary to map character to its constant value
58
- META_KEY_MAP = {
59
- 'a': META_a, 'b': META_b, 'c': META_c, 'd': META_d, 'e': META_e,
60
- 'f': META_f, 'g': META_g, 'h': META_h, 'i': META_i, 'j': META_j,
61
- 'k': META_k, 'l': META_l, 'm': META_m, 'n': META_n, 'o': META_o,
62
- 'p': META_p, 'q': META_q, 'r': META_r, 's': META_s, 't': META_t,
63
- 'u': META_u, 'v': META_v, 'w': META_w, 'x': META_x, 'y': META_y,
64
- 'z': META_z, 'A': META_A, 'B': META_B, 'C': META_C, 'D': META_D,
65
- 'E': META_E, 'F': META_F, 'G': META_G, 'H': META_H, 'I': META_I,
66
- 'J': META_J, 'K': META_K, 'L': META_L, 'M': META_M, 'N': META_N,
67
- 'O': META_O, 'P': META_P, 'Q': META_Q, 'R': META_R, 'S': META_S,
68
- 'T': META_T, 'U': META_U, 'V': META_V, 'W': META_W, 'X': META_X,
69
- 'Y': META_Y, 'Z': META_Z
70
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes