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,553 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Optimized search and highlighting system for text widgets.
4
+ Implements progressive highlighting, batching, and non-blocking operations.
5
+ """
6
+
7
+ import re
8
+ import time
9
+ import threading
10
+ import tkinter as tk
11
+ from typing import Dict, List, Optional, Any, Tuple, Generator, Callable
12
+ from dataclasses import dataclass, field
13
+ from collections import deque
14
+ from enum import Enum
15
+ import queue
16
+
17
+ class HighlightMode(Enum):
18
+ """Different highlighting modes for optimization."""
19
+ IMMEDIATE = "immediate" # Highlight all matches immediately
20
+ PROGRESSIVE = "progressive" # Highlight matches progressively
21
+ BATCH = "batch" # Highlight in batches
22
+ LAZY = "lazy" # Highlight only visible area
23
+
24
+ class SearchState(Enum):
25
+ """Search operation states."""
26
+ IDLE = "idle"
27
+ SEARCHING = "searching"
28
+ HIGHLIGHTING = "highlighting"
29
+ COMPLETED = "completed"
30
+ CANCELLED = "cancelled"
31
+ ERROR = "error"
32
+
33
+ @dataclass
34
+ class HighlightMatch:
35
+ """Represents a single highlight match."""
36
+ start: int
37
+ end: int
38
+ text: str
39
+ tag_name: str
40
+ priority: int = 0
41
+
42
+ @property
43
+ def length(self) -> int:
44
+ return self.end - self.start
45
+
46
+ @dataclass
47
+ class SearchProgress:
48
+ """Progress information for search operations."""
49
+ total_chars: int = 0
50
+ processed_chars: int = 0
51
+ matches_found: int = 0
52
+ batches_completed: int = 0
53
+ time_elapsed: float = 0.0
54
+ estimated_remaining: float = 0.0
55
+
56
+ @property
57
+ def progress_percent(self) -> float:
58
+ if self.total_chars == 0:
59
+ return 0.0
60
+ return (self.processed_chars / self.total_chars) * 100
61
+
62
+ @dataclass
63
+ class SearchOperation:
64
+ """Represents a search operation with its parameters."""
65
+ operation_id: str
66
+ pattern: str
67
+ text_widget: tk.Text
68
+ tag_name: str
69
+ flags: int = 0
70
+ mode: HighlightMode = HighlightMode.PROGRESSIVE
71
+ batch_size: int = 100
72
+ max_matches: int = 10000
73
+ timeout_ms: int = 5000
74
+
75
+ # State
76
+ state: SearchState = SearchState.IDLE
77
+ matches: List[HighlightMatch] = field(default_factory=list)
78
+ progress: SearchProgress = field(default_factory=SearchProgress)
79
+ start_time: float = field(default_factory=time.time)
80
+
81
+ # Callbacks
82
+ progress_callback: Optional[Callable] = None
83
+ completion_callback: Optional[Callable] = None
84
+ error_callback: Optional[Callable] = None
85
+
86
+ class OptimizedSearchHighlighter:
87
+ """
88
+ High-performance search and highlighting system with progressive updates,
89
+ batching, and non-blocking operations for large text documents.
90
+ """
91
+
92
+ def __init__(self,
93
+ default_batch_size: int = 100,
94
+ max_concurrent_operations: int = 3,
95
+ highlight_timeout_ms: int = 5000):
96
+
97
+ self.default_batch_size = default_batch_size
98
+ self.max_concurrent_operations = max_concurrent_operations
99
+ self.highlight_timeout_ms = highlight_timeout_ms
100
+
101
+ # Operation management
102
+ self.active_operations: Dict[str, SearchOperation] = {}
103
+ self.operation_queue = queue.Queue()
104
+ self.operation_lock = threading.RLock()
105
+
106
+ # Performance tracking
107
+ self.performance_stats = {
108
+ 'total_operations': 0,
109
+ 'completed_operations': 0,
110
+ 'cancelled_operations': 0,
111
+ 'error_operations': 0,
112
+ 'total_matches_found': 0,
113
+ 'total_processing_time': 0.0,
114
+ 'average_processing_time': 0.0
115
+ }
116
+
117
+ # Tag configuration
118
+ self.tag_configs = {
119
+ 'search_highlight': {'background': 'yellow', 'foreground': 'black'},
120
+ 'replace_highlight': {'background': 'pink', 'foreground': 'black'},
121
+ 'current_match': {'background': 'orange', 'foreground': 'black'},
122
+ 'error_highlight': {'background': 'red', 'foreground': 'white'},
123
+ 'yellow_highlight': {'background': 'yellow', 'foreground': 'black'},
124
+ 'pink_highlight': {'background': 'pink', 'foreground': 'black'}
125
+ }
126
+
127
+ # Worker thread for background processing
128
+ self.worker_thread = None
129
+ self.shutdown_event = threading.Event()
130
+ self._start_worker_thread()
131
+
132
+ def _start_worker_thread(self):
133
+ """Start the background worker thread for processing operations."""
134
+ if self.worker_thread is None or not self.worker_thread.is_alive():
135
+ self.worker_thread = threading.Thread(
136
+ target=self._worker_loop,
137
+ daemon=True,
138
+ name="SearchHighlighter-Worker"
139
+ )
140
+ self.worker_thread.start()
141
+
142
+ def _worker_loop(self):
143
+ """Main worker loop for processing search operations."""
144
+ while not self.shutdown_event.is_set():
145
+ try:
146
+ # Get next operation from queue (with timeout)
147
+ operation = self.operation_queue.get(timeout=1.0)
148
+ if operation is None: # Shutdown signal
149
+ break
150
+
151
+ self._process_operation(operation)
152
+
153
+ except queue.Empty:
154
+ continue
155
+ except Exception as e:
156
+ print(f"Error in search worker thread: {e}")
157
+
158
+ def search_and_highlight(self,
159
+ text_widget: tk.Text,
160
+ pattern: str,
161
+ tag_name: str = 'search_highlight',
162
+ mode: HighlightMode = HighlightMode.PROGRESSIVE,
163
+ flags: int = 0,
164
+ batch_size: Optional[int] = None,
165
+ max_matches: int = 10000,
166
+ progress_callback: Optional[Callable] = None,
167
+ completion_callback: Optional[Callable] = None) -> str:
168
+ """
169
+ Start a search and highlight operation.
170
+
171
+ Args:
172
+ text_widget: The tkinter Text widget to search in
173
+ pattern: Regular expression pattern to search for
174
+ tag_name: Tag name for highlighting matches
175
+ mode: Highlighting mode (immediate, progressive, batch, lazy)
176
+ flags: Regular expression flags
177
+ batch_size: Number of matches to process per batch
178
+ max_matches: Maximum number of matches to find
179
+ progress_callback: Callback for progress updates
180
+ completion_callback: Callback when operation completes
181
+
182
+ Returns:
183
+ Operation ID for tracking the operation
184
+ """
185
+ # Generate unique operation ID
186
+ operation_id = f"search_{int(time.time() * 1000000)}"
187
+
188
+ # Create search operation
189
+ operation = SearchOperation(
190
+ operation_id=operation_id,
191
+ pattern=pattern,
192
+ text_widget=text_widget,
193
+ tag_name=tag_name,
194
+ flags=flags,
195
+ mode=mode,
196
+ batch_size=batch_size or self.default_batch_size,
197
+ max_matches=max_matches,
198
+ timeout_ms=self.highlight_timeout_ms,
199
+ progress_callback=progress_callback,
200
+ completion_callback=completion_callback
201
+ )
202
+
203
+ # Configure tag if not already configured
204
+ self._configure_tag(text_widget, tag_name)
205
+
206
+ # Clear existing highlights for this tag
207
+ self.clear_highlights(text_widget, tag_name)
208
+
209
+ # Add to active operations
210
+ with self.operation_lock:
211
+ self.active_operations[operation_id] = operation
212
+ self.performance_stats['total_operations'] += 1
213
+
214
+ # Queue for processing
215
+ self.operation_queue.put(operation)
216
+
217
+ return operation_id
218
+
219
+ def _configure_tag(self, text_widget: tk.Text, tag_name: str):
220
+ """Configure highlighting tag in the text widget."""
221
+ if tag_name in self.tag_configs:
222
+ config = self.tag_configs[tag_name]
223
+ text_widget.tag_configure(tag_name, **config)
224
+ else:
225
+ # Default configuration
226
+ text_widget.tag_configure(tag_name, background='yellow', foreground='black')
227
+
228
+ def _process_operation(self, operation: SearchOperation):
229
+ """Process a search operation in the background."""
230
+ try:
231
+ operation.state = SearchState.SEARCHING
232
+ operation.start_time = time.time()
233
+
234
+ # Get text content
235
+ content = operation.text_widget.get("1.0", tk.END)
236
+ operation.progress.total_chars = len(content)
237
+
238
+ # Compile regex pattern
239
+ try:
240
+ compiled_pattern = re.compile(operation.pattern, operation.flags)
241
+ except re.error as e:
242
+ operation.state = SearchState.ERROR
243
+ if operation.error_callback:
244
+ operation.error_callback(operation, str(e))
245
+ return
246
+
247
+ # Find matches based on mode
248
+ if operation.mode == HighlightMode.IMMEDIATE:
249
+ self._find_all_matches_immediate(operation, compiled_pattern, content)
250
+ elif operation.mode == HighlightMode.PROGRESSIVE:
251
+ self._find_matches_progressive(operation, compiled_pattern, content)
252
+ elif operation.mode == HighlightMode.BATCH:
253
+ self._find_matches_batch(operation, compiled_pattern, content)
254
+ elif operation.mode == HighlightMode.LAZY:
255
+ self._find_matches_lazy(operation, compiled_pattern, content)
256
+
257
+ # Update performance stats
258
+ operation.progress.time_elapsed = time.time() - operation.start_time
259
+
260
+ with self.operation_lock:
261
+ if operation.state != SearchState.CANCELLED:
262
+ operation.state = SearchState.COMPLETED
263
+ self.performance_stats['completed_operations'] += 1
264
+ self.performance_stats['total_matches_found'] += len(operation.matches)
265
+ self.performance_stats['total_processing_time'] += operation.progress.time_elapsed
266
+
267
+ # Update average processing time
268
+ if self.performance_stats['completed_operations'] > 0:
269
+ self.performance_stats['average_processing_time'] = (
270
+ self.performance_stats['total_processing_time'] /
271
+ self.performance_stats['completed_operations']
272
+ )
273
+
274
+ # Remove from active operations
275
+ self.active_operations.pop(operation.operation_id, None)
276
+
277
+ # Call completion callback
278
+ if operation.completion_callback and operation.state == SearchState.COMPLETED:
279
+ operation.completion_callback(operation)
280
+
281
+ except Exception as e:
282
+ operation.state = SearchState.ERROR
283
+ with self.operation_lock:
284
+ self.performance_stats['error_operations'] += 1
285
+ self.active_operations.pop(operation.operation_id, None)
286
+
287
+ if operation.error_callback:
288
+ operation.error_callback(operation, str(e))
289
+
290
+ def _find_all_matches_immediate(self, operation: SearchOperation, pattern: re.Pattern, content: str):
291
+ """Find all matches immediately and highlight them."""
292
+ matches = []
293
+
294
+ for match in pattern.finditer(content):
295
+ if len(matches) >= operation.max_matches:
296
+ break
297
+
298
+ highlight_match = HighlightMatch(
299
+ start=match.start(),
300
+ end=match.end(),
301
+ text=match.group(),
302
+ tag_name=operation.tag_name
303
+ )
304
+ matches.append(highlight_match)
305
+
306
+ operation.matches = matches
307
+ operation.progress.matches_found = len(matches)
308
+ operation.progress.processed_chars = len(content)
309
+
310
+ # Apply highlights immediately
311
+ self._apply_highlights_immediate(operation)
312
+
313
+ def _find_matches_progressive(self, operation: SearchOperation, pattern: re.Pattern, content: str):
314
+ """Find matches progressively with periodic UI updates."""
315
+ matches = []
316
+ batch_matches = []
317
+ last_update_time = time.time()
318
+ update_interval = 0.1 # Update UI every 100ms
319
+
320
+ for match in pattern.finditer(content):
321
+ if operation.state == SearchState.CANCELLED:
322
+ break
323
+
324
+ if len(matches) >= operation.max_matches:
325
+ break
326
+
327
+ highlight_match = HighlightMatch(
328
+ start=match.start(),
329
+ end=match.end(),
330
+ text=match.group(),
331
+ tag_name=operation.tag_name
332
+ )
333
+
334
+ matches.append(highlight_match)
335
+ batch_matches.append(highlight_match)
336
+
337
+ # Update progress
338
+ operation.progress.matches_found = len(matches)
339
+ operation.progress.processed_chars = match.end()
340
+
341
+ # Apply highlights in batches
342
+ if (len(batch_matches) >= operation.batch_size or
343
+ time.time() - last_update_time > update_interval):
344
+
345
+ self._apply_highlights_batch(operation, batch_matches)
346
+ batch_matches = []
347
+ last_update_time = time.time()
348
+
349
+ # Call progress callback
350
+ if operation.progress_callback:
351
+ operation.progress_callback(operation)
352
+
353
+ # Apply remaining highlights
354
+ if batch_matches:
355
+ self._apply_highlights_batch(operation, batch_matches)
356
+
357
+ operation.matches = matches
358
+ operation.progress.processed_chars = len(content)
359
+
360
+ def _find_matches_batch(self, operation: SearchOperation, pattern: re.Pattern, content: str):
361
+ """Find matches in batches with controlled processing."""
362
+ matches = []
363
+ chunk_size = 10000 # Process 10KB chunks
364
+
365
+ for i in range(0, len(content), chunk_size):
366
+ if operation.state == SearchState.CANCELLED:
367
+ break
368
+
369
+ chunk = content[i:i + chunk_size]
370
+ chunk_matches = []
371
+
372
+ for match in pattern.finditer(chunk):
373
+ if len(matches) >= operation.max_matches:
374
+ break
375
+
376
+ highlight_match = HighlightMatch(
377
+ start=i + match.start(),
378
+ end=i + match.end(),
379
+ text=match.group(),
380
+ tag_name=operation.tag_name
381
+ )
382
+
383
+ matches.append(highlight_match)
384
+ chunk_matches.append(highlight_match)
385
+
386
+ # Apply highlights for this chunk
387
+ if chunk_matches:
388
+ self._apply_highlights_batch(operation, chunk_matches)
389
+
390
+ # Update progress
391
+ operation.progress.matches_found = len(matches)
392
+ operation.progress.processed_chars = min(i + chunk_size, len(content))
393
+ operation.progress.batches_completed += 1
394
+
395
+ # Call progress callback
396
+ if operation.progress_callback:
397
+ operation.progress_callback(operation)
398
+
399
+ # Small delay to prevent UI blocking
400
+ time.sleep(0.001)
401
+
402
+ operation.matches = matches
403
+
404
+ def _find_matches_lazy(self, operation: SearchOperation, pattern: re.Pattern, content: str):
405
+ """Find matches only in visible area (lazy loading)."""
406
+ # Get visible area of text widget
407
+ try:
408
+ visible_start = operation.text_widget.index("@0,0")
409
+ visible_end = operation.text_widget.index(f"@{operation.text_widget.winfo_width()},{operation.text_widget.winfo_height()}")
410
+
411
+ start_idx = operation.text_widget.count("1.0", visible_start, "chars")[0]
412
+ end_idx = operation.text_widget.count("1.0", visible_end, "chars")[0]
413
+
414
+ visible_content = content[start_idx:end_idx]
415
+
416
+ except (tk.TclError, TypeError):
417
+ # Fallback to processing entire content
418
+ visible_content = content
419
+ start_idx = 0
420
+
421
+ matches = []
422
+
423
+ for match in pattern.finditer(visible_content):
424
+ if len(matches) >= operation.max_matches:
425
+ break
426
+
427
+ highlight_match = HighlightMatch(
428
+ start=start_idx + match.start(),
429
+ end=start_idx + match.end(),
430
+ text=match.group(),
431
+ tag_name=operation.tag_name
432
+ )
433
+ matches.append(highlight_match)
434
+
435
+ operation.matches = matches
436
+ operation.progress.matches_found = len(matches)
437
+ operation.progress.processed_chars = len(visible_content)
438
+
439
+ # Apply highlights
440
+ self._apply_highlights_batch(operation, matches)
441
+
442
+ def _apply_highlights_immediate(self, operation: SearchOperation):
443
+ """Apply all highlights immediately."""
444
+ def apply():
445
+ for match in operation.matches:
446
+ try:
447
+ start_pos = f"1.0 + {match.start}c"
448
+ end_pos = f"1.0 + {match.end}c"
449
+ operation.text_widget.tag_add(match.tag_name, start_pos, end_pos)
450
+ except tk.TclError:
451
+ continue
452
+
453
+ # Schedule on main thread
454
+ operation.text_widget.after_idle(apply)
455
+
456
+ def _apply_highlights_batch(self, operation: SearchOperation, matches: List[HighlightMatch]):
457
+ """Apply highlights in a batch."""
458
+ def apply():
459
+ for match in matches:
460
+ try:
461
+ start_pos = f"1.0 + {match.start}c"
462
+ end_pos = f"1.0 + {match.end}c"
463
+ operation.text_widget.tag_add(match.tag_name, start_pos, end_pos)
464
+ except tk.TclError:
465
+ continue
466
+
467
+ # Schedule on main thread
468
+ operation.text_widget.after_idle(apply)
469
+
470
+ def cancel_operation(self, operation_id: str) -> bool:
471
+ """Cancel a running search operation."""
472
+ with self.operation_lock:
473
+ if operation_id in self.active_operations:
474
+ operation = self.active_operations[operation_id]
475
+ operation.state = SearchState.CANCELLED
476
+ self.performance_stats['cancelled_operations'] += 1
477
+ return True
478
+ return False
479
+
480
+ def cancel_all_operations(self):
481
+ """Cancel all running search operations."""
482
+ with self.operation_lock:
483
+ for operation in self.active_operations.values():
484
+ operation.state = SearchState.CANCELLED
485
+ self.performance_stats['cancelled_operations'] += len(self.active_operations)
486
+ self.active_operations.clear()
487
+
488
+ def clear_highlights(self, text_widget: tk.Text, tag_name: str):
489
+ """Clear all highlights for a specific tag."""
490
+ def clear():
491
+ try:
492
+ text_widget.tag_remove(tag_name, "1.0", tk.END)
493
+ except tk.TclError:
494
+ pass
495
+
496
+ text_widget.after_idle(clear)
497
+
498
+ def clear_all_highlights(self, text_widget: tk.Text):
499
+ """Clear all highlights in the text widget."""
500
+ def clear():
501
+ try:
502
+ for tag_name in self.tag_configs.keys():
503
+ text_widget.tag_remove(tag_name, "1.0", tk.END)
504
+ except tk.TclError:
505
+ pass
506
+
507
+ text_widget.after_idle(clear)
508
+
509
+ def get_operation_status(self, operation_id: str) -> Optional[SearchOperation]:
510
+ """Get the status of a search operation."""
511
+ with self.operation_lock:
512
+ return self.active_operations.get(operation_id)
513
+
514
+ def get_active_operations(self) -> List[str]:
515
+ """Get list of active operation IDs."""
516
+ with self.operation_lock:
517
+ return list(self.active_operations.keys())
518
+
519
+ def get_performance_stats(self) -> Dict[str, Any]:
520
+ """Get performance statistics."""
521
+ with self.operation_lock:
522
+ return self.performance_stats.copy()
523
+
524
+ def configure_tag(self, tag_name: str, **config):
525
+ """Configure a highlight tag."""
526
+ self.tag_configs[tag_name] = config
527
+
528
+ def shutdown(self):
529
+ """Shutdown the highlighter and cleanup resources."""
530
+ self.cancel_all_operations()
531
+ self.shutdown_event.set()
532
+
533
+ if self.worker_thread and self.worker_thread.is_alive():
534
+ # Signal shutdown
535
+ self.operation_queue.put(None)
536
+ self.worker_thread.join(timeout=2.0)
537
+
538
+ # Global instance
539
+ _global_search_highlighter = None
540
+
541
+ def get_search_highlighter() -> OptimizedSearchHighlighter:
542
+ """Get the global search highlighter instance."""
543
+ global _global_search_highlighter
544
+ if _global_search_highlighter is None:
545
+ _global_search_highlighter = OptimizedSearchHighlighter()
546
+ return _global_search_highlighter
547
+
548
+ def shutdown_search_highlighter():
549
+ """Shutdown the global search highlighter."""
550
+ global _global_search_highlighter
551
+ if _global_search_highlighter is not None:
552
+ _global_search_highlighter.shutdown()
553
+ _global_search_highlighter = None