kopipasta 0.34.0__py3-none-any.whl → 0.35.0__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 kopipasta might be problematic. Click here for more details.
- kopipasta/tree_selector.py +80 -30
- {kopipasta-0.34.0.dist-info → kopipasta-0.35.0.dist-info}/METADATA +1 -1
- {kopipasta-0.34.0.dist-info → kopipasta-0.35.0.dist-info}/RECORD +7 -7
- {kopipasta-0.34.0.dist-info → kopipasta-0.35.0.dist-info}/LICENSE +0 -0
- {kopipasta-0.34.0.dist-info → kopipasta-0.35.0.dist-info}/WHEEL +0 -0
- {kopipasta-0.34.0.dist-info → kopipasta-0.35.0.dist-info}/entry_points.txt +0 -0
- {kopipasta-0.34.0.dist-info → kopipasta-0.35.0.dist-info}/top_level.txt +0 -0
kopipasta/tree_selector.py
CHANGED
|
@@ -20,9 +20,12 @@ class FileNode:
|
|
|
20
20
|
self.parent = parent
|
|
21
21
|
self.children: List['FileNode'] = []
|
|
22
22
|
self.expanded = False
|
|
23
|
-
# This flag marks the invisible root of the file tree, which is not meant to be displayed.
|
|
24
23
|
self.is_scan_root = is_scan_root
|
|
24
|
+
# Base size (for files) or initial placeholder (for dirs)
|
|
25
25
|
self.size = 0 if is_dir else os.path.getsize(self.path)
|
|
26
|
+
# New attributes for caching the results of a deep scan
|
|
27
|
+
self.total_size: int = self.size
|
|
28
|
+
self.is_scanned: bool = not self.is_dir
|
|
26
29
|
|
|
27
30
|
@property
|
|
28
31
|
def name(self):
|
|
@@ -48,7 +51,41 @@ class TreeSelector:
|
|
|
48
51
|
self.char_count = 0
|
|
49
52
|
self.quit_selection = False
|
|
50
53
|
self.viewport_offset = 0 # First visible item index
|
|
54
|
+
self._metrics_cache: Dict[str, Tuple[int, int]] = {}
|
|
55
|
+
|
|
56
|
+
def _calculate_directory_metrics(self, node: FileNode) -> Tuple[int, int]:
|
|
57
|
+
"""Recursively calculate total and selected size for a directory."""
|
|
58
|
+
if not node.is_dir:
|
|
59
|
+
return 0, 0
|
|
51
60
|
|
|
61
|
+
# Use instance cache for this render cycle
|
|
62
|
+
if node.path in self._metrics_cache:
|
|
63
|
+
return self._metrics_cache[node.path]
|
|
64
|
+
|
|
65
|
+
total_size = 0
|
|
66
|
+
selected_size = 0
|
|
67
|
+
|
|
68
|
+
# Ensure directory is scanned
|
|
69
|
+
if not node.children:
|
|
70
|
+
self._deep_scan_directory_and_calc_size(node.path, node)
|
|
71
|
+
|
|
72
|
+
for child in node.children:
|
|
73
|
+
if child.is_dir:
|
|
74
|
+
child_total, child_selected = self._calculate_directory_metrics(child)
|
|
75
|
+
total_size += child_total
|
|
76
|
+
selected_size += child_selected
|
|
77
|
+
else: # It's a file
|
|
78
|
+
total_size += child.size
|
|
79
|
+
if child.path in self.selected_files:
|
|
80
|
+
is_snippet, _ = self.selected_files[child.path]
|
|
81
|
+
if is_snippet:
|
|
82
|
+
selected_size += len(get_file_snippet(child.path))
|
|
83
|
+
else:
|
|
84
|
+
selected_size += child.size
|
|
85
|
+
|
|
86
|
+
self._metrics_cache[node.path] = (total_size, selected_size)
|
|
87
|
+
return total_size, selected_size
|
|
88
|
+
|
|
52
89
|
def build_tree(self, paths: List[str]) -> FileNode:
|
|
53
90
|
"""Build tree structure from given paths."""
|
|
54
91
|
# If one directory is given, make its contents the top level of the tree.
|
|
@@ -56,7 +93,7 @@ class TreeSelector:
|
|
|
56
93
|
root_path = os.path.abspath(paths[0])
|
|
57
94
|
root = FileNode(root_path, True, is_scan_root=True)
|
|
58
95
|
root.expanded = True
|
|
59
|
-
self.
|
|
96
|
+
self._deep_scan_directory_and_calc_size(root_path, root)
|
|
60
97
|
return root
|
|
61
98
|
|
|
62
99
|
# Otherwise, create a virtual root to hold multiple items (e.g., `kopipasta file.py dir/`).
|
|
@@ -79,7 +116,7 @@ class TreeSelector:
|
|
|
79
116
|
|
|
80
117
|
return root
|
|
81
118
|
|
|
82
|
-
def
|
|
119
|
+
def _deep_scan_directory_and_calc_size(self, dir_path: str, parent_node: FileNode):
|
|
83
120
|
"""Recursively scan directory and build tree"""
|
|
84
121
|
abs_dir_path = os.path.abspath(dir_path)
|
|
85
122
|
|
|
@@ -138,7 +175,7 @@ class TreeSelector:
|
|
|
138
175
|
result.append((node, level))
|
|
139
176
|
if node.is_dir and node.expanded:
|
|
140
177
|
if not node.children:
|
|
141
|
-
self.
|
|
178
|
+
self._deep_scan_directory_and_calc_size(node.path, node)
|
|
142
179
|
for child in node.children:
|
|
143
180
|
result.extend(self._flatten_tree(child, level + 1))
|
|
144
181
|
|
|
@@ -146,6 +183,8 @@ class TreeSelector:
|
|
|
146
183
|
|
|
147
184
|
def _build_display_tree(self) -> Tree:
|
|
148
185
|
"""Build Rich tree for display with viewport"""
|
|
186
|
+
self._metrics_cache = {} # Clear cache for each new render
|
|
187
|
+
|
|
149
188
|
# Get terminal size
|
|
150
189
|
_, term_height = shutil.get_terminal_size()
|
|
151
190
|
|
|
@@ -177,10 +216,9 @@ class TreeSelector:
|
|
|
177
216
|
if self.viewport_offset > 0:
|
|
178
217
|
tree_title += f" ↑ ({self.viewport_offset} more)"
|
|
179
218
|
|
|
180
|
-
tree = Tree(tree_title
|
|
219
|
+
tree = Tree(tree_title)
|
|
181
220
|
|
|
182
221
|
# Build tree structure - only for visible portion
|
|
183
|
-
node_map = {}
|
|
184
222
|
viewport_end = min(len(flat_tree), self.viewport_offset + available_height)
|
|
185
223
|
|
|
186
224
|
# Track what level each visible item is at for proper tree structure
|
|
@@ -193,30 +231,35 @@ class TreeSelector:
|
|
|
193
231
|
is_current = i == self.current_index
|
|
194
232
|
style = "bold cyan" if is_current else ""
|
|
195
233
|
|
|
234
|
+
label = Text()
|
|
235
|
+
|
|
196
236
|
if node.is_dir:
|
|
197
237
|
icon = "📂" if node.expanded else "📁"
|
|
198
|
-
|
|
199
|
-
|
|
238
|
+
total_size, selected_size = self._calculate_directory_metrics(node)
|
|
239
|
+
if total_size > 0:
|
|
240
|
+
size_str = f" ({get_human_readable_size(selected_size)} / {get_human_readable_size(total_size)})"
|
|
241
|
+
else:
|
|
242
|
+
size_str = "" # Don't show size for empty dirs
|
|
243
|
+
|
|
244
|
+
# Omit the selection circle for directories
|
|
245
|
+
label.append(f"{icon} {node.name}{size_str}", style=style)
|
|
246
|
+
|
|
247
|
+
else: # It's a file
|
|
200
248
|
icon = "📄"
|
|
201
249
|
size_str = f" ({get_human_readable_size(node.size)})"
|
|
202
250
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
251
|
+
# File selection indicator
|
|
252
|
+
abs_path = os.path.abspath(node.path)
|
|
253
|
+
if abs_path in self.selected_files:
|
|
254
|
+
is_snippet, _ = self.selected_files[abs_path]
|
|
255
|
+
selection = "◐" if is_snippet else "●"
|
|
256
|
+
style = "green " + style
|
|
209
257
|
else:
|
|
210
|
-
selection = "
|
|
211
|
-
style = "green " + style
|
|
212
|
-
else:
|
|
213
|
-
selection = "○"
|
|
258
|
+
selection = "○"
|
|
214
259
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
label.append(f"{icon} {node.name}{size_str}", style=style)
|
|
219
|
-
|
|
260
|
+
label.append(f"{selection} ", style="dim")
|
|
261
|
+
label.append(f"{icon} {node.name}{size_str}", style=style)
|
|
262
|
+
|
|
220
263
|
# Add to tree at correct level
|
|
221
264
|
if level == 0:
|
|
222
265
|
tree_node = tree.add(label)
|
|
@@ -230,10 +273,17 @@ class TreeSelector:
|
|
|
230
273
|
level_stacks[level] = tree_node
|
|
231
274
|
else:
|
|
232
275
|
# Fallback - add to root with indentation indicator
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
276
|
+
indent_text = " " * level
|
|
277
|
+
if not node.is_dir:
|
|
278
|
+
# Re-add file selection marker for indented fallback
|
|
279
|
+
selection_char = "○"
|
|
280
|
+
if node.path in self.selected_files:
|
|
281
|
+
selection_char = "◐" if self.selected_files[node.path][0] else "●"
|
|
282
|
+
indent_text += f"{selection_char} "
|
|
283
|
+
|
|
284
|
+
# Create a new label with proper indentation for this edge case
|
|
285
|
+
fallback_label_text = f"{indent_text}{label.plain}"
|
|
286
|
+
tree_node = tree.add(Text(fallback_label_text, style=style))
|
|
237
287
|
level_stacks[level] = tree_node
|
|
238
288
|
|
|
239
289
|
# Add scroll indicator at bottom if needed
|
|
@@ -344,7 +394,7 @@ q: Quit and finalize"""
|
|
|
344
394
|
|
|
345
395
|
# Ensure children are loaded
|
|
346
396
|
if not node.children:
|
|
347
|
-
self.
|
|
397
|
+
self._deep_scan_directory_and_calc_size(node.path, node)
|
|
348
398
|
|
|
349
399
|
# Collect all files recursively
|
|
350
400
|
all_files = []
|
|
@@ -353,7 +403,7 @@ q: Quit and finalize"""
|
|
|
353
403
|
if n.is_dir:
|
|
354
404
|
# CRITICAL FIX: Ensure sub-directory children are loaded before recursing
|
|
355
405
|
if not n.children:
|
|
356
|
-
self.
|
|
406
|
+
self._deep_scan_directory_and_calc_size(n.path, n)
|
|
357
407
|
for child in n.children:
|
|
358
408
|
collect_files(child)
|
|
359
409
|
else:
|
|
@@ -479,7 +529,7 @@ q: Quit and finalize"""
|
|
|
479
529
|
node.expanded = True
|
|
480
530
|
# Ensure children are loaded
|
|
481
531
|
if not node.children:
|
|
482
|
-
self.
|
|
532
|
+
self._deep_scan_directory_and_calc_size(node.path, node)
|
|
483
533
|
found = True
|
|
484
534
|
break
|
|
485
535
|
|
|
@@ -4,10 +4,10 @@ kopipasta/file.py,sha256=tPCLNvSXHzIvAXLB6fgJswLpCFce7T1QnbNdSWHbIso,4829
|
|
|
4
4
|
kopipasta/import_parser.py,sha256=yLzkMlQm2avKjfqcpMY0PxbA_2ihV9gSYJplreWIPEQ,12424
|
|
5
5
|
kopipasta/main.py,sha256=YzzEnDph1v3NojHYIjvD3Z4blnIEX5zqj9W6gNLlA7E,50058
|
|
6
6
|
kopipasta/prompt.py,sha256=fOCuJVTLUfR0fjKf5qIlnl_3pNsKNKsvt3C8f4tsmxk,6889
|
|
7
|
-
kopipasta/tree_selector.py,sha256=
|
|
8
|
-
kopipasta-0.
|
|
9
|
-
kopipasta-0.
|
|
10
|
-
kopipasta-0.
|
|
11
|
-
kopipasta-0.
|
|
12
|
-
kopipasta-0.
|
|
13
|
-
kopipasta-0.
|
|
7
|
+
kopipasta/tree_selector.py,sha256=lK56mEspCqMgL0YLDmTQWHSXrsmrxScdPK5iGceHVHo,30580
|
|
8
|
+
kopipasta-0.35.0.dist-info/LICENSE,sha256=xw4C9TAU7LFu4r_MwSbky90uzkzNtRwAo3c51IWR8lk,1091
|
|
9
|
+
kopipasta-0.35.0.dist-info/METADATA,sha256=8q5DXnATeJOEAUqBWIDlGDiLIYslg1NTYyvwujo0xto,4894
|
|
10
|
+
kopipasta-0.35.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
11
|
+
kopipasta-0.35.0.dist-info/entry_points.txt,sha256=but54qDNz1-F8fVvGstq_QID5tHjczP7bO7rWLFkc6Y,50
|
|
12
|
+
kopipasta-0.35.0.dist-info/top_level.txt,sha256=iXohixMuCdw8UjGDUp0ouICLYBDrx207sgZIJ9lxn0o,10
|
|
13
|
+
kopipasta-0.35.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|