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,715 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Optimized find and replace processor with chunked processing and progress feedback.
4
+ """
5
+
6
+ import tkinter as tk
7
+ import re
8
+ import time
9
+ import threading
10
+ import queue
11
+ from typing import Dict, List, Optional, Callable, Any
12
+ from dataclasses import dataclass, field
13
+ from enum import Enum
14
+
15
+ class ReplaceOperation(Enum):
16
+ """Types of replace operations."""
17
+ SIMPLE = "simple"
18
+ REGEX = "regex"
19
+
20
+ class ProcessingMode(Enum):
21
+ """Processing modes for find/replace operations."""
22
+ IMMEDIATE = "immediate"
23
+ CHUNKED = "chunked"
24
+ STREAMING = "streaming"
25
+ PREVIEW_ONLY = "preview_only"
26
+
27
+ @dataclass
28
+ class ReplaceMatch:
29
+ """Represents a single find/replace match."""
30
+ start: int
31
+ end: int
32
+ original_text: str
33
+ replacement_text: str
34
+ match_number: int
35
+
36
+ @property
37
+ def length_change(self) -> int:
38
+ """Calculate the change in text length after replacement."""
39
+ return len(self.replacement_text) - len(self.original_text)
40
+
41
+ @dataclass
42
+ class ProcessingProgress:
43
+ """Progress information for find/replace operations."""
44
+ total_chars: int = 0
45
+ processed_chars: int = 0
46
+ matches_found: int = 0
47
+ matches_replaced: int = 0
48
+ chunks_completed: int = 0
49
+ time_elapsed: float = 0.0
50
+ estimated_remaining: float = 0.0
51
+
52
+ @property
53
+ def progress_percent(self) -> float:
54
+ if self.total_chars == 0:
55
+ return 0.0
56
+ return (self.processed_chars / self.total_chars) * 100
57
+
58
+ @dataclass
59
+ class FindReplaceOperation:
60
+ """Represents a find/replace operation with its parameters."""
61
+ operation_id: str
62
+ find_pattern: str
63
+ replace_text: str
64
+ text_widget: tk.Text
65
+ operation_type: ReplaceOperation = ReplaceOperation.SIMPLE
66
+ processing_mode: ProcessingMode = ProcessingMode.CHUNKED
67
+
68
+ # Options
69
+ case_sensitive: bool = True
70
+ whole_words: bool = False
71
+ use_regex: bool = False
72
+ max_replacements: int = -1 # -1 for unlimited
73
+ chunk_size: int = 10000
74
+
75
+ # State
76
+ matches: List[ReplaceMatch] = field(default_factory=list)
77
+ progress: ProcessingProgress = field(default_factory=ProcessingProgress)
78
+ start_time: float = field(default_factory=time.time)
79
+ is_cancelled: bool = False
80
+
81
+ # Results
82
+ original_text: str = ""
83
+ processed_text: str = ""
84
+
85
+ # Callbacks
86
+ progress_callback: Optional[Callable] = None
87
+ completion_callback: Optional[Callable] = None
88
+ error_callback: Optional[Callable] = None
89
+
90
+ class OptimizedFindReplace:
91
+ """
92
+ High-performance find and replace processor with chunked processing,
93
+ progress feedback, and efficient preview generation.
94
+ """
95
+
96
+ def __init__(self,
97
+ default_chunk_size: int = 10000,
98
+ max_concurrent_operations: int = 2,
99
+ progress_update_interval: float = 0.1):
100
+
101
+ self.default_chunk_size = default_chunk_size
102
+ self.max_concurrent_operations = max_concurrent_operations
103
+ self.progress_update_interval = progress_update_interval
104
+
105
+ # Operation management
106
+ self.active_operations: Dict[str, FindReplaceOperation] = {}
107
+ self.operation_queue = queue.Queue()
108
+ self.operation_lock = threading.RLock()
109
+
110
+ # Performance tracking
111
+ self.performance_stats = {
112
+ 'total_operations': 0,
113
+ 'completed_operations': 0,
114
+ 'cancelled_operations': 0,
115
+ 'error_operations': 0,
116
+ 'total_matches_processed': 0,
117
+ 'total_processing_time': 0.0,
118
+ 'average_processing_time': 0.0
119
+ }
120
+
121
+ # Worker thread for background processing
122
+ self.worker_thread = None
123
+ self.shutdown_event = threading.Event()
124
+ self._start_worker_thread()
125
+
126
+ def _start_worker_thread(self):
127
+ """Start the background worker thread for processing operations."""
128
+ if self.worker_thread is None or not self.worker_thread.is_alive():
129
+ self.worker_thread = threading.Thread(
130
+ target=self._worker_loop,
131
+ daemon=True,
132
+ name="FindReplace-Worker"
133
+ )
134
+ self.worker_thread.start()
135
+
136
+ def _worker_loop(self):
137
+ """Main worker loop for processing find/replace operations."""
138
+ while not self.shutdown_event.is_set():
139
+ try:
140
+ # Get next operation from queue (with timeout)
141
+ operation = self.operation_queue.get(timeout=1.0)
142
+ if operation is None: # Shutdown signal
143
+ break
144
+
145
+ self._process_operation(operation)
146
+
147
+ except queue.Empty:
148
+ continue
149
+ except Exception as e:
150
+ print(f"Error in find/replace worker thread: {e}")
151
+
152
+ def find_and_replace(self,
153
+ text_widget: tk.Text,
154
+ find_pattern: str,
155
+ replace_text: str,
156
+ case_sensitive: bool = True,
157
+ whole_words: bool = False,
158
+ use_regex: bool = False,
159
+ max_replacements: int = -1,
160
+ processing_mode: ProcessingMode = ProcessingMode.CHUNKED,
161
+ chunk_size: Optional[int] = None,
162
+ progress_callback: Optional[Callable] = None,
163
+ completion_callback: Optional[Callable] = None) -> str:
164
+ """
165
+ Start a find and replace operation.
166
+
167
+ Args:
168
+ text_widget: The tkinter Text widget to process
169
+ find_pattern: Pattern to search for
170
+ replace_text: Text to replace matches with
171
+ case_sensitive: Whether search is case sensitive
172
+ whole_words: Whether to match whole words only
173
+ use_regex: Whether to use regular expressions
174
+ max_replacements: Maximum number of replacements (-1 for unlimited)
175
+ processing_mode: How to process the operation
176
+ chunk_size: Size of chunks for chunked processing
177
+ progress_callback: Callback for progress updates
178
+ completion_callback: Callback when operation completes
179
+
180
+ Returns:
181
+ Operation ID for tracking the operation
182
+ """
183
+ # Generate unique operation ID
184
+ operation_id = f"findreplace_{int(time.time() * 1000000)}"
185
+
186
+ # Determine operation type
187
+ if use_regex:
188
+ operation_type = ReplaceOperation.REGEX
189
+ else:
190
+ operation_type = ReplaceOperation.SIMPLE
191
+
192
+ # Create operation
193
+ operation = FindReplaceOperation(
194
+ operation_id=operation_id,
195
+ find_pattern=find_pattern,
196
+ replace_text=replace_text,
197
+ text_widget=text_widget,
198
+ operation_type=operation_type,
199
+ processing_mode=processing_mode,
200
+ case_sensitive=case_sensitive,
201
+ whole_words=whole_words,
202
+ use_regex=use_regex,
203
+ max_replacements=max_replacements,
204
+ chunk_size=chunk_size or self.default_chunk_size,
205
+ progress_callback=progress_callback,
206
+ completion_callback=completion_callback
207
+ )
208
+
209
+ # Get original text
210
+ operation.original_text = text_widget.get("1.0", tk.END)
211
+ operation.progress.total_chars = len(operation.original_text)
212
+
213
+ # Add to active operations
214
+ with self.operation_lock:
215
+ self.active_operations[operation_id] = operation
216
+ self.performance_stats['total_operations'] += 1
217
+
218
+ # Queue for processing
219
+ self.operation_queue.put(operation)
220
+
221
+ return operation_id
222
+
223
+ def generate_preview(self,
224
+ text_widget: tk.Text,
225
+ find_pattern: str,
226
+ replace_text: str,
227
+ case_sensitive: bool = True,
228
+ whole_words: bool = False,
229
+ use_regex: bool = False,
230
+ max_matches: int = 1000,
231
+ progress_callback: Optional[Callable] = None) -> str:
232
+ """
233
+ Generate a preview of find/replace operation without modifying the text.
234
+
235
+ Returns:
236
+ Operation ID for tracking the preview generation
237
+ """
238
+ return self.find_and_replace(
239
+ text_widget=text_widget,
240
+ find_pattern=find_pattern,
241
+ replace_text=replace_text,
242
+ case_sensitive=case_sensitive,
243
+ whole_words=whole_words,
244
+ use_regex=use_regex,
245
+ max_replacements=max_matches,
246
+ processing_mode=ProcessingMode.PREVIEW_ONLY,
247
+ progress_callback=progress_callback
248
+ )
249
+
250
+ def _process_operation(self, operation: FindReplaceOperation):
251
+ """Process a find/replace operation in the background."""
252
+ try:
253
+ operation.start_time = time.time()
254
+
255
+ # Build search pattern
256
+ pattern = self._build_search_pattern(operation)
257
+ if pattern is None:
258
+ return
259
+
260
+ # Process based on mode
261
+ if operation.processing_mode == ProcessingMode.IMMEDIATE:
262
+ self._process_immediate(operation, pattern)
263
+ elif operation.processing_mode == ProcessingMode.CHUNKED:
264
+ self._process_chunked(operation, pattern)
265
+ elif operation.processing_mode == ProcessingMode.STREAMING:
266
+ self._process_streaming(operation, pattern)
267
+ elif operation.processing_mode == ProcessingMode.PREVIEW_ONLY:
268
+ self._process_preview_only(operation, pattern)
269
+
270
+ # Update performance stats
271
+ operation.progress.time_elapsed = time.time() - operation.start_time
272
+
273
+ with self.operation_lock:
274
+ if not operation.is_cancelled:
275
+ self.performance_stats['completed_operations'] += 1
276
+ self.performance_stats['total_matches_processed'] += len(operation.matches)
277
+ self.performance_stats['total_processing_time'] += operation.progress.time_elapsed
278
+
279
+ # Update average processing time
280
+ if self.performance_stats['completed_operations'] > 0:
281
+ self.performance_stats['average_processing_time'] = (
282
+ self.performance_stats['total_processing_time'] /
283
+ self.performance_stats['completed_operations']
284
+ )
285
+
286
+ # Remove from active operations
287
+ self.active_operations.pop(operation.operation_id, None)
288
+
289
+ # Call completion callback
290
+ if operation.completion_callback and not operation.is_cancelled:
291
+ operation.completion_callback(operation)
292
+
293
+ except Exception as e:
294
+ with self.operation_lock:
295
+ self.performance_stats['error_operations'] += 1
296
+ self.active_operations.pop(operation.operation_id, None)
297
+
298
+ if operation.error_callback:
299
+ operation.error_callback(operation, str(e))
300
+
301
+ def _build_search_pattern(self, operation: FindReplaceOperation) -> Optional[re.Pattern]:
302
+ """Build the search pattern based on operation parameters."""
303
+ try:
304
+ pattern_str = operation.find_pattern
305
+
306
+ # Handle whole words
307
+ if operation.whole_words and not operation.use_regex:
308
+ pattern_str = r'\b' + re.escape(pattern_str) + r'\b'
309
+ elif not operation.use_regex:
310
+ pattern_str = re.escape(pattern_str)
311
+
312
+ # Build flags
313
+ flags = 0
314
+ if not operation.case_sensitive:
315
+ flags |= re.IGNORECASE
316
+
317
+ return re.compile(pattern_str, flags)
318
+
319
+ except re.error as e:
320
+ if operation.error_callback:
321
+ operation.error_callback(operation, f"Invalid regex pattern: {e}")
322
+ return None
323
+
324
+ def _process_immediate(self, operation: FindReplaceOperation, pattern: re.Pattern):
325
+ """Process the entire text immediately."""
326
+ content = operation.original_text
327
+ matches = []
328
+
329
+ # Find all matches
330
+ for match_num, match in enumerate(pattern.finditer(content)):
331
+ if operation.is_cancelled:
332
+ break
333
+
334
+ if operation.max_replacements > 0 and len(matches) >= operation.max_replacements:
335
+ break
336
+
337
+ replacement = self._get_replacement_text(operation, match)
338
+
339
+ replace_match = ReplaceMatch(
340
+ start=match.start(),
341
+ end=match.end(),
342
+ original_text=match.group(),
343
+ replacement_text=replacement,
344
+ match_number=match_num
345
+ )
346
+
347
+ matches.append(replace_match)
348
+
349
+ operation.matches = matches
350
+ operation.progress.matches_found = len(matches)
351
+ operation.progress.processed_chars = len(content)
352
+
353
+ # Apply replacements if not preview mode
354
+ if operation.processing_mode != ProcessingMode.PREVIEW_ONLY:
355
+ operation.processed_text = self._apply_replacements(content, matches)
356
+ self._update_text_widget(operation)
357
+
358
+ def _process_chunked(self, operation: FindReplaceOperation, pattern: re.Pattern):
359
+ """Process text in chunks with progress updates."""
360
+ content = operation.original_text
361
+ matches = []
362
+ chunk_size = operation.chunk_size
363
+ last_update_time = time.time()
364
+
365
+ # Process in overlapping chunks to handle matches that span chunk boundaries
366
+ overlap = min(1000, chunk_size // 10) # 10% overlap or 1KB max
367
+
368
+ for i in range(0, len(content), chunk_size - overlap):
369
+ if operation.is_cancelled:
370
+ break
371
+
372
+ # Get chunk with overlap
373
+ chunk_start = i
374
+ chunk_end = min(i + chunk_size, len(content))
375
+ chunk = content[chunk_start:chunk_end]
376
+
377
+ # Find matches in chunk
378
+ chunk_matches = []
379
+ for match_num, match in enumerate(pattern.finditer(chunk)):
380
+ if operation.max_replacements > 0 and len(matches) >= operation.max_replacements:
381
+ break
382
+
383
+ # Adjust match positions to global coordinates
384
+ global_start = chunk_start + match.start()
385
+ global_end = chunk_start + match.end()
386
+
387
+ # Skip if this match overlaps with previous chunk (avoid duplicates)
388
+ if i > 0 and global_start < i:
389
+ continue
390
+
391
+ replacement = self._get_replacement_text(operation, match)
392
+
393
+ replace_match = ReplaceMatch(
394
+ start=global_start,
395
+ end=global_end,
396
+ original_text=match.group(),
397
+ replacement_text=replacement,
398
+ match_number=len(matches)
399
+ )
400
+
401
+ matches.append(replace_match)
402
+ chunk_matches.append(replace_match)
403
+
404
+ # Update progress
405
+ operation.progress.matches_found = len(matches)
406
+ operation.progress.processed_chars = min(chunk_end, len(content))
407
+ operation.progress.chunks_completed += 1
408
+
409
+ # Call progress callback periodically
410
+ if (time.time() - last_update_time > self.progress_update_interval and
411
+ operation.progress_callback):
412
+ operation.progress_callback(operation)
413
+ last_update_time = time.time()
414
+
415
+ # Small delay to prevent UI blocking
416
+ time.sleep(0.001)
417
+
418
+ operation.matches = matches
419
+ operation.progress.processed_chars = len(content)
420
+
421
+ # Apply replacements if not preview mode
422
+ if operation.processing_mode != ProcessingMode.PREVIEW_ONLY:
423
+ operation.processed_text = self._apply_replacements(content, matches)
424
+ self._update_text_widget(operation)
425
+
426
+ def _process_streaming(self, operation: FindReplaceOperation, pattern: re.Pattern):
427
+ """Process text with streaming updates."""
428
+ content = operation.original_text
429
+ matches = []
430
+ processed_text = content
431
+ offset = 0 # Track offset due to replacements
432
+
433
+ for match_num, match in enumerate(pattern.finditer(content)):
434
+ if operation.is_cancelled:
435
+ break
436
+
437
+ if operation.max_replacements > 0 and len(matches) >= operation.max_replacements:
438
+ break
439
+
440
+ replacement = self._get_replacement_text(operation, match)
441
+
442
+ replace_match = ReplaceMatch(
443
+ start=match.start(),
444
+ end=match.end(),
445
+ original_text=match.group(),
446
+ replacement_text=replacement,
447
+ match_number=match_num
448
+ )
449
+
450
+ matches.append(replace_match)
451
+
452
+ # Apply replacement immediately if not preview mode
453
+ if operation.processing_mode != ProcessingMode.PREVIEW_ONLY:
454
+ # Adjust positions for previous replacements
455
+ adjusted_start = match.start() + offset
456
+ adjusted_end = match.end() + offset
457
+
458
+ # Replace in processed text
459
+ processed_text = (
460
+ processed_text[:adjusted_start] +
461
+ replacement +
462
+ processed_text[adjusted_end:]
463
+ )
464
+
465
+ # Update offset
466
+ offset += len(replacement) - len(match.group())
467
+
468
+ # Update text widget periodically
469
+ if len(matches) % 10 == 0: # Every 10 matches
470
+ self._update_text_widget_partial(operation, processed_text)
471
+
472
+ # Update progress
473
+ operation.progress.matches_found = len(matches)
474
+ operation.progress.processed_chars = match.end()
475
+
476
+ # Call progress callback
477
+ if operation.progress_callback:
478
+ operation.progress_callback(operation)
479
+
480
+ operation.matches = matches
481
+ operation.processed_text = processed_text
482
+ operation.progress.processed_chars = len(content)
483
+
484
+ # Final update if not preview mode
485
+ if operation.processing_mode != ProcessingMode.PREVIEW_ONLY:
486
+ self._update_text_widget(operation)
487
+
488
+ def _process_preview_only(self, operation: FindReplaceOperation, pattern: re.Pattern):
489
+ """Process for preview only - find matches but don't replace."""
490
+ content = operation.original_text
491
+ matches = []
492
+
493
+ # Limit matches for preview to avoid performance issues
494
+ max_preview_matches = min(operation.max_replacements if operation.max_replacements > 0 else 1000, 1000)
495
+
496
+ for match_num, match in enumerate(pattern.finditer(content)):
497
+ if operation.is_cancelled:
498
+ break
499
+
500
+ if len(matches) >= max_preview_matches:
501
+ break
502
+
503
+ replacement = self._get_replacement_text(operation, match)
504
+
505
+ replace_match = ReplaceMatch(
506
+ start=match.start(),
507
+ end=match.end(),
508
+ original_text=match.group(),
509
+ replacement_text=replacement,
510
+ match_number=match_num
511
+ )
512
+
513
+ matches.append(replace_match)
514
+
515
+ # Update progress periodically
516
+ if match_num % 100 == 0 and operation.progress_callback:
517
+ operation.progress.matches_found = len(matches)
518
+ operation.progress.processed_chars = match.end()
519
+ operation.progress_callback(operation)
520
+
521
+ operation.matches = matches
522
+ operation.progress.matches_found = len(matches)
523
+ operation.progress.processed_chars = len(content)
524
+
525
+ # Generate preview text
526
+ operation.processed_text = self._apply_replacements(content, matches)
527
+
528
+ def _get_replacement_text(self, operation: FindReplaceOperation, match: re.Match) -> str:
529
+ """Get the replacement text for a match."""
530
+ if operation.operation_type == ReplaceOperation.REGEX:
531
+ try:
532
+ return match.expand(operation.replace_text)
533
+ except re.error:
534
+ return operation.replace_text
535
+ else:
536
+ return operation.replace_text
537
+
538
+ def _apply_replacements(self, content: str, matches: List[ReplaceMatch]) -> str:
539
+ """Apply all replacements to the content."""
540
+ if not matches:
541
+ return content
542
+
543
+ # Sort matches by position (reverse order to maintain positions)
544
+ sorted_matches = sorted(matches, key=lambda m: m.start, reverse=True)
545
+
546
+ result = content
547
+ for match in sorted_matches:
548
+ result = result[:match.start] + match.replacement_text + result[match.end:]
549
+
550
+ return result
551
+
552
+ def _update_text_widget(self, operation: FindReplaceOperation):
553
+ """Update the text widget with processed text."""
554
+ def update():
555
+ try:
556
+ operation.text_widget.config(state="normal")
557
+ operation.text_widget.delete("1.0", tk.END)
558
+ operation.text_widget.insert("1.0", operation.processed_text)
559
+ operation.text_widget.config(state="disabled")
560
+ except tk.TclError:
561
+ pass
562
+
563
+ operation.text_widget.after_idle(update)
564
+
565
+ def _update_text_widget_partial(self, operation: FindReplaceOperation, text: str):
566
+ """Update the text widget with partial processed text."""
567
+ def update():
568
+ try:
569
+ operation.text_widget.config(state="normal")
570
+ operation.text_widget.delete("1.0", tk.END)
571
+ operation.text_widget.insert("1.0", text)
572
+ operation.text_widget.config(state="disabled")
573
+ except tk.TclError:
574
+ pass
575
+
576
+ operation.text_widget.after_idle(update)
577
+
578
+ def cancel_operation(self, operation_id: str) -> bool:
579
+ """Cancel a running find/replace operation."""
580
+ with self.operation_lock:
581
+ if operation_id in self.active_operations:
582
+ operation = self.active_operations[operation_id]
583
+ operation.is_cancelled = True
584
+ self.performance_stats['cancelled_operations'] += 1
585
+ return True
586
+ return False
587
+
588
+ def cancel_all_operations(self):
589
+ """Cancel all running find/replace operations."""
590
+ with self.operation_lock:
591
+ for operation in self.active_operations.values():
592
+ operation.is_cancelled = True
593
+ self.performance_stats['cancelled_operations'] += len(self.active_operations)
594
+ self.active_operations.clear()
595
+
596
+ def get_operation_status(self, operation_id: str) -> Optional[FindReplaceOperation]:
597
+ """Get the status of a find/replace operation."""
598
+ with self.operation_lock:
599
+ return self.active_operations.get(operation_id)
600
+
601
+ def get_active_operations(self) -> List[str]:
602
+ """Get list of active operation IDs."""
603
+ with self.operation_lock:
604
+ return list(self.active_operations.keys())
605
+
606
+ def get_performance_stats(self) -> Dict[str, Any]:
607
+ """Get performance statistics."""
608
+ with self.operation_lock:
609
+ return self.performance_stats.copy()
610
+
611
+ def process_find_replace(self, text, find_pattern, replace_text, mode="Text", options=None):
612
+ """
613
+ Process find and replace operation with compatibility for the main application.
614
+
615
+ Args:
616
+ text: Input text to process
617
+ find_pattern: Pattern to search for
618
+ replace_text: Text to replace matches with
619
+ mode: "Text" or "Regex"
620
+ options: Dictionary with options like ignore_case, whole_words, etc.
621
+
622
+ Returns:
623
+ Result object with processed_text, success, error_message, processing_time_ms
624
+ """
625
+ from dataclasses import dataclass
626
+ import time
627
+ import re
628
+
629
+ @dataclass
630
+ class ProcessResult:
631
+ processed_text: str = ""
632
+ success: bool = True
633
+ error_message: str = ""
634
+ processing_time_ms: float = 0.0
635
+
636
+ start_time = time.time()
637
+ result = ProcessResult()
638
+
639
+ try:
640
+ if not options:
641
+ options = {}
642
+
643
+ # Convert options to our format
644
+ case_sensitive = not options.get('ignore_case', False)
645
+ whole_words = options.get('whole_words', False)
646
+ use_regex = (mode == "Regex")
647
+
648
+ # Process directly without using the async worker
649
+ if use_regex:
650
+ # Handle regex mode
651
+ try:
652
+ flags = 0 if case_sensitive else re.IGNORECASE
653
+ result.processed_text = re.sub(find_pattern, replace_text, text, flags=flags)
654
+ result.success = True
655
+ except re.error as e:
656
+ result.success = False
657
+ result.error_message = f"Regex error: {e}"
658
+ result.processed_text = text
659
+ else:
660
+ # Handle text mode
661
+ if whole_words:
662
+ # Whole words matching
663
+ pattern = r'\b' + re.escape(find_pattern) + r'\b'
664
+ flags = 0 if case_sensitive else re.IGNORECASE
665
+ try:
666
+ result.processed_text = re.sub(pattern, replace_text, text, flags=flags)
667
+ result.success = True
668
+ except re.error as e:
669
+ result.success = False
670
+ result.error_message = f"Whole words error: {e}"
671
+ result.processed_text = text
672
+ else:
673
+ # Simple text replacement
674
+ if case_sensitive:
675
+ result.processed_text = text.replace(find_pattern, replace_text)
676
+ else:
677
+ # Case-insensitive replacement
678
+ pattern = re.escape(find_pattern)
679
+ result.processed_text = re.sub(pattern, replace_text, text, flags=re.IGNORECASE)
680
+ result.success = True
681
+
682
+ except Exception as e:
683
+ result.success = False
684
+ result.error_message = str(e)
685
+ result.processed_text = text
686
+
687
+ result.processing_time_ms = (time.time() - start_time) * 1000
688
+ return result
689
+
690
+ def shutdown(self):
691
+ """Shutdown the processor and cleanup resources."""
692
+ self.cancel_all_operations()
693
+ self.shutdown_event.set()
694
+
695
+ if self.worker_thread and self.worker_thread.is_alive():
696
+ # Signal shutdown
697
+ self.operation_queue.put(None)
698
+ self.worker_thread.join(timeout=2.0)
699
+
700
+ # Global instance
701
+ _global_find_replace_processor = None
702
+
703
+ def get_find_replace_processor() -> OptimizedFindReplace:
704
+ """Get the global find/replace processor instance."""
705
+ global _global_find_replace_processor
706
+ if _global_find_replace_processor is None:
707
+ _global_find_replace_processor = OptimizedFindReplace()
708
+ return _global_find_replace_processor
709
+
710
+ def shutdown_find_replace_processor():
711
+ """Shutdown the global find/replace processor."""
712
+ global _global_find_replace_processor
713
+ if _global_find_replace_processor is not None:
714
+ _global_find_replace_processor.shutdown()
715
+ _global_find_replace_processor = None