pomera-ai-commander 0.1.0

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.
Files changed (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +680 -0
  3. package/bin/pomera-ai-commander.js +62 -0
  4. package/core/__init__.py +66 -0
  5. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  7. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  8. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  9. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  10. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  11. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  12. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  13. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  14. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  15. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  16. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  17. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  18. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  19. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  20. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  21. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  22. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  23. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  24. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  25. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  26. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  27. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  28. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  29. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  30. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  31. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  32. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  33. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  34. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  35. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  36. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  37. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  38. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  39. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  40. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  41. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  42. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  43. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  44. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  45. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  46. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  47. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  48. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  49. package/core/app_context.py +482 -0
  50. package/core/async_text_processor.py +422 -0
  51. package/core/backup_manager.py +656 -0
  52. package/core/backup_recovery_manager.py +1034 -0
  53. package/core/content_hash_cache.py +509 -0
  54. package/core/context_menu.py +313 -0
  55. package/core/data_validator.py +1067 -0
  56. package/core/database_connection_manager.py +745 -0
  57. package/core/database_curl_settings_manager.py +609 -0
  58. package/core/database_promera_ai_settings_manager.py +447 -0
  59. package/core/database_schema.py +412 -0
  60. package/core/database_schema_manager.py +396 -0
  61. package/core/database_settings_manager.py +1508 -0
  62. package/core/database_settings_manager_interface.py +457 -0
  63. package/core/dialog_manager.py +735 -0
  64. package/core/efficient_line_numbers.py +511 -0
  65. package/core/error_handler.py +747 -0
  66. package/core/error_service.py +431 -0
  67. package/core/event_consolidator.py +512 -0
  68. package/core/mcp/__init__.py +43 -0
  69. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  70. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  71. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  72. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  73. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  74. package/core/mcp/protocol.py +288 -0
  75. package/core/mcp/schema.py +251 -0
  76. package/core/mcp/server_stdio.py +299 -0
  77. package/core/mcp/tool_registry.py +2345 -0
  78. package/core/memory_efficient_text_widget.py +712 -0
  79. package/core/migration_manager.py +915 -0
  80. package/core/migration_test_suite.py +1086 -0
  81. package/core/migration_validator.py +1144 -0
  82. package/core/optimized_find_replace.py +715 -0
  83. package/core/optimized_pattern_engine.py +424 -0
  84. package/core/optimized_search_highlighter.py +553 -0
  85. package/core/performance_monitor.py +675 -0
  86. package/core/persistence_manager.py +713 -0
  87. package/core/progressive_stats_calculator.py +632 -0
  88. package/core/regex_pattern_cache.py +530 -0
  89. package/core/regex_pattern_library.py +351 -0
  90. package/core/search_operation_manager.py +435 -0
  91. package/core/settings_defaults_registry.py +1087 -0
  92. package/core/settings_integrity_validator.py +1112 -0
  93. package/core/settings_serializer.py +558 -0
  94. package/core/settings_validator.py +1824 -0
  95. package/core/smart_stats_calculator.py +710 -0
  96. package/core/statistics_update_manager.py +619 -0
  97. package/core/stats_config_manager.py +858 -0
  98. package/core/streaming_text_handler.py +723 -0
  99. package/core/task_scheduler.py +596 -0
  100. package/core/update_pattern_library.py +169 -0
  101. package/core/visibility_monitor.py +596 -0
  102. package/core/widget_cache.py +498 -0
  103. package/mcp.json +61 -0
  104. package/package.json +57 -0
  105. package/pomera.py +7483 -0
  106. package/pomera_mcp_server.py +144 -0
  107. package/tools/__init__.py +5 -0
  108. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  109. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  110. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  111. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  112. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  113. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  114. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  115. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  116. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  117. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  118. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  119. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  120. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  121. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  122. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  123. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  124. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  125. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  126. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  127. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  128. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  129. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  130. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  131. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  132. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  133. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  134. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  135. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  136. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  137. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  138. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  139. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  140. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  141. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  142. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  143. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  144. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  145. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  146. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  147. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  148. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
  151. package/tools/ai_tools.py +2892 -0
  152. package/tools/ascii_art_generator.py +353 -0
  153. package/tools/base64_tools.py +184 -0
  154. package/tools/base_tool.py +511 -0
  155. package/tools/case_tool.py +309 -0
  156. package/tools/column_tools.py +396 -0
  157. package/tools/cron_tool.py +885 -0
  158. package/tools/curl_history.py +601 -0
  159. package/tools/curl_processor.py +1208 -0
  160. package/tools/curl_settings.py +503 -0
  161. package/tools/curl_tool.py +5467 -0
  162. package/tools/diff_viewer.py +1072 -0
  163. package/tools/email_extraction_tool.py +249 -0
  164. package/tools/email_header_analyzer.py +426 -0
  165. package/tools/extraction_tools.py +250 -0
  166. package/tools/find_replace.py +1751 -0
  167. package/tools/folder_file_reporter.py +1463 -0
  168. package/tools/folder_file_reporter_adapter.py +480 -0
  169. package/tools/generator_tools.py +1217 -0
  170. package/tools/hash_generator.py +256 -0
  171. package/tools/html_tool.py +657 -0
  172. package/tools/huggingface_helper.py +449 -0
  173. package/tools/jsonxml_tool.py +730 -0
  174. package/tools/line_tools.py +419 -0
  175. package/tools/list_comparator.py +720 -0
  176. package/tools/markdown_tools.py +562 -0
  177. package/tools/mcp_widget.py +1417 -0
  178. package/tools/notes_widget.py +973 -0
  179. package/tools/number_base_converter.py +373 -0
  180. package/tools/regex_extractor.py +572 -0
  181. package/tools/slug_generator.py +311 -0
  182. package/tools/sorter_tools.py +459 -0
  183. package/tools/string_escape_tool.py +393 -0
  184. package/tools/text_statistics_tool.py +366 -0
  185. package/tools/text_wrapper.py +431 -0
  186. package/tools/timestamp_converter.py +422 -0
  187. package/tools/tool_loader.py +710 -0
  188. package/tools/translator_tools.py +523 -0
  189. package/tools/url_link_extractor.py +262 -0
  190. package/tools/url_parser.py +205 -0
  191. package/tools/whitespace_tools.py +356 -0
  192. package/tools/word_frequency_counter.py +147 -0
@@ -0,0 +1,712 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Memory-efficient text widget with optimized text insertion and virtual scrolling.
4
+ Designed for handling extremely large documents with minimal memory footprint.
5
+ """
6
+
7
+ import tkinter as tk
8
+ from tkinter import ttk, scrolledtext
9
+ import threading
10
+ import time
11
+ import gc
12
+ from typing import Dict, List, Optional, Any, Tuple, Callable
13
+ from dataclasses import dataclass, field
14
+ from collections import deque
15
+ import weakref
16
+ import hashlib
17
+
18
+ # Performance monitoring integration
19
+ try:
20
+ from performance_metrics import record_operation_metric, record_ui_metric
21
+ PERFORMANCE_MONITORING_AVAILABLE = True
22
+ except ImportError:
23
+ PERFORMANCE_MONITORING_AVAILABLE = False
24
+
25
+ @dataclass
26
+ class TextChunk:
27
+ """Represents a chunk of text for virtual scrolling."""
28
+ start_line: int
29
+ end_line: int
30
+ content: str
31
+ content_hash: str
32
+ last_access: float = field(default_factory=time.time)
33
+ is_loaded: bool = True
34
+ compressed_content: Optional[bytes] = None
35
+
36
+ @property
37
+ def line_count(self) -> int:
38
+ """Get the number of lines in this chunk."""
39
+ return self.end_line - self.start_line + 1
40
+
41
+ @property
42
+ def size_bytes(self) -> int:
43
+ """Get the size of this chunk in bytes."""
44
+ if self.is_loaded:
45
+ return len(self.content.encode('utf-8'))
46
+ elif self.compressed_content:
47
+ return len(self.compressed_content)
48
+ return 0
49
+
50
+ @dataclass
51
+ class VirtualScrollConfig:
52
+ """Configuration for virtual scrolling behavior."""
53
+ chunk_size_lines: int = 1000 # Lines per chunk
54
+ max_loaded_chunks: int = 10 # Maximum chunks to keep in memory
55
+ preload_chunks: int = 2 # Chunks to preload around visible area
56
+ compression_threshold_kb: int = 100 # Compress chunks larger than this
57
+ gc_interval_seconds: float = 30.0 # Garbage collection interval
58
+
59
+ class MemoryEfficientTextWidget(tk.Frame):
60
+ """
61
+ Memory-efficient text widget with virtual scrolling and optimized performance.
62
+ Handles extremely large documents by loading only visible portions into memory.
63
+ """
64
+
65
+ def __init__(self, parent, virtual_scrolling=True, **kwargs):
66
+ super().__init__(parent)
67
+
68
+ # Configuration
69
+ self.virtual_scrolling = virtual_scrolling
70
+ self.config = VirtualScrollConfig()
71
+
72
+ # Text storage
73
+ self.text_chunks: Dict[int, TextChunk] = {} # chunk_id -> TextChunk
74
+ self.total_lines = 0
75
+ self.total_size_bytes = 0
76
+
77
+ # Virtual scrolling state
78
+ self.visible_start_line = 0
79
+ self.visible_end_line = 0
80
+ self.loaded_chunks: deque = deque(maxlen=self.config.max_loaded_chunks)
81
+
82
+ # Performance tracking
83
+ self.performance_stats = {
84
+ 'insertions': 0,
85
+ 'chunk_loads': 0,
86
+ 'chunk_unloads': 0,
87
+ 'gc_cycles': 0,
88
+ 'total_insert_time_ms': 0.0,
89
+ 'avg_insert_time_ms': 0.0,
90
+ 'memory_pressure_events': 0
91
+ }
92
+
93
+ # Threading
94
+ self.operation_lock = threading.RLock()
95
+ self.background_thread = None
96
+ self.shutdown_event = threading.Event()
97
+
98
+ # Create UI components
99
+ self._create_widgets(**kwargs)
100
+
101
+ # Start background maintenance
102
+ self._start_background_maintenance()
103
+
104
+ # Bind events
105
+ self._bind_events()
106
+
107
+ def _create_widgets(self, **kwargs):
108
+ """Create the text widget and scrollbars."""
109
+ # Configure grid
110
+ self.grid_rowconfigure(0, weight=1)
111
+ self.grid_columnconfigure(0, weight=1)
112
+
113
+ # Create text widget with optimized configuration
114
+ text_config = {
115
+ 'wrap': kwargs.get('wrap', tk.WORD),
116
+ 'undo': kwargs.get('undo', True),
117
+ 'maxundo': kwargs.get('maxundo', 20), # Limit undo stack
118
+ 'height': kwargs.get('height', 20),
119
+ 'width': kwargs.get('width', 80),
120
+ 'font': kwargs.get('font', ('Consolas', 10)),
121
+ 'relief': kwargs.get('relief', tk.SUNKEN),
122
+ 'borderwidth': kwargs.get('borderwidth', 1),
123
+ 'insertwidth': 2,
124
+ 'selectbackground': '#316AC5',
125
+ 'inactiveselectbackground': '#316AC5'
126
+ }
127
+
128
+ # Optimize text widget for performance
129
+ if self.virtual_scrolling:
130
+ # For virtual scrolling, we need more control
131
+ self.text_widget = tk.Text(self, **text_config)
132
+ else:
133
+ # Use ScrolledText for simpler cases
134
+ self.text_widget = scrolledtext.ScrolledText(self, **text_config)
135
+
136
+ self.text_widget.grid(row=0, column=0, sticky="nsew")
137
+
138
+ # Create custom scrollbars for virtual scrolling
139
+ if self.virtual_scrolling:
140
+ self.v_scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self._on_scroll)
141
+ self.v_scrollbar.grid(row=0, column=1, sticky="ns")
142
+
143
+ self.h_scrollbar = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.text_widget.xview)
144
+ self.h_scrollbar.grid(row=1, column=0, sticky="ew")
145
+
146
+ # Configure text widget scrolling
147
+ self.text_widget.config(yscrollcommand=self._on_text_scroll, xscrollcommand=self.h_scrollbar.set)
148
+
149
+ def _bind_events(self):
150
+ """Bind events for performance monitoring and virtual scrolling."""
151
+ # Monitor text modifications
152
+ self.text_widget.bind('<<Modified>>', self._on_text_modified)
153
+ self.text_widget.bind('<KeyPress>', self._on_key_press)
154
+ self.text_widget.bind('<Button-1>', self._on_mouse_click)
155
+
156
+ # Monitor scrolling for virtual scrolling
157
+ if self.virtual_scrolling:
158
+ self.text_widget.bind('<MouseWheel>', self._on_mouse_wheel)
159
+ self.text_widget.bind('<Button-4>', self._on_mouse_wheel)
160
+ self.text_widget.bind('<Button-5>', self._on_mouse_wheel)
161
+
162
+ def _start_background_maintenance(self):
163
+ """Start background thread for memory management."""
164
+ if self.background_thread is None or not self.background_thread.is_alive():
165
+ self.background_thread = threading.Thread(
166
+ target=self._background_maintenance_loop,
167
+ daemon=True,
168
+ name="TextWidget-Maintenance"
169
+ )
170
+ self.background_thread.start()
171
+
172
+ def _background_maintenance_loop(self):
173
+ """Background maintenance for memory management and garbage collection."""
174
+ while not self.shutdown_event.is_set():
175
+ try:
176
+ # Perform maintenance tasks
177
+ self._cleanup_unused_chunks()
178
+ self._compress_old_chunks()
179
+ self._trigger_gc_if_needed()
180
+
181
+ # Sleep until next maintenance cycle
182
+ self.shutdown_event.wait(self.config.gc_interval_seconds)
183
+
184
+ except Exception as e:
185
+ print(f"Error in text widget maintenance: {e}")
186
+ time.sleep(5.0)
187
+
188
+ def insert_text_optimized(self, position: str, text: str, tags: Optional[List[str]] = None) -> bool:
189
+ """
190
+ Optimized text insertion with chunking and performance monitoring.
191
+
192
+ Args:
193
+ position: Insert position (e.g., "1.0", "end")
194
+ text: Text to insert
195
+ tags: Optional tags to apply
196
+
197
+ Returns:
198
+ True if insertion was successful
199
+ """
200
+ start_time = time.time()
201
+
202
+ try:
203
+ with self.operation_lock:
204
+ # Determine insertion strategy based on text size
205
+ text_size = len(text.encode('utf-8'))
206
+
207
+ if text_size > 1024 * 1024: # > 1MB
208
+ success = self._insert_large_text(position, text, tags)
209
+ elif text_size > 10 * 1024: # > 10KB
210
+ success = self._insert_medium_text(position, text, tags)
211
+ else:
212
+ success = self._insert_small_text(position, text, tags)
213
+
214
+ # Update performance stats
215
+ duration_ms = (time.time() - start_time) * 1000
216
+ self.performance_stats['insertions'] += 1
217
+ self.performance_stats['total_insert_time_ms'] += duration_ms
218
+ self.performance_stats['avg_insert_time_ms'] = (
219
+ self.performance_stats['total_insert_time_ms'] /
220
+ self.performance_stats['insertions']
221
+ )
222
+
223
+ # Record performance metrics
224
+ if PERFORMANCE_MONITORING_AVAILABLE:
225
+ record_operation_metric("text_insert", duration_ms, success)
226
+ record_ui_metric("text_widget", duration_ms)
227
+
228
+ return success
229
+
230
+ except Exception as e:
231
+ print(f"Error in optimized text insertion: {e}")
232
+ return False
233
+
234
+ def _insert_small_text(self, position: str, text: str, tags: Optional[List[str]] = None) -> bool:
235
+ """Insert small text directly."""
236
+ try:
237
+ self.text_widget.insert(position, text, tags or ())
238
+ return True
239
+ except tk.TclError as e:
240
+ print(f"Error inserting small text: {e}")
241
+ return False
242
+
243
+ def _insert_medium_text(self, position: str, text: str, tags: Optional[List[str]] = None) -> bool:
244
+ """Insert medium-sized text with progress feedback."""
245
+ try:
246
+ # Split into smaller chunks for responsive insertion
247
+ chunk_size = 4096 # 4KB chunks
248
+ lines = text.splitlines(keepends=True)
249
+
250
+ current_pos = position
251
+ for i in range(0, len(lines), 100): # Process 100 lines at a time
252
+ chunk_lines = lines[i:i+100]
253
+ chunk_text = ''.join(chunk_lines)
254
+
255
+ self.text_widget.insert(current_pos, chunk_text, tags or ())
256
+
257
+ # Update position for next chunk
258
+ if current_pos == "end":
259
+ current_pos = "end"
260
+ else:
261
+ # Calculate new position
262
+ line_count = len(chunk_lines)
263
+ if current_pos.endswith('.0'):
264
+ line_num = int(current_pos.split('.')[0])
265
+ current_pos = f"{line_num + line_count}.0"
266
+
267
+ # Allow UI updates
268
+ self.text_widget.update_idletasks()
269
+
270
+ return True
271
+
272
+ except tk.TclError as e:
273
+ print(f"Error inserting medium text: {e}")
274
+ return False
275
+
276
+ def _insert_large_text(self, position: str, text: str, tags: Optional[List[str]] = None) -> bool:
277
+ """Insert large text with virtual scrolling and chunking."""
278
+ try:
279
+ if not self.virtual_scrolling:
280
+ # Fall back to chunked insertion without virtual scrolling
281
+ return self._insert_chunked_text(position, text, tags)
282
+
283
+ # For virtual scrolling, we need to manage chunks
284
+ lines = text.splitlines(keepends=True)
285
+ total_lines = len(lines)
286
+
287
+ # Create chunks
288
+ chunk_id = 0
289
+ for i in range(0, total_lines, self.config.chunk_size_lines):
290
+ chunk_lines = lines[i:i+self.config.chunk_size_lines]
291
+ chunk_text = ''.join(chunk_lines)
292
+
293
+ # Create text chunk
294
+ chunk = TextChunk(
295
+ start_line=i,
296
+ end_line=min(i + len(chunk_lines) - 1, total_lines - 1),
297
+ content=chunk_text,
298
+ content_hash=self._generate_content_hash(chunk_text)
299
+ )
300
+
301
+ self.text_chunks[chunk_id] = chunk
302
+ chunk_id += 1
303
+
304
+ # Update total lines
305
+ self.total_lines = total_lines
306
+ self.total_size_bytes = len(text.encode('utf-8'))
307
+
308
+ # Load initial visible chunks
309
+ self._load_visible_chunks()
310
+
311
+ return True
312
+
313
+ except Exception as e:
314
+ print(f"Error inserting large text: {e}")
315
+ return False
316
+
317
+ def _insert_chunked_text(self, position: str, text: str, tags: Optional[List[str]] = None) -> bool:
318
+ """Insert text in chunks with progress feedback (non-virtual scrolling)."""
319
+ try:
320
+ lines = text.splitlines(keepends=True)
321
+ chunk_size = 1000 # Lines per chunk
322
+
323
+ # Disable undo during bulk insertion
324
+ original_undo = self.text_widget.cget('undo')
325
+ self.text_widget.config(undo=False)
326
+
327
+ try:
328
+ current_pos = position
329
+ for i in range(0, len(lines), chunk_size):
330
+ chunk_lines = lines[i:i+chunk_size]
331
+ chunk_text = ''.join(chunk_lines)
332
+
333
+ self.text_widget.insert(current_pos, chunk_text, tags or ())
334
+
335
+ # Update position
336
+ if current_pos == "end":
337
+ current_pos = "end"
338
+
339
+ # Allow UI updates every few chunks
340
+ if i % (chunk_size * 5) == 0:
341
+ self.text_widget.update_idletasks()
342
+
343
+ return True
344
+
345
+ finally:
346
+ # Re-enable undo
347
+ self.text_widget.config(undo=original_undo)
348
+
349
+ except tk.TclError as e:
350
+ print(f"Error in chunked text insertion: {e}")
351
+ return False
352
+
353
+ def _load_visible_chunks(self):
354
+ """Load chunks that should be visible in the current view."""
355
+ if not self.virtual_scrolling:
356
+ return
357
+
358
+ # Calculate visible line range
359
+ try:
360
+ # Get visible area
361
+ top_line = int(self.text_widget.index("@0,0").split('.')[0])
362
+ bottom_line = int(self.text_widget.index(f"@0,{self.text_widget.winfo_height()}").split('.')[0])
363
+
364
+ self.visible_start_line = max(0, top_line - self.config.preload_chunks * self.config.chunk_size_lines)
365
+ self.visible_end_line = min(self.total_lines, bottom_line + self.config.preload_chunks * self.config.chunk_size_lines)
366
+
367
+ # Find chunks that need to be loaded
368
+ chunks_to_load = []
369
+ for chunk_id, chunk in self.text_chunks.items():
370
+ if (chunk.start_line <= self.visible_end_line and
371
+ chunk.end_line >= self.visible_start_line and
372
+ not chunk.is_loaded):
373
+ chunks_to_load.append(chunk_id)
374
+
375
+ # Load needed chunks
376
+ for chunk_id in chunks_to_load:
377
+ self._load_chunk(chunk_id)
378
+
379
+ # Unload distant chunks
380
+ self._unload_distant_chunks()
381
+
382
+ except Exception as e:
383
+ print(f"Error loading visible chunks: {e}")
384
+
385
+ def _load_chunk(self, chunk_id: int):
386
+ """Load a specific chunk into memory."""
387
+ if chunk_id not in self.text_chunks:
388
+ return
389
+
390
+ chunk = self.text_chunks[chunk_id]
391
+ if chunk.is_loaded:
392
+ return
393
+
394
+ try:
395
+ # Decompress if needed
396
+ if chunk.compressed_content:
397
+ import zlib
398
+ chunk.content = zlib.decompress(chunk.compressed_content).decode('utf-8')
399
+ chunk.compressed_content = None
400
+
401
+ chunk.is_loaded = True
402
+ chunk.last_access = time.time()
403
+
404
+ # Add to loaded chunks queue
405
+ if chunk_id not in self.loaded_chunks:
406
+ self.loaded_chunks.append(chunk_id)
407
+
408
+ self.performance_stats['chunk_loads'] += 1
409
+
410
+ except Exception as e:
411
+ print(f"Error loading chunk {chunk_id}: {e}")
412
+
413
+ def _unload_distant_chunks(self):
414
+ """Unload chunks that are far from the visible area."""
415
+ chunks_to_unload = []
416
+
417
+ for chunk_id in list(self.loaded_chunks):
418
+ if chunk_id not in self.text_chunks:
419
+ continue
420
+
421
+ chunk = self.text_chunks[chunk_id]
422
+
423
+ # Check if chunk is far from visible area
424
+ if (chunk.end_line < self.visible_start_line - self.config.chunk_size_lines or
425
+ chunk.start_line > self.visible_end_line + self.config.chunk_size_lines):
426
+ chunks_to_unload.append(chunk_id)
427
+
428
+ # Unload distant chunks
429
+ for chunk_id in chunks_to_unload:
430
+ self._unload_chunk(chunk_id)
431
+
432
+ def _unload_chunk(self, chunk_id: int):
433
+ """Unload a chunk from memory."""
434
+ if chunk_id not in self.text_chunks:
435
+ return
436
+
437
+ chunk = self.text_chunks[chunk_id]
438
+ if not chunk.is_loaded:
439
+ return
440
+
441
+ try:
442
+ # Compress content if it's large enough
443
+ if len(chunk.content.encode('utf-8')) > self.config.compression_threshold_kb * 1024:
444
+ import zlib
445
+ chunk.compressed_content = zlib.compress(chunk.content.encode('utf-8'))
446
+
447
+ # Clear content from memory
448
+ chunk.content = ""
449
+ chunk.is_loaded = False
450
+
451
+ # Remove from loaded chunks
452
+ if chunk_id in self.loaded_chunks:
453
+ self.loaded_chunks.remove(chunk_id)
454
+
455
+ self.performance_stats['chunk_unloads'] += 1
456
+
457
+ except Exception as e:
458
+ print(f"Error unloading chunk {chunk_id}: {e}")
459
+
460
+ def _cleanup_unused_chunks(self):
461
+ """Clean up chunks that haven't been accessed recently."""
462
+ current_time = time.time()
463
+ max_age = 300 # 5 minutes
464
+
465
+ chunks_to_cleanup = []
466
+ for chunk_id, chunk in self.text_chunks.items():
467
+ if (chunk.is_loaded and
468
+ current_time - chunk.last_access > max_age and
469
+ chunk_id not in self.loaded_chunks[-self.config.preload_chunks:]):
470
+ chunks_to_cleanup.append(chunk_id)
471
+
472
+ for chunk_id in chunks_to_cleanup:
473
+ self._unload_chunk(chunk_id)
474
+
475
+ def _compress_old_chunks(self):
476
+ """Compress old chunks to save memory."""
477
+ current_time = time.time()
478
+ compression_age = 60 # 1 minute
479
+
480
+ for chunk in self.text_chunks.values():
481
+ if (chunk.is_loaded and
482
+ current_time - chunk.last_access > compression_age and
483
+ len(chunk.content.encode('utf-8')) > self.config.compression_threshold_kb * 1024 and
484
+ chunk.compressed_content is None):
485
+
486
+ try:
487
+ import zlib
488
+ chunk.compressed_content = zlib.compress(chunk.content.encode('utf-8'))
489
+ # Keep content in memory but mark as compressed
490
+ except Exception as e:
491
+ print(f"Error compressing chunk: {e}")
492
+
493
+ def _trigger_gc_if_needed(self):
494
+ """Trigger garbage collection if memory pressure is detected."""
495
+ try:
496
+ import psutil
497
+ process = psutil.Process()
498
+ memory_mb = process.memory_info().rss / 1024 / 1024
499
+
500
+ # Trigger GC if memory usage is high
501
+ if memory_mb > 500: # 500MB threshold
502
+ gc.collect()
503
+ self.performance_stats['gc_cycles'] += 1
504
+ self.performance_stats['memory_pressure_events'] += 1
505
+
506
+ # Record performance metric
507
+ if PERFORMANCE_MONITORING_AVAILABLE:
508
+ record_operation_metric("memory_gc", 0, True)
509
+
510
+ except ImportError:
511
+ # psutil not available, use basic GC
512
+ gc.collect()
513
+ self.performance_stats['gc_cycles'] += 1
514
+ except Exception as e:
515
+ print(f"Error in garbage collection: {e}")
516
+
517
+ def _generate_content_hash(self, content: str) -> str:
518
+ """Generate a hash for content identification."""
519
+ return hashlib.md5(content.encode('utf-8')).hexdigest()[:16]
520
+
521
+ def _on_scroll(self, *args):
522
+ """Handle scrollbar events for virtual scrolling."""
523
+ if not self.virtual_scrolling:
524
+ return
525
+
526
+ # Update text widget scroll position
527
+ self.text_widget.yview(*args)
528
+
529
+ # Load visible chunks
530
+ self._load_visible_chunks()
531
+
532
+ def _on_text_scroll(self, *args):
533
+ """Handle text widget scroll events."""
534
+ # Update scrollbar
535
+ if self.virtual_scrolling:
536
+ self.v_scrollbar.set(*args)
537
+
538
+ # Load visible chunks
539
+ self._load_visible_chunks()
540
+
541
+ def _on_mouse_wheel(self, event):
542
+ """Handle mouse wheel scrolling."""
543
+ # Standard mouse wheel handling
544
+ if event.delta:
545
+ delta = -1 * (event.delta / 120)
546
+ else:
547
+ delta = -1 if event.num == 4 else 1
548
+
549
+ self.text_widget.yview_scroll(int(delta), "units")
550
+
551
+ # Load visible chunks
552
+ if self.virtual_scrolling:
553
+ self._load_visible_chunks()
554
+
555
+ def _on_text_modified(self, event=None):
556
+ """Handle text modification events."""
557
+ # Reset modified flag
558
+ if event and event.widget.edit_modified():
559
+ event.widget.edit_modified(False)
560
+
561
+ # Update chunk information if using virtual scrolling
562
+ if self.virtual_scrolling:
563
+ # For now, we'll invalidate chunks on modification
564
+ # In a more sophisticated implementation, we'd update specific chunks
565
+ pass
566
+
567
+ def _on_key_press(self, event):
568
+ """Handle key press events for performance monitoring."""
569
+ start_time = time.time()
570
+
571
+ # Record UI responsiveness
572
+ def record_response():
573
+ duration_ms = (time.time() - start_time) * 1000
574
+ if PERFORMANCE_MONITORING_AVAILABLE:
575
+ record_ui_metric("key_press", duration_ms)
576
+
577
+ self.after_idle(record_response)
578
+
579
+ def _on_mouse_click(self, event):
580
+ """Handle mouse click events."""
581
+ start_time = time.time()
582
+
583
+ # Record UI responsiveness
584
+ def record_response():
585
+ duration_ms = (time.time() - start_time) * 1000
586
+ if PERFORMANCE_MONITORING_AVAILABLE:
587
+ record_ui_metric("mouse_click", duration_ms)
588
+
589
+ self.after_idle(record_response)
590
+
591
+ # Public API methods
592
+ def insert(self, position: str, text: str, *tags):
593
+ """Insert text at the specified position."""
594
+ return self.insert_text_optimized(position, text, list(tags) if tags else None)
595
+
596
+ def get(self, start: str, end: str = None) -> str:
597
+ """Get text from the widget."""
598
+ if end is None:
599
+ end = f"{start} lineend"
600
+ return self.text_widget.get(start, end)
601
+
602
+ def delete(self, start: str, end: str = None):
603
+ """Delete text from the widget."""
604
+ if end is None:
605
+ end = f"{start} lineend"
606
+ self.text_widget.delete(start, end)
607
+
608
+ def clear(self):
609
+ """Clear all text from the widget."""
610
+ self.text_widget.delete("1.0", tk.END)
611
+
612
+ # Clear virtual scrolling data
613
+ if self.virtual_scrolling:
614
+ with self.operation_lock:
615
+ self.text_chunks.clear()
616
+ self.loaded_chunks.clear()
617
+ self.total_lines = 0
618
+ self.total_size_bytes = 0
619
+
620
+ def config(self, **kwargs):
621
+ """Configure the text widget."""
622
+ self.text_widget.config(**kwargs)
623
+
624
+ def configure(self, **kwargs):
625
+ """Configure the text widget (alias for config)."""
626
+ self.config(**kwargs)
627
+
628
+ def bind(self, sequence, func, add=None):
629
+ """Bind events to the text widget."""
630
+ return self.text_widget.bind(sequence, func, add)
631
+
632
+ def focus_set(self):
633
+ """Set focus to the text widget."""
634
+ self.text_widget.focus_set()
635
+
636
+ def get_performance_stats(self) -> Dict[str, Any]:
637
+ """Get performance statistics for this widget."""
638
+ with self.operation_lock:
639
+ stats = self.performance_stats.copy()
640
+ stats.update({
641
+ 'total_chunks': len(self.text_chunks),
642
+ 'loaded_chunks': len(self.loaded_chunks),
643
+ 'total_lines': self.total_lines,
644
+ 'total_size_bytes': self.total_size_bytes,
645
+ 'virtual_scrolling_enabled': self.virtual_scrolling
646
+ })
647
+ return stats
648
+
649
+ def optimize_for_large_content(self):
650
+ """Optimize widget configuration for large content."""
651
+ # Disable expensive features for large content
652
+ self.text_widget.config(
653
+ undo=False, # Disable undo for large content
654
+ wrap=tk.NONE, # Disable word wrapping
655
+ state=tk.NORMAL
656
+ )
657
+
658
+ # Enable virtual scrolling if not already enabled
659
+ if not self.virtual_scrolling:
660
+ self.virtual_scrolling = True
661
+ # Recreate scrollbars if needed
662
+ self._create_virtual_scrollbars()
663
+
664
+ def _create_virtual_scrollbars(self):
665
+ """Create virtual scrollbars if they don't exist."""
666
+ if not hasattr(self, 'v_scrollbar'):
667
+ self.v_scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self._on_scroll)
668
+ self.v_scrollbar.grid(row=0, column=1, sticky="ns")
669
+
670
+ self.text_widget.config(yscrollcommand=self._on_text_scroll)
671
+
672
+ def shutdown(self):
673
+ """Shutdown the widget and cleanup resources."""
674
+ self.shutdown_event.set()
675
+
676
+ if self.background_thread and self.background_thread.is_alive():
677
+ self.background_thread.join(timeout=2.0)
678
+
679
+ # Clear all chunks
680
+ with self.operation_lock:
681
+ self.text_chunks.clear()
682
+ self.loaded_chunks.clear()
683
+
684
+ def __del__(self):
685
+ """Cleanup when widget is destroyed."""
686
+ try:
687
+ self.shutdown()
688
+ except:
689
+ pass
690
+
691
+ # Factory function for creating optimized text widgets
692
+ def create_memory_efficient_text_widget(parent, large_content_mode=False, **kwargs) -> MemoryEfficientTextWidget:
693
+ """
694
+ Factory function to create a memory-efficient text widget.
695
+
696
+ Args:
697
+ parent: Parent widget
698
+ large_content_mode: Enable optimizations for large content
699
+ **kwargs: Additional arguments for text widget
700
+
701
+ Returns:
702
+ MemoryEfficientTextWidget instance
703
+ """
704
+ # Determine if virtual scrolling should be enabled
705
+ virtual_scrolling = large_content_mode or kwargs.pop('virtual_scrolling', False)
706
+
707
+ widget = MemoryEfficientTextWidget(parent, virtual_scrolling=virtual_scrolling, **kwargs)
708
+
709
+ if large_content_mode:
710
+ widget.optimize_for_large_content()
711
+
712
+ return widget