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