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,511 @@
1
+ """
2
+ Efficient line number rendering system for Promera AI Commander.
3
+ Optimizes line number display for large documents by only rendering visible lines
4
+ and implementing intelligent caching and debouncing.
5
+ """
6
+
7
+ import tkinter as tk
8
+ from tkinter import scrolledtext
9
+ import platform
10
+ import time
11
+ import threading
12
+ from typing import Dict, List, Tuple, Optional, Any
13
+ from dataclasses import dataclass
14
+
15
+ @dataclass
16
+ class LineInfo:
17
+ """Information about a line in the text widget."""
18
+ line_number: int
19
+ y_position: int
20
+ height: int
21
+ is_visible: bool
22
+
23
+ class EfficientLineNumbers(tk.Frame):
24
+ """
25
+ Optimized line number widget that only renders visible lines
26
+ and implements intelligent caching and debouncing.
27
+ """
28
+
29
+ def __init__(self, *args, **kwargs):
30
+ super().__init__(*args, **kwargs)
31
+
32
+ # Configuration
33
+ self.line_number_width = 50 # Adjustable width
34
+ self.debounce_delay = 5 # ms - very responsive updates
35
+ self.cache_size_limit = 1000 # Maximum cached line positions
36
+
37
+ # Create widgets
38
+ self.text = scrolledtext.ScrolledText(
39
+ self, wrap=tk.WORD, height=15, width=50, undo=True
40
+ )
41
+ self.linenumbers = tk.Canvas(
42
+ self, width=self.line_number_width, bg='#f0f0f0',
43
+ highlightthickness=0, bd=0
44
+ )
45
+
46
+ # Layout
47
+ self.linenumbers.pack(side=tk.LEFT, fill=tk.Y)
48
+ self.text.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
49
+
50
+ # Performance tracking
51
+ self.visible_lines_cache: Dict[str, LineInfo] = {}
52
+ self.last_scroll_position: Optional[Tuple[float, float]] = None
53
+ self.last_content_hash: Optional[str] = None
54
+ self.last_update_time: float = 0
55
+ self.pending_update_id: Optional[str] = None
56
+
57
+ # Rendering optimization
58
+ self.canvas_items: List[int] = [] # Track canvas items for efficient clearing
59
+ self.font_metrics: Optional[Dict[str, int]] = None
60
+ self.line_height: int = 16 # Default, will be calculated
61
+
62
+ # Setup event bindings
63
+ self._setup_event_bindings()
64
+
65
+ # Ensure scrollbar is properly connected
66
+ self._setup_scrollbar_sync()
67
+
68
+ # Initial render
69
+ self.after(10, self._update_line_numbers)
70
+
71
+ def _setup_event_bindings(self):
72
+ """Setup optimized event bindings."""
73
+ # Mouse wheel events on both text and line numbers
74
+ self.linenumbers.bind("<MouseWheel>", self._on_mousewheel)
75
+ self.linenumbers.bind("<Button-4>", self._on_mousewheel)
76
+ self.linenumbers.bind("<Button-5>", self._on_mousewheel)
77
+ self.text.bind("<MouseWheel>", self._on_text_mousewheel)
78
+ self.text.bind("<Button-4>", self._on_text_mousewheel)
79
+ self.text.bind("<Button-5>", self._on_text_mousewheel)
80
+
81
+ # Key events for navigation (works even when disabled)
82
+ self.text.bind("<Up>", self._on_navigation_key)
83
+ self.text.bind("<Down>", self._on_navigation_key)
84
+ self.text.bind("<Page_Up>", self._on_navigation_key)
85
+ self.text.bind("<Page_Down>", self._on_navigation_key)
86
+ self.text.bind("<Home>", self._on_navigation_key)
87
+ self.text.bind("<End>", self._on_navigation_key)
88
+
89
+ # Text modification events (with immediate and debounced updates)
90
+ self.text.bind("<<Modified>>", self._on_text_modified_debounced)
91
+ self.text.bind("<Configure>", self._on_configure_debounced)
92
+ self.text.bind("<KeyPress>", self._on_key_press)
93
+ self.text.bind("<KeyRelease>", self._on_key_release)
94
+ self.text.bind("<Button-1>", self._on_mouse_click)
95
+
96
+ # Focus events for optimization
97
+ self.text.bind("<FocusIn>", self._on_focus_in)
98
+ self.text.bind("<FocusOut>", self._on_focus_out)
99
+
100
+ def _setup_scrollbar_sync(self):
101
+ """Setup proper scrollbar synchronization."""
102
+ # Configure the scrollbar to call our custom scroll handler
103
+ original_yscrollcommand = self.text.cget('yscrollcommand')
104
+
105
+ def combined_scroll_command(*args):
106
+ # Call original scroll command (updates scrollbar)
107
+ if original_yscrollcommand:
108
+ self.text.tk.call(original_yscrollcommand, *args)
109
+ # Update line numbers immediately for scrollbar changes
110
+ self._update_line_numbers()
111
+
112
+ # Set up the text widget to call our combined handler
113
+ self.text.config(yscrollcommand=combined_scroll_command)
114
+
115
+ # Configure scrollbar to call our scroll handler
116
+ self.text.vbar.config(command=self._on_text_scroll)
117
+
118
+ # Also bind scrollbar events directly
119
+ self.text.vbar.bind("<Button-1>", lambda e: self.after(10, self._update_line_numbers))
120
+ self.text.vbar.bind("<B1-Motion>", lambda e: self.after(1, self._update_line_numbers))
121
+
122
+ def _on_text_scroll(self, *args):
123
+ """Handle scrolling with optimized line number updates."""
124
+ # Apply the scroll to the text widget
125
+ self.text.yview(*args)
126
+
127
+ # Immediate line number update for scrolling (no delay)
128
+ self._update_line_numbers()
129
+
130
+ def _on_mousewheel(self, event):
131
+ """Handle mouse wheel scrolling with platform-specific logic."""
132
+ if platform.system() == "Windows":
133
+ delta = int(-1 * (event.delta / 120))
134
+ elif platform.system() == "Darwin": # macOS
135
+ delta = int(-1 * event.delta)
136
+ else: # Linux
137
+ delta = -1 if event.num == 4 else 1
138
+
139
+ # Scroll the text widget
140
+ self.text.yview_scroll(delta, "units")
141
+
142
+ # Immediate line number update for mouse wheel scrolling (no delay)
143
+ self._update_line_numbers()
144
+ return "break"
145
+
146
+ def _on_text_modified_debounced(self, event=None):
147
+ """Handle text modifications with debouncing."""
148
+ if event and hasattr(event.widget, 'edit_modified') and event.widget.edit_modified():
149
+ event.widget.edit_modified(False)
150
+ self._schedule_line_number_update()
151
+
152
+ def _on_configure_debounced(self, event=None):
153
+ """Handle widget configuration changes with debouncing."""
154
+ self._schedule_line_number_update()
155
+
156
+ def _on_focus_in(self, event=None):
157
+ """Handle focus in - ensure line numbers are up to date."""
158
+ self._schedule_line_number_update()
159
+
160
+ def _on_focus_out(self, event=None):
161
+ """Handle focus out - can reduce update frequency."""
162
+ pass # Could implement reduced update frequency when not focused
163
+
164
+ def _on_key_press(self, event=None):
165
+ """Handle key press events - immediate update for Enter key."""
166
+ if event and event.keysym in ['Return', 'BackSpace', 'Delete']:
167
+ # For line-changing operations, update immediately
168
+ self.after_idle(self._update_line_numbers)
169
+ else:
170
+ # For other keys, use debounced update
171
+ self._schedule_line_number_update()
172
+
173
+ def _on_key_release(self, event=None):
174
+ """Handle key release events."""
175
+ # Schedule update after key release
176
+ self._schedule_line_number_update()
177
+
178
+ def _on_mouse_click(self, event=None):
179
+ """Handle mouse click events."""
180
+ # Update line numbers after mouse click (cursor position change)
181
+ self.after_idle(self._update_line_numbers)
182
+
183
+ def _on_text_mousewheel(self, event):
184
+ """Handle mouse wheel scrolling on text widget."""
185
+ # Let the text widget handle the scroll normally, then update line numbers immediately
186
+ self.after(1, self._update_line_numbers) # Very short delay to let text widget scroll first
187
+ # Don't return "break" so the text widget can handle the scroll
188
+
189
+ def _on_navigation_key(self, event):
190
+ """Handle navigation keys that might change the view."""
191
+ # Let the text widget handle the key first, then update line numbers
192
+ self.after(1, self._update_line_numbers)
193
+ # Don't return "break" so the text widget can handle the key
194
+
195
+ def _schedule_line_number_update(self):
196
+ """Schedule a debounced line number update."""
197
+ current_time = time.time() * 1000 # Convert to milliseconds
198
+
199
+ # Cancel pending update
200
+ if self.pending_update_id:
201
+ self.after_cancel(self.pending_update_id)
202
+
203
+ # Schedule new update
204
+ self.pending_update_id = self.after(
205
+ self.debounce_delay,
206
+ self._update_line_numbers
207
+ )
208
+
209
+ self.last_update_time = current_time
210
+
211
+ def _update_line_numbers(self):
212
+ """Update line numbers with optimizations."""
213
+ try:
214
+ self.pending_update_id = None
215
+
216
+ # Get current view information
217
+ view_info = self._get_view_info()
218
+ if not view_info:
219
+ return
220
+
221
+ # Check if update is actually needed
222
+ if not self._needs_update(view_info):
223
+ return
224
+
225
+ # Clear existing canvas items efficiently
226
+ self._clear_canvas_items()
227
+
228
+ # Get visible lines only
229
+ visible_lines = self._get_visible_lines()
230
+
231
+ # Render visible line numbers
232
+ self._render_line_numbers(visible_lines)
233
+
234
+ # Update cache
235
+ self._update_cache(view_info, visible_lines)
236
+
237
+ except Exception as e:
238
+ # Graceful error handling
239
+ print(f"Error updating line numbers: {e}")
240
+
241
+ def _get_view_info(self) -> Optional[Dict[str, Any]]:
242
+ """Get current view information."""
243
+ try:
244
+ return {
245
+ 'scroll_position': self.text.yview(),
246
+ 'widget_height': self.text.winfo_height(),
247
+ 'content_hash': self._get_content_hash()
248
+ }
249
+ except Exception:
250
+ return None
251
+
252
+ def _get_content_hash(self) -> str:
253
+ """Get a hash of the current content for change detection."""
254
+ try:
255
+ content = self.text.get("1.0", "end-1c")
256
+ # Simple hash based on content length and first/last chars
257
+ if content:
258
+ return f"{len(content)}_{content[:10]}_{content[-10:]}"
259
+ return "empty"
260
+ except Exception:
261
+ return "error"
262
+
263
+ def _needs_update(self, view_info: Dict[str, Any]) -> bool:
264
+ """Check if line numbers actually need updating."""
265
+ # Always update if no previous state
266
+ if self.last_scroll_position is None or self.last_content_hash is None:
267
+ return True
268
+
269
+ # Check if content changed
270
+ if view_info['content_hash'] != self.last_content_hash:
271
+ return True
272
+
273
+ # Check if scroll position changed significantly
274
+ old_top, old_bottom = self.last_scroll_position
275
+ new_top, new_bottom = view_info['scroll_position']
276
+
277
+ # Update if scroll position changed by more than 0.1% of view (more sensitive)
278
+ scroll_threshold = 0.001
279
+ if (abs(new_top - old_top) > scroll_threshold or
280
+ abs(new_bottom - old_bottom) > scroll_threshold):
281
+ return True
282
+
283
+ return False
284
+
285
+ def _clear_canvas_items(self):
286
+ """Efficiently clear canvas items."""
287
+ if self.canvas_items:
288
+ for item_id in self.canvas_items:
289
+ try:
290
+ self.linenumbers.delete(item_id)
291
+ except Exception:
292
+ pass # Item may already be deleted
293
+ self.canvas_items.clear()
294
+ else:
295
+ # Fallback to delete all
296
+ self.linenumbers.delete("all")
297
+
298
+ def _get_visible_lines(self) -> List[LineInfo]:
299
+ """Get information about currently visible lines."""
300
+ visible_lines = []
301
+
302
+ try:
303
+ # Get the first visible line
304
+ first_visible = self.text.index("@0,0")
305
+
306
+ # Calculate font metrics if not cached
307
+ if self.font_metrics is None:
308
+ self._calculate_font_metrics()
309
+
310
+ # Iterate through visible lines
311
+ current_index = first_visible
312
+ y_offset = 0
313
+
314
+ while True:
315
+ try:
316
+ # Get line display info
317
+ dline_info = self.text.dlineinfo(current_index)
318
+ if dline_info is None:
319
+ break
320
+
321
+ # Extract line information
322
+ x, y, width, height, baseline = dline_info
323
+ line_number = int(current_index.split('.')[0])
324
+
325
+ # Check if line is within visible area
326
+ widget_height = self.text.winfo_height()
327
+ if y > widget_height:
328
+ break
329
+
330
+ visible_lines.append(LineInfo(
331
+ line_number=line_number,
332
+ y_position=y,
333
+ height=height,
334
+ is_visible=True
335
+ ))
336
+
337
+ # Move to next line
338
+ next_index = self.text.index(f"{current_index}+1line")
339
+ if next_index == current_index:
340
+ break
341
+ current_index = next_index
342
+
343
+ except Exception:
344
+ break
345
+
346
+ except Exception as e:
347
+ print(f"Error getting visible lines: {e}")
348
+
349
+ return visible_lines
350
+
351
+ def _calculate_font_metrics(self):
352
+ """Calculate font metrics for line height estimation."""
353
+ try:
354
+ # Create a temporary text item to measure font
355
+ temp_item = self.linenumbers.create_text(
356
+ 0, 0, text="1", font=("TkDefaultFont",), anchor="nw"
357
+ )
358
+ bbox = self.linenumbers.bbox(temp_item)
359
+ if bbox:
360
+ self.line_height = bbox[3] - bbox[1]
361
+ self.linenumbers.delete(temp_item)
362
+
363
+ self.font_metrics = {
364
+ 'line_height': self.line_height,
365
+ 'char_width': 8 # Approximate
366
+ }
367
+ except Exception:
368
+ self.font_metrics = {
369
+ 'line_height': 16,
370
+ 'char_width': 8
371
+ }
372
+
373
+ def _render_line_numbers(self, visible_lines: List[LineInfo]):
374
+ """Render line numbers for visible lines only."""
375
+ if not visible_lines:
376
+ return
377
+
378
+ try:
379
+ # Calculate optimal text positioning
380
+ text_x = self.line_number_width - 5 # Right-aligned with padding
381
+
382
+ # Render each visible line number
383
+ for line_info in visible_lines:
384
+ try:
385
+ item_id = self.linenumbers.create_text(
386
+ text_x, line_info.y_position,
387
+ text=str(line_info.line_number),
388
+ anchor="ne", # Right-aligned
389
+ fill="gray",
390
+ font=("TkDefaultFont", "9")
391
+ )
392
+ self.canvas_items.append(item_id)
393
+ except Exception as e:
394
+ print(f"Error rendering line {line_info.line_number}: {e}")
395
+ continue
396
+
397
+ # Sync canvas scroll position with text widget
398
+ self._sync_canvas_scroll()
399
+
400
+ except Exception as e:
401
+ print(f"Error rendering line numbers: {e}")
402
+
403
+ def _sync_canvas_scroll(self):
404
+ """Synchronize canvas scroll position with text widget."""
405
+ try:
406
+ # Get the text widget's current scroll position
407
+ scroll_top, scroll_bottom = self.text.yview()
408
+
409
+ # Move the canvas to match the text widget's scroll position
410
+ self.linenumbers.yview_moveto(scroll_top)
411
+ except Exception as e:
412
+ print(f"Error syncing canvas scroll: {e}")
413
+
414
+ def _update_cache(self, view_info: Dict[str, Any], visible_lines: List[LineInfo]):
415
+ """Update internal cache with current state."""
416
+ self.last_scroll_position = view_info['scroll_position']
417
+ self.last_content_hash = view_info['content_hash']
418
+
419
+ # Update visible lines cache (with size limit)
420
+ cache_key = f"{view_info['content_hash']}_{view_info['scroll_position']}"
421
+ self.visible_lines_cache[cache_key] = visible_lines
422
+
423
+ # Limit cache size
424
+ if len(self.visible_lines_cache) > self.cache_size_limit:
425
+ # Remove oldest entries (simple FIFO)
426
+ oldest_keys = list(self.visible_lines_cache.keys())[:-self.cache_size_limit//2]
427
+ for key in oldest_keys:
428
+ self.visible_lines_cache.pop(key, None)
429
+
430
+ def get_performance_stats(self) -> Dict[str, Any]:
431
+ """Get performance statistics for monitoring."""
432
+ return {
433
+ 'cache_size': len(self.visible_lines_cache),
434
+ 'canvas_items': len(self.canvas_items),
435
+ 'last_update_time': self.last_update_time,
436
+ 'debounce_delay': self.debounce_delay,
437
+ 'line_height': self.line_height
438
+ }
439
+
440
+ def clear_cache(self):
441
+ """Clear internal caches for memory management."""
442
+ self.visible_lines_cache.clear()
443
+ self.last_scroll_position = None
444
+ self.last_content_hash = None
445
+
446
+ def set_line_number_width(self, width: int):
447
+ """Dynamically adjust line number width."""
448
+ self.line_number_width = width
449
+ self.linenumbers.config(width=width)
450
+ self._schedule_line_number_update()
451
+
452
+ def set_debounce_delay(self, delay: int):
453
+ """Adjust debounce delay for different performance needs."""
454
+ self.debounce_delay = max(10, min(500, delay)) # Clamp between 10-500ms
455
+
456
+ class OptimizedTextWithLineNumbers(EfficientLineNumbers):
457
+ """
458
+ Drop-in replacement for the original TextWithLineNumbers class
459
+ with all the performance optimizations including lazy updates.
460
+ """
461
+
462
+ def __init__(self, *args, **kwargs):
463
+ super().__init__(*args, **kwargs)
464
+
465
+ # Add performance monitoring integration if available
466
+ try:
467
+ from performance_monitor import get_performance_monitor, PerformanceContext
468
+ self.performance_monitor = get_performance_monitor()
469
+ self.performance_monitoring = True
470
+ except ImportError:
471
+ self.performance_monitor = None
472
+ self.performance_monitoring = False
473
+
474
+ def _on_text_modified(self, event=None):
475
+ """Compatibility method for the main application."""
476
+ # This method is called by the main application for compatibility
477
+ # Delegate to our optimized update method
478
+ self._schedule_line_number_update()
479
+
480
+ # Handle the modified flag like the original implementation
481
+ if event and hasattr(event.widget, 'edit_modified') and event.widget.edit_modified():
482
+ event.widget.edit_modified(False)
483
+
484
+ def _update_line_numbers(self):
485
+ """Override with performance monitoring."""
486
+ if self.performance_monitoring and self.performance_monitor:
487
+ try:
488
+ from performance_monitor import PerformanceContext
489
+ with PerformanceContext(self.performance_monitor, 'line_numbers_update'):
490
+ super()._update_line_numbers()
491
+ except ImportError:
492
+ # Fall back to non-monitored update
493
+ super()._update_line_numbers()
494
+ else:
495
+ super()._update_line_numbers()
496
+
497
+ def force_immediate_update(self):
498
+ """Force an immediate line number update."""
499
+ self._update_line_numbers()
500
+
501
+ def cleanup_resources(self):
502
+ """Clean up resources when widget is destroyed."""
503
+ # Clear caches
504
+ self.clear_cache()
505
+
506
+ def get_performance_info(self) -> Dict[str, Any]:
507
+ """Get comprehensive performance information."""
508
+ return self.get_performance_stats()
509
+
510
+ # Backward compatibility alias
511
+ TextWithLineNumbers = OptimizedTextWithLineNumbers