listpick 0.1.15.19__py3-none-any.whl → 0.1.16.17__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.
- listpick/listpick_app.py +1235 -660
- listpick/pane/get_data.py +20 -0
- listpick/pane/left_pane_functions.py +198 -0
- listpick/pane/pane_functions.py +45 -5
- listpick/pane/pane_functions_1.py +175 -0
- listpick/pane/pane_utils.py +3 -3
- listpick/ui/build_help.py +37 -12
- listpick/ui/draw_screen.py +0 -0
- listpick/ui/footer.py +3 -3
- listpick/ui/input_field.py +1 -20
- listpick/ui/keys.py +16 -10
- listpick/ui/picker_colours.py +55 -0
- listpick/utils/generate_data.py +45 -2
- listpick/utils/generate_data_multiprocessing.py +10 -0
- listpick/utils/generate_data_multithreaded.py +234 -0
- listpick/utils/generate_data_utils.py +43 -0
- listpick/utils/user_input.py +7 -1
- listpick/utils/utils.py +17 -15
- {listpick-0.1.15.19.dist-info → listpick-0.1.16.17.dist-info}/METADATA +31 -42
- listpick-0.1.16.17.dist-info/RECORD +43 -0
- listpick-0.1.15.19.dist-info/RECORD +0 -37
- {listpick-0.1.15.19.dist-info → listpick-0.1.16.17.dist-info}/WHEEL +0 -0
- {listpick-0.1.15.19.dist-info → listpick-0.1.16.17.dist-info}/entry_points.txt +0 -0
- {listpick-0.1.15.19.dist-info → listpick-0.1.16.17.dist-info}/licenses/LICENSE.txt +0 -0
- {listpick-0.1.15.19.dist-info → listpick-0.1.16.17.dist-info}/top_level.txt +0 -0
listpick/ui/keys.py
CHANGED
|
@@ -51,8 +51,8 @@ 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": [
|
|
55
|
-
"opts_select": [ord('
|
|
54
|
+
"opts_input": [keycodes.META_o],
|
|
55
|
+
# "opts_select": [ord('+')],
|
|
56
56
|
"mode_next": [9], # Tab key
|
|
57
57
|
"mode_prev": [353], # Shift+Tab key
|
|
58
58
|
"pipe_input": [ord('|')],
|
|
@@ -62,13 +62,14 @@ picker_keys = {
|
|
|
62
62
|
"col_select_prev": [ord('<')],
|
|
63
63
|
"col_hide": [ord('!'), ord('@'), ord('#'), ord('$'), ord('%'), ord('^'), ord('&'), ord('*'), ord('('), ord(')')],
|
|
64
64
|
"edit": [ord('e')],
|
|
65
|
-
"edit_picker": [ord('E')],
|
|
65
|
+
# "edit_picker": [ord('E')],
|
|
66
|
+
"edit_nvim": [ord('E')],
|
|
66
67
|
"edit_ipython": [5], # Ctrl+e
|
|
67
68
|
"copy": [ord('y')],
|
|
68
69
|
"paste": [ord('p')],
|
|
69
70
|
"save": [19, ord('D')], # Ctrl+s
|
|
70
71
|
"load": [15], # Ctrl+o
|
|
71
|
-
"open": [ord('O')],
|
|
72
|
+
# "open": [ord('O')],
|
|
72
73
|
"toggle_footer": [ord('_')],
|
|
73
74
|
"notification_toggle": [ord('z')],
|
|
74
75
|
"redo": [ord('.')],
|
|
@@ -90,6 +91,8 @@ picker_keys = {
|
|
|
90
91
|
# "sheet_prev": [],
|
|
91
92
|
"toggle_right_pane": [ord("'")],
|
|
92
93
|
"cycle_right_pane": [ord('"')],
|
|
94
|
+
"toggle_left_pane": [ord(";")],
|
|
95
|
+
"cycle_left_pane": [ord(':')],
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
|
|
@@ -104,8 +107,8 @@ help_keys = {
|
|
|
104
107
|
"page_down": [curses.KEY_NPAGE, 6],
|
|
105
108
|
"cursor_bottom": [ord('G'), curses.KEY_END],
|
|
106
109
|
"cursor_top": [ord('g'), curses.KEY_HOME],
|
|
107
|
-
"five_up": [ord('K')],
|
|
108
|
-
"five_down": [ord('J')],
|
|
110
|
+
"five_up": [ord('K'), keycodes.META_k],
|
|
111
|
+
"five_down": [ord('J'), keycodes.META_j],
|
|
109
112
|
"redraw_screen": [12], # Ctrl+l
|
|
110
113
|
"cycle_sort_method": [ord('s')],
|
|
111
114
|
"cycle_sort_method_reverse": [ord('S')],
|
|
@@ -235,8 +238,8 @@ edit_menu_keys = {
|
|
|
235
238
|
"continue_search_forward": [ord('n'), ord('i')],
|
|
236
239
|
"continue_search_backward": [ord('N'), ord('I')],
|
|
237
240
|
"cancel": [27], # Escape key
|
|
238
|
-
"opts_input": [
|
|
239
|
-
"opts_select": [ord('
|
|
241
|
+
"opts_input": [keycodes.META_o],
|
|
242
|
+
# "opts_select": [ord('+')],
|
|
240
243
|
"mode_next": [9], # Tab key
|
|
241
244
|
"mode_prev": [353], # Shift+Tab key
|
|
242
245
|
"pipe_input": [ord('|')],
|
|
@@ -246,11 +249,14 @@ edit_menu_keys = {
|
|
|
246
249
|
"col_select_prev": [ord('<'), ord('h')],
|
|
247
250
|
"col_hide": [ord('!'), ord('@'), ord('#'), ord('$'), ord('%'), ord('^'), ord('&'), ord('*'), ord('('), ord(')')],
|
|
248
251
|
"edit": [ord('e')],
|
|
249
|
-
"
|
|
252
|
+
"edit_nvim": [ord('E')],
|
|
250
253
|
"edit_ipython": [5], # Ctrl+e
|
|
251
254
|
"copy": [ord('y')],
|
|
252
255
|
"save": [19, ord('D')], # Ctrl+s
|
|
253
256
|
"load": [ord('L'), 15], # Ctrl+o
|
|
254
|
-
"open": [ord('O')],
|
|
257
|
+
# "open": [ord('O')],
|
|
255
258
|
"toggle_footer": [ord('_')],
|
|
259
|
+
"visual_selection_toggle": [ord('v')],
|
|
260
|
+
"visual_deselection_toggle": [ord('V')],
|
|
261
|
+
"toggle_select": [ord(' ')],
|
|
256
262
|
}
|
listpick/ui/picker_colours.py
CHANGED
|
@@ -294,6 +294,61 @@ def get_colours(pick:int=0) -> Dict[str, int]:
|
|
|
294
294
|
'active_column_bg': curses.COLOR_BLACK,
|
|
295
295
|
'active_column_fg': curses.COLOR_WHITE,
|
|
296
296
|
},
|
|
297
|
+
### (6) Use default colors for bg
|
|
298
|
+
# {
|
|
299
|
+
# 'background': -1,
|
|
300
|
+
# 'normal_fg': -1,
|
|
301
|
+
# 'unselected_bg': -1,
|
|
302
|
+
# 'unselected_fg': -1,
|
|
303
|
+
# 'cursor_bg': 21,
|
|
304
|
+
# 'cursor_fg': -1,
|
|
305
|
+
# 'selected_bg': 54,
|
|
306
|
+
# 'selected_fg': -1,
|
|
307
|
+
# 'header_bg': 255,
|
|
308
|
+
# 'header_fg': 232,
|
|
309
|
+
# 'error_bg': 232,
|
|
310
|
+
# 'error_fg': curses.COLOR_RED,
|
|
311
|
+
# 'complete_bg': 232,
|
|
312
|
+
# 'complete_fg': 82,
|
|
313
|
+
# 'waiting_bg': 232,
|
|
314
|
+
# 'waiting_fg': curses.COLOR_YELLOW,
|
|
315
|
+
# 'active_bg': 232,
|
|
316
|
+
# 'active_fg': 33,
|
|
317
|
+
# 'paused_bg': -1,
|
|
318
|
+
# 'paused_fg': 244,
|
|
319
|
+
# 'search_bg': 162,
|
|
320
|
+
# 'search_fg': -1,
|
|
321
|
+
# 'active_input_bg': -1,
|
|
322
|
+
# 'active_input_fg': 232,
|
|
323
|
+
# 'modes_selected_bg': 232,
|
|
324
|
+
# 'modes_selected_fg': -1,
|
|
325
|
+
# 'modes_unselected_bg': 255,
|
|
326
|
+
# 'modes_unselected_fg': 232,
|
|
327
|
+
# 'title_bar': 232,
|
|
328
|
+
# 'title_bg': 232,
|
|
329
|
+
# 'title_fg': -1,
|
|
330
|
+
# 'scroll_bar_bg': 247,
|
|
331
|
+
# 'selected_header_column_bg': 232,
|
|
332
|
+
# 'selected_header_column_fg': -1,
|
|
333
|
+
# 'unselected_header_column_bg': -1,
|
|
334
|
+
# 'unselected_header_column_fg': -1,
|
|
335
|
+
# 'footer_bg': 232,
|
|
336
|
+
# 'footer_fg': -1,
|
|
337
|
+
# 'refreshing_bg': 232,
|
|
338
|
+
# 'refreshing_fg': -1,
|
|
339
|
+
# 'refreshing_inactive_bg': 232,
|
|
340
|
+
# 'refreshing_inactive_fg': 232,
|
|
341
|
+
# '40pc_bg': 232,
|
|
342
|
+
# '40pc_fg': 166,
|
|
343
|
+
# 'footer_string_bg': 232,
|
|
344
|
+
# 'footer_string_fg': -1,
|
|
345
|
+
# 'selected_cell_bg': 54,
|
|
346
|
+
# 'selected_cell_fg': -1,
|
|
347
|
+
# 'deselecting_cell_bg': 162,
|
|
348
|
+
# 'deselecting_cell_fg': -1,
|
|
349
|
+
# 'active_column_bg': 234,
|
|
350
|
+
# 'active_column_fg': -1,
|
|
351
|
+
# },
|
|
297
352
|
]
|
|
298
353
|
for colour in colours:
|
|
299
354
|
colour["20pc_bg"] = colour["background"]
|
listpick/utils/generate_data.py
CHANGED
|
@@ -15,8 +15,45 @@ import toml
|
|
|
15
15
|
import logging
|
|
16
16
|
|
|
17
17
|
logger = logging.getLogger('picker_log')
|
|
18
|
+
import concurrent.futures
|
|
19
|
+
import re
|
|
18
20
|
|
|
19
21
|
def generate_columns(funcs: list, files: list) -> list[list[str]]:
|
|
22
|
+
"""
|
|
23
|
+
Takes a list of functions and a list of files.
|
|
24
|
+
Tasks are run in parallel using concurrent.futures.
|
|
25
|
+
"""
|
|
26
|
+
logger.info("function: generate_columns (generate_data.py)")
|
|
27
|
+
|
|
28
|
+
results = []
|
|
29
|
+
# Create a future object for each combination of func and file
|
|
30
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
31
|
+
futures = [[executor.submit(func, file) for func in funcs] for file in files]
|
|
32
|
+
|
|
33
|
+
for file_futures in futures:
|
|
34
|
+
result = [future.result() for future in file_futures]
|
|
35
|
+
results.append(result)
|
|
36
|
+
return results
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def generate_columns_multithread(funcs: list, files: list) -> list[list[str]]:
|
|
40
|
+
"""
|
|
41
|
+
Takes a list of functions and a list of files.
|
|
42
|
+
Tasks are run in parallel using concurrent.futures.
|
|
43
|
+
"""
|
|
44
|
+
logger.info("function: generate_columns (generate_data.py)")
|
|
45
|
+
|
|
46
|
+
results = []
|
|
47
|
+
# Create a future object for each combination of func and file
|
|
48
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
49
|
+
futures = [[executor.submit(func, file) for func in funcs] for file in files]
|
|
50
|
+
|
|
51
|
+
for file_futures in futures:
|
|
52
|
+
result = [future.result() for future in file_futures]
|
|
53
|
+
results.append(result)
|
|
54
|
+
return results
|
|
55
|
+
|
|
56
|
+
def generate_columns_single_thread(funcs: list, files: list) -> list[list[str]]:
|
|
20
57
|
"""
|
|
21
58
|
Takes a list of functions and a list of files. Each function is run for each file and a list of lists is returned.
|
|
22
59
|
"""
|
|
@@ -30,7 +67,7 @@ def generate_columns(funcs: list, files: list) -> list[list[str]]:
|
|
|
30
67
|
except:
|
|
31
68
|
item.append("")
|
|
32
69
|
items.append(item)
|
|
33
|
-
|
|
70
|
+
|
|
34
71
|
return items
|
|
35
72
|
|
|
36
73
|
def command_to_func(command: str) -> Callable:
|
|
@@ -43,7 +80,7 @@ def command_to_func(command: str) -> Callable:
|
|
|
43
80
|
"""
|
|
44
81
|
logger.info("function: command_to_func (generate_data.py)")
|
|
45
82
|
|
|
46
|
-
func = lambda arg: subprocess.run(command
|
|
83
|
+
func = lambda arg: subprocess.run(replace_braces(command, repr(arg)), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8").strip()
|
|
47
84
|
return func
|
|
48
85
|
|
|
49
86
|
def load_environment(envs:dict):
|
|
@@ -55,6 +92,12 @@ def load_environment(envs:dict):
|
|
|
55
92
|
if "cwd" in envs:
|
|
56
93
|
os.chdir(os.path.expandvars(os.path.expanduser(envs["cwd"])))
|
|
57
94
|
|
|
95
|
+
def replace_braces(text, s):
|
|
96
|
+
text = re.sub(r'\{\{(.*?)\}\}', r'@@\1@@', text)
|
|
97
|
+
text = re.sub(r'\{\}', s, text)
|
|
98
|
+
text = re.sub(r'@@(.*?)@@', r'{{\1}}', text)
|
|
99
|
+
return text
|
|
100
|
+
|
|
58
101
|
|
|
59
102
|
def read_toml(file_path) -> Tuple[dict, list, list]:
|
|
60
103
|
"""
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
generate_data_multithreaded.py
|
|
5
|
+
Generate data for listpick Picker.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
1. Read toml file.
|
|
9
|
+
2. Set environment variables.
|
|
10
|
+
3. Get files from first command.
|
|
11
|
+
4. Create items with "..." for cells to be filled.
|
|
12
|
+
5. Create a priority queue which determines which cells are to be filled first.
|
|
13
|
+
6. Create a queue updater which increases the priorty of cells which are on screen which does so each second.
|
|
14
|
+
7. Create threads to start generating data for cells.
|
|
15
|
+
|
|
16
|
+
Author: GrimAndGreedy
|
|
17
|
+
License: MIT
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import subprocess
|
|
21
|
+
import os
|
|
22
|
+
from typing import Tuple, Callable
|
|
23
|
+
import toml
|
|
24
|
+
import logging
|
|
25
|
+
import threading
|
|
26
|
+
from queue import PriorityQueue
|
|
27
|
+
import time
|
|
28
|
+
import re
|
|
29
|
+
import shlex
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger('picker_log')
|
|
32
|
+
|
|
33
|
+
def generate_columns_worker(
|
|
34
|
+
funcs: list,
|
|
35
|
+
files: list,
|
|
36
|
+
items: list[list[str]],
|
|
37
|
+
getting_data: threading.Event,
|
|
38
|
+
task_queue: PriorityQueue,
|
|
39
|
+
completed_cells: set,
|
|
40
|
+
state: dict,
|
|
41
|
+
) -> None:
|
|
42
|
+
""" Get a task from the priorty queue and fill the data for that cell."""
|
|
43
|
+
while task_queue.qsize() > 0 and not state["thread_stop_event"].is_set():
|
|
44
|
+
_, (i, j) = task_queue.get()
|
|
45
|
+
|
|
46
|
+
if (i, j) in completed_cells:
|
|
47
|
+
task_queue.task_done()
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
if state["thread_stop_event"].is_set():
|
|
51
|
+
with task_queue.mutex:
|
|
52
|
+
task_queue.queue.clear()
|
|
53
|
+
|
|
54
|
+
generate_cell(
|
|
55
|
+
func=funcs[j],
|
|
56
|
+
file=files[i],
|
|
57
|
+
items=items,
|
|
58
|
+
row=i,
|
|
59
|
+
col=j+1,
|
|
60
|
+
state=state,
|
|
61
|
+
)
|
|
62
|
+
completed_cells.add((i, j))
|
|
63
|
+
task_queue.task_done()
|
|
64
|
+
getting_data.set()
|
|
65
|
+
|
|
66
|
+
def generate_cell(func: Callable, file: str, items: list[list[str]], row: int, col: int, state: dict) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Takes a function, file and a file and then sets items[row][col] to the result.
|
|
69
|
+
"""
|
|
70
|
+
if not state["thread_stop_event"].is_set():
|
|
71
|
+
try:
|
|
72
|
+
result = func(file).strip()
|
|
73
|
+
if not state["thread_stop_event"].is_set():
|
|
74
|
+
items[row][col] = result
|
|
75
|
+
except Exception as e:
|
|
76
|
+
pass
|
|
77
|
+
# import pyperclip
|
|
78
|
+
# pyperclip.copy(f"({row}, {col}): len(items)={len(items)}, len(items[0])={len(items[0])} {e}")
|
|
79
|
+
|
|
80
|
+
def update_queue(task_queue: PriorityQueue, visible_rows_indices: list[int], rows: int, cols: int, state: dict):
|
|
81
|
+
""" Increase the priority of getting the data for the cells that are currently visible. """
|
|
82
|
+
while task_queue.qsize() > 0:
|
|
83
|
+
time.sleep(0.1)
|
|
84
|
+
if state["thread_stop_event"].is_set():
|
|
85
|
+
with task_queue.mutex:
|
|
86
|
+
task_queue.queue.clear()
|
|
87
|
+
break
|
|
88
|
+
for row in visible_rows_indices:
|
|
89
|
+
for col in range(cols):
|
|
90
|
+
if state["generate_data_for_hidden_columns"] == False and col+1 in state["hidden_columns"]: continue
|
|
91
|
+
if 0 <= row < rows:
|
|
92
|
+
task_queue.put((1, (row, col)))
|
|
93
|
+
|
|
94
|
+
# Delay
|
|
95
|
+
time.sleep(0.9)
|
|
96
|
+
|
|
97
|
+
def command_to_func(command: str) -> Callable:
|
|
98
|
+
"""
|
|
99
|
+
Convert a command string to a function that will run the command.
|
|
100
|
+
|
|
101
|
+
E.g.,
|
|
102
|
+
mediainfo {} | grep -i format
|
|
103
|
+
mediainfo {} | grep -i format | head -n 1 | awk '{{print $3}}'
|
|
104
|
+
"""
|
|
105
|
+
logger.info("function: command_to_func (generate_data.py)")
|
|
106
|
+
|
|
107
|
+
func = lambda arg: subprocess.run(replace_braces(command, arg), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8").strip()
|
|
108
|
+
return func
|
|
109
|
+
|
|
110
|
+
def load_environment(envs:dict):
|
|
111
|
+
"""
|
|
112
|
+
Load environment variables from an envs dict.
|
|
113
|
+
"""
|
|
114
|
+
logger.info("function: load_environment (generate_data.py)")
|
|
115
|
+
|
|
116
|
+
if "cwd" in envs:
|
|
117
|
+
os.chdir(os.path.expandvars(os.path.expanduser(envs["cwd"])))
|
|
118
|
+
|
|
119
|
+
def replace_braces(text, s):
|
|
120
|
+
text = re.sub(r'\{\{(.*?)\}\}', r'@@\1@@', text)
|
|
121
|
+
text = re.sub(r'\{\}', shlex.quote(s), text)
|
|
122
|
+
text = re.sub(r'@@(.*?)@@', r'{{\1}}', text)
|
|
123
|
+
return text
|
|
124
|
+
|
|
125
|
+
def read_toml(file_path) -> Tuple[dict, list, list]:
|
|
126
|
+
"""
|
|
127
|
+
Read toml file and return the environment, commands and header sections.
|
|
128
|
+
"""
|
|
129
|
+
logger.info("function: read_toml (generate_data.py)")
|
|
130
|
+
with open(file_path, 'r') as file:
|
|
131
|
+
config = toml.load(file)
|
|
132
|
+
|
|
133
|
+
environment = config['environment'] if 'environment' in config else {}
|
|
134
|
+
data = config['data'] if 'data' in config else {}
|
|
135
|
+
commands = [command.strip() for command in data['commands']] if 'commands' in data else []
|
|
136
|
+
header = [header for header in data['header']] if 'header' in data else []
|
|
137
|
+
return environment, commands, header
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def generate_picker_data_from_file(
|
|
141
|
+
file_path: str,
|
|
142
|
+
items,
|
|
143
|
+
header,
|
|
144
|
+
visible_rows_indices,
|
|
145
|
+
getting_data,
|
|
146
|
+
state,
|
|
147
|
+
|
|
148
|
+
) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Generate data for Picker based upon the toml file commands.
|
|
151
|
+
"""
|
|
152
|
+
logger.info("function: generate_picker_data (generate_data.py)")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
environment, lines, hdr = read_toml(file_path)
|
|
156
|
+
|
|
157
|
+
# Load any environment variables from the toml file
|
|
158
|
+
if environment:
|
|
159
|
+
load_environment(environment)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Get list of files to be displayed in the first column.
|
|
163
|
+
get_files_command = lines[0]
|
|
164
|
+
files = subprocess.run(get_files_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8").strip().split("\n")
|
|
165
|
+
files = [file.strip() for file in files if files]
|
|
166
|
+
|
|
167
|
+
commands_list = [line.strip() for line in lines[1:]]
|
|
168
|
+
command_funcs = [command_to_func(command) for command in commands_list]
|
|
169
|
+
|
|
170
|
+
generate_picker_data(
|
|
171
|
+
files = files,
|
|
172
|
+
column_functions = command_funcs,
|
|
173
|
+
data_header = hdr,
|
|
174
|
+
items = items,
|
|
175
|
+
picker_header = header,
|
|
176
|
+
visible_rows_indices = visible_rows_indices,
|
|
177
|
+
getting_data = getting_data,
|
|
178
|
+
state=state,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def generate_picker_data(
|
|
182
|
+
files: list[str],
|
|
183
|
+
column_functions: list[Callable],
|
|
184
|
+
data_header,
|
|
185
|
+
items,
|
|
186
|
+
picker_header,
|
|
187
|
+
visible_rows_indices,
|
|
188
|
+
getting_data,
|
|
189
|
+
state,
|
|
190
|
+
) -> None:
|
|
191
|
+
"""
|
|
192
|
+
Generate data from a list of files and a list of column functions which will be used to
|
|
193
|
+
generate subsequent columns.
|
|
194
|
+
|
|
195
|
+
This function is performed asynchronously with os.cpu_count() threads.
|
|
196
|
+
|
|
197
|
+
data_header: header list to be set for the picker
|
|
198
|
+
picker_header: the picker header will be passed in so that it can be set for the class
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
logger.info("function: generate_picker_data (generate_data.py)")
|
|
202
|
+
|
|
203
|
+
items.clear()
|
|
204
|
+
items.extend([[file] + ["..." for _ in column_functions] for file in files])
|
|
205
|
+
picker_header[:] = data_header
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
task_queue = state["data_generation_queue"]
|
|
209
|
+
for i in range(len(files)):
|
|
210
|
+
for j in range(len(column_functions)):
|
|
211
|
+
if state["generate_data_for_hidden_columns"] == False and j+1 in state["hidden_columns"]: continue
|
|
212
|
+
task_queue.put((10, (i, j)))
|
|
213
|
+
|
|
214
|
+
num_workers = os.cpu_count()
|
|
215
|
+
if num_workers in [None, -1]: num_workers = 4
|
|
216
|
+
if num_workers == None or num_workers < 1: num_workers = 1
|
|
217
|
+
completed_cells = set()
|
|
218
|
+
|
|
219
|
+
for _ in range(num_workers):
|
|
220
|
+
gen_items_thread = threading.Thread(
|
|
221
|
+
target=generate_columns_worker,
|
|
222
|
+
args=(column_functions, files, items, getting_data, task_queue, completed_cells, state),
|
|
223
|
+
)
|
|
224
|
+
state["threads"].append(gen_items_thread)
|
|
225
|
+
gen_items_thread.daemon = True
|
|
226
|
+
gen_items_thread.start()
|
|
227
|
+
|
|
228
|
+
update_queue_thread = threading.Thread(
|
|
229
|
+
target=update_queue,
|
|
230
|
+
args=(task_queue, visible_rows_indices, len(files), len(column_functions), state),
|
|
231
|
+
)
|
|
232
|
+
state["threads"].append(update_queue_thread)
|
|
233
|
+
update_queue_thread.daemon = True
|
|
234
|
+
update_queue_thread.start()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
generate_data_utils.py
|
|
5
|
+
|
|
6
|
+
Author: GrimAndGreedy
|
|
7
|
+
License: MIT
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def sort_priority_first(element):
|
|
11
|
+
return element[0]
|
|
12
|
+
|
|
13
|
+
class ProcessSafePriorityQueue:
|
|
14
|
+
def __init__(self, manager):
|
|
15
|
+
self.data = manager.list()
|
|
16
|
+
self.lock = manager.Lock()
|
|
17
|
+
|
|
18
|
+
def put(self, item):
|
|
19
|
+
with self.lock:
|
|
20
|
+
self.data.append(item)
|
|
21
|
+
self.data.sort(key=sort_priority_first)
|
|
22
|
+
|
|
23
|
+
def get(self, timeout=None):
|
|
24
|
+
start = time.time()
|
|
25
|
+
while True:
|
|
26
|
+
with self.lock:
|
|
27
|
+
if self.data:
|
|
28
|
+
return self.data.pop(0)
|
|
29
|
+
if timeout is not None and (time.time() - start) > timeout:
|
|
30
|
+
raise IndexError("get timeout")
|
|
31
|
+
time.sleep(0.01)
|
|
32
|
+
|
|
33
|
+
def qsize(self):
|
|
34
|
+
with self.lock:
|
|
35
|
+
return len(self.data)
|
|
36
|
+
|
|
37
|
+
def empty(self):
|
|
38
|
+
with self.lock:
|
|
39
|
+
return len(self.data) == 0
|
|
40
|
+
|
|
41
|
+
def clear(self):
|
|
42
|
+
with self.lock:
|
|
43
|
+
self.data[:] = []
|
listpick/utils/user_input.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
from listpick.utils import keycodes
|
|
2
2
|
import os, tty, select, curses
|
|
3
|
+
import termios
|
|
3
4
|
|
|
4
5
|
def open_tty():
|
|
5
6
|
""" Return a file descriptor for the tty that we are opening"""
|
|
6
7
|
tty_fd = os.open('/dev/tty', os.O_RDONLY)
|
|
8
|
+
old_terminal_settings = termios.tcgetattr(tty_fd)
|
|
7
9
|
tty.setraw(tty_fd)
|
|
8
|
-
return tty_fd
|
|
10
|
+
return tty_fd, old_terminal_settings
|
|
11
|
+
|
|
12
|
+
def restore_terminal_settings(tty_fd, old_settings):
|
|
13
|
+
""" Restore the terminal to its previous state """
|
|
14
|
+
termios.tcsetattr(tty_fd, termios.TCSADRAIN, old_settings)
|
|
9
15
|
|
|
10
16
|
def get_char(tty_fd, timeout: float = 0.2, secondary: bool = False) -> int:
|
|
11
17
|
""" Get character from a tty_fd with a timeout. """
|
listpick/utils/utils.py
CHANGED
|
@@ -14,9 +14,14 @@ import tempfile
|
|
|
14
14
|
import os
|
|
15
15
|
from typing import Tuple, Dict
|
|
16
16
|
import logging
|
|
17
|
+
import shlex
|
|
18
|
+
from collections import defaultdict
|
|
19
|
+
import time
|
|
17
20
|
|
|
18
21
|
logger = logging.getLogger('picker_log')
|
|
19
22
|
|
|
23
|
+
|
|
24
|
+
|
|
20
25
|
def clip_left(text, n):
|
|
21
26
|
"""
|
|
22
27
|
Clips the first `n` display-width characters from the left of the input string.
|
|
@@ -98,7 +103,12 @@ def format_row(row: list[str], hidden_columns: list, column_widths: list[int], s
|
|
|
98
103
|
|
|
99
104
|
def get_column_widths(items: list[list[str]], header: list[str]=[], max_column_width:int=70, number_columns:bool=True, max_total_width=-1, separator = " ", unicode_char_width: bool = True) -> list[int]:
|
|
100
105
|
""" Calculate maximum width of each column with clipping. """
|
|
101
|
-
if len(items) == 0: return [0]
|
|
106
|
+
if len(items) == 0 and len(header) == 0: return [0]
|
|
107
|
+
elif len(items) == 0:
|
|
108
|
+
header_widths = [wcswidth(f"{i}. {str(h)}") if number_columns else wcswidth(str(h)) for i, h in enumerate(header)]
|
|
109
|
+
col_widths = [min(max_column_width, header_widths[i]) for i in range(len(header))]
|
|
110
|
+
return col_widths
|
|
111
|
+
|
|
102
112
|
assert len(items) > 0
|
|
103
113
|
widths = [max(wcswidth(str(row[i])) for row in items) for i in range(len(items[0]))]
|
|
104
114
|
# widths = [max(len(str(row[i])) for row in items) for i in range(len(items[0]))]
|
|
@@ -204,17 +214,11 @@ def get_selected_cells(cell_selections: Dict[Tuple[int, int], bool]) -> list[Tup
|
|
|
204
214
|
def get_selected_cells_by_row(cell_selections: dict[tuple[int, int], bool]) -> dict[int, list[int]]:
|
|
205
215
|
""" {0: [1,2], 9: [1] }"""
|
|
206
216
|
|
|
207
|
-
d =
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
d[tup[0]].append(tup[1])
|
|
213
|
-
else:
|
|
214
|
-
d[tup[0]] = [tup[1]]
|
|
215
|
-
except:
|
|
216
|
-
pass
|
|
217
|
-
return d
|
|
217
|
+
d = defaultdict(list)
|
|
218
|
+
for (row, col), selected in cell_selections.items():
|
|
219
|
+
if selected:
|
|
220
|
+
d[row].append(col)
|
|
221
|
+
return dict(d)
|
|
218
222
|
|
|
219
223
|
def get_selected_values(items: list[list[str]], selections: dict[int, bool]) -> list[list[str]]:
|
|
220
224
|
""" Return a list of rows based on wich are True in the selections dictionary. """
|
|
@@ -290,9 +294,7 @@ def openFiles(files: list[str]) -> str:
|
|
|
290
294
|
apps[app] = [t]
|
|
291
295
|
|
|
292
296
|
return apps
|
|
293
|
-
for
|
|
294
|
-
if ' ' in files[i] and files[i][0] not in ["'", '"']:
|
|
295
|
-
files[i] = repr(files[i])
|
|
297
|
+
files = [shlex.quote(file) for file in files]
|
|
296
298
|
|
|
297
299
|
types = get_mime_types(files)
|
|
298
300
|
apps = get_applications(types.keys())
|