kollabor 0.4.9__py3-none-any.whl
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.
- core/__init__.py +18 -0
- core/application.py +578 -0
- core/cli.py +193 -0
- core/commands/__init__.py +43 -0
- core/commands/executor.py +277 -0
- core/commands/menu_renderer.py +319 -0
- core/commands/parser.py +186 -0
- core/commands/registry.py +331 -0
- core/commands/system_commands.py +479 -0
- core/config/__init__.py +7 -0
- core/config/llm_task_config.py +110 -0
- core/config/loader.py +501 -0
- core/config/manager.py +112 -0
- core/config/plugin_config_manager.py +346 -0
- core/config/plugin_schema.py +424 -0
- core/config/service.py +399 -0
- core/effects/__init__.py +1 -0
- core/events/__init__.py +12 -0
- core/events/bus.py +129 -0
- core/events/executor.py +154 -0
- core/events/models.py +258 -0
- core/events/processor.py +176 -0
- core/events/registry.py +289 -0
- core/fullscreen/__init__.py +19 -0
- core/fullscreen/command_integration.py +290 -0
- core/fullscreen/components/__init__.py +12 -0
- core/fullscreen/components/animation.py +258 -0
- core/fullscreen/components/drawing.py +160 -0
- core/fullscreen/components/matrix_components.py +177 -0
- core/fullscreen/manager.py +302 -0
- core/fullscreen/plugin.py +204 -0
- core/fullscreen/renderer.py +282 -0
- core/fullscreen/session.py +324 -0
- core/io/__init__.py +52 -0
- core/io/buffer_manager.py +362 -0
- core/io/config_status_view.py +272 -0
- core/io/core_status_views.py +410 -0
- core/io/input_errors.py +313 -0
- core/io/input_handler.py +2655 -0
- core/io/input_mode_manager.py +402 -0
- core/io/key_parser.py +344 -0
- core/io/layout.py +587 -0
- core/io/message_coordinator.py +204 -0
- core/io/message_renderer.py +601 -0
- core/io/modal_interaction_handler.py +315 -0
- core/io/raw_input_processor.py +946 -0
- core/io/status_renderer.py +845 -0
- core/io/terminal_renderer.py +586 -0
- core/io/terminal_state.py +551 -0
- core/io/visual_effects.py +734 -0
- core/llm/__init__.py +26 -0
- core/llm/api_communication_service.py +863 -0
- core/llm/conversation_logger.py +473 -0
- core/llm/conversation_manager.py +414 -0
- core/llm/file_operations_executor.py +1401 -0
- core/llm/hook_system.py +402 -0
- core/llm/llm_service.py +1629 -0
- core/llm/mcp_integration.py +386 -0
- core/llm/message_display_service.py +450 -0
- core/llm/model_router.py +214 -0
- core/llm/plugin_sdk.py +396 -0
- core/llm/response_parser.py +848 -0
- core/llm/response_processor.py +364 -0
- core/llm/tool_executor.py +520 -0
- core/logging/__init__.py +19 -0
- core/logging/setup.py +208 -0
- core/models/__init__.py +5 -0
- core/models/base.py +23 -0
- core/plugins/__init__.py +13 -0
- core/plugins/collector.py +212 -0
- core/plugins/discovery.py +386 -0
- core/plugins/factory.py +263 -0
- core/plugins/registry.py +152 -0
- core/storage/__init__.py +5 -0
- core/storage/state_manager.py +84 -0
- core/ui/__init__.py +6 -0
- core/ui/config_merger.py +176 -0
- core/ui/config_widgets.py +369 -0
- core/ui/live_modal_renderer.py +276 -0
- core/ui/modal_actions.py +162 -0
- core/ui/modal_overlay_renderer.py +373 -0
- core/ui/modal_renderer.py +591 -0
- core/ui/modal_state_manager.py +443 -0
- core/ui/widget_integration.py +222 -0
- core/ui/widgets/__init__.py +27 -0
- core/ui/widgets/base_widget.py +136 -0
- core/ui/widgets/checkbox.py +85 -0
- core/ui/widgets/dropdown.py +140 -0
- core/ui/widgets/label.py +78 -0
- core/ui/widgets/slider.py +185 -0
- core/ui/widgets/text_input.py +224 -0
- core/utils/__init__.py +11 -0
- core/utils/config_utils.py +656 -0
- core/utils/dict_utils.py +212 -0
- core/utils/error_utils.py +275 -0
- core/utils/key_reader.py +171 -0
- core/utils/plugin_utils.py +267 -0
- core/utils/prompt_renderer.py +151 -0
- kollabor-0.4.9.dist-info/METADATA +298 -0
- kollabor-0.4.9.dist-info/RECORD +128 -0
- kollabor-0.4.9.dist-info/WHEEL +5 -0
- kollabor-0.4.9.dist-info/entry_points.txt +2 -0
- kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
- kollabor-0.4.9.dist-info/top_level.txt +4 -0
- kollabor_cli_main.py +20 -0
- plugins/__init__.py +1 -0
- plugins/enhanced_input/__init__.py +18 -0
- plugins/enhanced_input/box_renderer.py +103 -0
- plugins/enhanced_input/box_styles.py +142 -0
- plugins/enhanced_input/color_engine.py +165 -0
- plugins/enhanced_input/config.py +150 -0
- plugins/enhanced_input/cursor_manager.py +72 -0
- plugins/enhanced_input/geometry.py +81 -0
- plugins/enhanced_input/state.py +130 -0
- plugins/enhanced_input/text_processor.py +115 -0
- plugins/enhanced_input_plugin.py +385 -0
- plugins/fullscreen/__init__.py +9 -0
- plugins/fullscreen/example_plugin.py +327 -0
- plugins/fullscreen/matrix_plugin.py +132 -0
- plugins/hook_monitoring_plugin.py +1299 -0
- plugins/query_enhancer_plugin.py +350 -0
- plugins/save_conversation_plugin.py +502 -0
- plugins/system_commands_plugin.py +93 -0
- plugins/tmux_plugin.py +795 -0
- plugins/workflow_enforcement_plugin.py +629 -0
- system_prompt/default.md +1286 -0
- system_prompt/default_win.md +265 -0
- system_prompt/example_with_trender.md +47 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""State management for Enhanced Input Plugin."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import random
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .config import InputConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PluginState:
|
|
12
|
+
"""Manages runtime state for Enhanced Input Plugin."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, config: 'InputConfig'):
|
|
15
|
+
"""Initialize plugin state.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
config: Plugin configuration instance.
|
|
19
|
+
"""
|
|
20
|
+
self.config = config
|
|
21
|
+
|
|
22
|
+
# Randomization state
|
|
23
|
+
self.last_randomize_time = 0
|
|
24
|
+
self.current_random_style = config.style
|
|
25
|
+
|
|
26
|
+
# Cursor blinking state
|
|
27
|
+
self.cursor_visible = True
|
|
28
|
+
self.last_blink_time = 0
|
|
29
|
+
|
|
30
|
+
# Curated list of sophisticated styles matching user preferences
|
|
31
|
+
self.random_styles = [
|
|
32
|
+
# User's confirmed favorites
|
|
33
|
+
"dots_only", "brackets", "dotted", "dashed", "square",
|
|
34
|
+
# Clean classics
|
|
35
|
+
"rounded", "double", "thick", "underline", "minimal",
|
|
36
|
+
# New sophisticated mixed-weight styles
|
|
37
|
+
"mixed_weight", "typography", "sophisticated", "editorial",
|
|
38
|
+
"clean_corners", "refined", "gradient_line",
|
|
39
|
+
# Clean minimal lines
|
|
40
|
+
"lines_only", "thick_lines", "double_lines"
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def get_current_style(self) -> str:
|
|
44
|
+
"""Get the current style, handling randomization.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Current active style name.
|
|
48
|
+
"""
|
|
49
|
+
current_time = time.time()
|
|
50
|
+
|
|
51
|
+
# Handle randomization
|
|
52
|
+
if self.config.randomize_style or self.config.style == "random":
|
|
53
|
+
if (current_time - self.last_randomize_time) >= self.config.randomize_interval:
|
|
54
|
+
self.current_random_style = self._get_random_style()
|
|
55
|
+
self.last_randomize_time = current_time
|
|
56
|
+
return self.current_random_style
|
|
57
|
+
|
|
58
|
+
return self.config.style
|
|
59
|
+
|
|
60
|
+
def _get_random_style(self) -> str:
|
|
61
|
+
"""Get a random style from curated good styles.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Random style name.
|
|
65
|
+
"""
|
|
66
|
+
return random.choice(self.random_styles)
|
|
67
|
+
|
|
68
|
+
def update_cursor_blink(self, input_is_active: bool) -> None:
|
|
69
|
+
"""Update cursor blinking state.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
input_is_active: Whether input is currently active.
|
|
73
|
+
"""
|
|
74
|
+
if not input_is_active:
|
|
75
|
+
self.cursor_visible = True
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
current_time = time.time()
|
|
79
|
+
if current_time - self.last_blink_time >= self.config.cursor_blink_rate:
|
|
80
|
+
self.cursor_visible = not self.cursor_visible
|
|
81
|
+
self.last_blink_time = current_time
|
|
82
|
+
|
|
83
|
+
def get_cursor_char(self, input_is_active: bool) -> str:
|
|
84
|
+
"""Get the current cursor character.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
input_is_active: Whether input is currently active.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Cursor character to display.
|
|
91
|
+
"""
|
|
92
|
+
if input_is_active:
|
|
93
|
+
return "█" if self.cursor_visible else " "
|
|
94
|
+
else:
|
|
95
|
+
return "█"
|
|
96
|
+
|
|
97
|
+
def is_input_active(self, renderer) -> bool:
|
|
98
|
+
"""Determine if input is currently active.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
renderer: Terminal renderer instance.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
True if input should be considered active.
|
|
105
|
+
"""
|
|
106
|
+
writing_messages = getattr(renderer, 'writing_messages', False)
|
|
107
|
+
thinking_active = getattr(renderer, 'thinking_active', False)
|
|
108
|
+
conversation_active = getattr(renderer, 'conversation_active', True)
|
|
109
|
+
|
|
110
|
+
return not writing_messages and not thinking_active and conversation_active
|
|
111
|
+
|
|
112
|
+
def get_status_display(self) -> str:
|
|
113
|
+
"""Get status display string for current style.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Status string showing current style.
|
|
117
|
+
"""
|
|
118
|
+
current_style = self.get_current_style()
|
|
119
|
+
if self.config.randomize_style or self.config.style == "random":
|
|
120
|
+
return f"{current_style} (random)"
|
|
121
|
+
return current_style
|
|
122
|
+
|
|
123
|
+
def reset_randomization(self) -> None:
|
|
124
|
+
"""Reset randomization timer."""
|
|
125
|
+
self.last_randomize_time = 0
|
|
126
|
+
|
|
127
|
+
def reset_cursor_blink(self) -> None:
|
|
128
|
+
"""Reset cursor blink state."""
|
|
129
|
+
self.cursor_visible = True
|
|
130
|
+
self.last_blink_time = 0
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Text processing utilities for enhanced input plugin."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TextProcessor:
|
|
8
|
+
"""Handles text wrapping, truncation, and visual length calculations."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, config):
|
|
11
|
+
"""Initialize text processor.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
config: InputConfig object with plugin configuration.
|
|
15
|
+
"""
|
|
16
|
+
self.config = config
|
|
17
|
+
|
|
18
|
+
def wrap_text(self, text: str, width: int) -> List[str]:
|
|
19
|
+
"""Wrap text to fit within the specified width.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
text: Text to wrap.
|
|
23
|
+
width: Maximum width for each line.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List of wrapped text lines.
|
|
27
|
+
"""
|
|
28
|
+
wrap_text = self.config.wrap_text
|
|
29
|
+
|
|
30
|
+
if not wrap_text or not text:
|
|
31
|
+
return [text] if text else [""]
|
|
32
|
+
|
|
33
|
+
words = text.split()
|
|
34
|
+
lines = []
|
|
35
|
+
current_line = ""
|
|
36
|
+
|
|
37
|
+
for word in words:
|
|
38
|
+
# Check if adding this word would exceed the width
|
|
39
|
+
test_line = f"{current_line} {word}".strip()
|
|
40
|
+
if len(test_line) <= width:
|
|
41
|
+
current_line = test_line
|
|
42
|
+
else:
|
|
43
|
+
# Start a new line
|
|
44
|
+
if current_line:
|
|
45
|
+
lines.append(current_line)
|
|
46
|
+
current_line = word
|
|
47
|
+
|
|
48
|
+
# Add the last line
|
|
49
|
+
if current_line:
|
|
50
|
+
lines.append(current_line)
|
|
51
|
+
|
|
52
|
+
return lines if lines else [""]
|
|
53
|
+
|
|
54
|
+
def get_visual_length(self, text: str) -> int:
|
|
55
|
+
"""Get the visual length of text, excluding ANSI escape codes.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
text: Text that may contain ANSI codes.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Visual length of the text.
|
|
62
|
+
"""
|
|
63
|
+
# Remove ANSI escape codes
|
|
64
|
+
ansi_escape = re.compile(r'\x1b\[[0-9;]*m')
|
|
65
|
+
clean_text = ansi_escape.sub('', text)
|
|
66
|
+
return len(clean_text)
|
|
67
|
+
|
|
68
|
+
def truncate_with_ellipsis(self, text: str, max_width: int) -> str:
|
|
69
|
+
"""Truncate text with ellipsis if it exceeds max width.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
text: Text to potentially truncate.
|
|
73
|
+
max_width: Maximum allowed width.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Truncated text with ellipsis if needed.
|
|
77
|
+
"""
|
|
78
|
+
visual_length = self.get_visual_length(text)
|
|
79
|
+
if visual_length > max_width:
|
|
80
|
+
# Truncate content but be careful with ANSI codes
|
|
81
|
+
return text[:max_width-3] + "..."
|
|
82
|
+
return text
|
|
83
|
+
|
|
84
|
+
def pad_to_width(self, text: str, target_width: int) -> str:
|
|
85
|
+
"""Pad text to target width with spaces.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
text: Text to pad.
|
|
89
|
+
target_width: Target width.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Padded text.
|
|
93
|
+
"""
|
|
94
|
+
visual_length = self.get_visual_length(text)
|
|
95
|
+
if visual_length < target_width:
|
|
96
|
+
padding_needed = target_width - visual_length
|
|
97
|
+
return text + " " * padding_needed
|
|
98
|
+
return text
|
|
99
|
+
|
|
100
|
+
def fit_text_to_width(self, text: str, target_width: int) -> str:
|
|
101
|
+
"""Fit text to target width by truncating or padding as needed.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
text: Text to fit.
|
|
105
|
+
target_width: Target width.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Text fitted to target width.
|
|
109
|
+
"""
|
|
110
|
+
visual_length = self.get_visual_length(text)
|
|
111
|
+
|
|
112
|
+
if visual_length > target_width:
|
|
113
|
+
return self.truncate_with_ellipsis(text, target_width)
|
|
114
|
+
else:
|
|
115
|
+
return self.pad_to_width(text, target_width)
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""Enhanced Input Plugin for Kollabor CLI - Refactored Version.
|
|
2
|
+
|
|
3
|
+
Provides enhanced input rendering with bordered boxes using Unicode box-drawing characters.
|
|
4
|
+
Now built with modular components following DRY principles.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Add parent directory to path so we can import from chat_app
|
|
15
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
16
|
+
|
|
17
|
+
from core.events import Event, EventType, Hook, HookPriority
|
|
18
|
+
|
|
19
|
+
# Import our modular components
|
|
20
|
+
from plugins.enhanced_input.config import InputConfig
|
|
21
|
+
from plugins.enhanced_input.state import PluginState
|
|
22
|
+
from plugins.enhanced_input.box_styles import BoxStyleRegistry
|
|
23
|
+
from plugins.enhanced_input.color_engine import ColorEngine
|
|
24
|
+
from plugins.enhanced_input.geometry import GeometryCalculator
|
|
25
|
+
from plugins.enhanced_input.text_processor import TextProcessor
|
|
26
|
+
from plugins.enhanced_input.cursor_manager import CursorManager
|
|
27
|
+
from plugins.enhanced_input.box_renderer import BoxRenderer
|
|
28
|
+
|
|
29
|
+
# Type hints (only needed for type checking)
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from core.config import ConfigManager
|
|
32
|
+
from core.storage import StateManager
|
|
33
|
+
from core.events import EventBus
|
|
34
|
+
from core.io.terminal_renderer import TerminalRenderer
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EnhancedInputPlugin:
|
|
38
|
+
"""Plugin that renders enhanced bordered input boxes using modular components."""
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def get_default_config() -> Dict[str, Any]:
|
|
42
|
+
"""Get the default configuration for the enhanced input plugin.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Default configuration dictionary for the enhanced input plugin.
|
|
46
|
+
"""
|
|
47
|
+
# Use the default config from InputConfig
|
|
48
|
+
default_config = InputConfig()
|
|
49
|
+
return default_config.get_default_config_dict()
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def get_startup_info(config: 'ConfigManager') -> List[str]:
|
|
53
|
+
"""Get startup information to display for this plugin.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
config: Configuration manager instance.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
List of strings to display during startup.
|
|
60
|
+
"""
|
|
61
|
+
enabled = config.get('plugins.enhanced_input.enabled', True)
|
|
62
|
+
style = config.get('plugins.enhanced_input.style', 'rounded')
|
|
63
|
+
return [
|
|
64
|
+
f"Enabled: {enabled}",
|
|
65
|
+
f"Style: {style}",
|
|
66
|
+
f"Unicode box drawing support"
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def get_config_widgets() -> Dict[str, Any]:
|
|
71
|
+
"""Get configuration widgets for this plugin.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Widget section definition for the config modal.
|
|
75
|
+
"""
|
|
76
|
+
return {
|
|
77
|
+
"title": "Enhanced Input Plugin",
|
|
78
|
+
"widgets": [
|
|
79
|
+
{
|
|
80
|
+
"type": "dropdown",
|
|
81
|
+
"label": "Box Style",
|
|
82
|
+
"config_path": "plugins.enhanced_input.style",
|
|
83
|
+
"options": ["rounded", "square", "double", "thick", "thin"],
|
|
84
|
+
"help": "Visual style of the input box border"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"type": "text_input",
|
|
88
|
+
"label": "Width",
|
|
89
|
+
"config_path": "plugins.enhanced_input.width",
|
|
90
|
+
"placeholder": "auto",
|
|
91
|
+
"help": "Box width (auto, pixels, or percentage)"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"type": "text_input",
|
|
95
|
+
"label": "Placeholder Text",
|
|
96
|
+
"config_path": "plugins.enhanced_input.placeholder",
|
|
97
|
+
"placeholder": "Type your message here...",
|
|
98
|
+
"help": "Text shown when input is empty"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"type": "checkbox",
|
|
102
|
+
"label": "Show Placeholder",
|
|
103
|
+
"config_path": "plugins.enhanced_input.show_placeholder",
|
|
104
|
+
"help": "Display placeholder text when input is empty"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"type": "slider",
|
|
108
|
+
"label": "Min Width",
|
|
109
|
+
"config_path": "plugins.enhanced_input.min_width",
|
|
110
|
+
"min_value": 20,
|
|
111
|
+
"max_value": 200,
|
|
112
|
+
"step": 10,
|
|
113
|
+
"help": "Minimum box width in characters"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"type": "slider",
|
|
117
|
+
"label": "Max Width",
|
|
118
|
+
"config_path": "plugins.enhanced_input.max_width",
|
|
119
|
+
"min_value": 40,
|
|
120
|
+
"max_value": 300,
|
|
121
|
+
"step": 10,
|
|
122
|
+
"help": "Maximum box width in characters"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"type": "checkbox",
|
|
126
|
+
"label": "Randomize Style",
|
|
127
|
+
"config_path": "plugins.enhanced_input.randomize_style",
|
|
128
|
+
"help": "Randomly change box style periodically"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"type": "slider",
|
|
132
|
+
"label": "Randomize Interval",
|
|
133
|
+
"config_path": "plugins.enhanced_input.randomize_interval",
|
|
134
|
+
"min_value": 1.0,
|
|
135
|
+
"max_value": 60.0,
|
|
136
|
+
"step": 1.0,
|
|
137
|
+
"help": "Seconds between style randomizations"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"type": "checkbox",
|
|
141
|
+
"label": "Dynamic Sizing",
|
|
142
|
+
"config_path": "plugins.enhanced_input.dynamic_sizing",
|
|
143
|
+
"help": "Automatically resize box based on content"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"type": "slider",
|
|
147
|
+
"label": "Min Height",
|
|
148
|
+
"config_path": "plugins.enhanced_input.min_height",
|
|
149
|
+
"min_value": 1,
|
|
150
|
+
"max_value": 20,
|
|
151
|
+
"step": 1,
|
|
152
|
+
"help": "Minimum box height in lines"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"type": "slider",
|
|
156
|
+
"label": "Max Height",
|
|
157
|
+
"config_path": "plugins.enhanced_input.max_height",
|
|
158
|
+
"min_value": 3,
|
|
159
|
+
"max_value": 50,
|
|
160
|
+
"step": 1,
|
|
161
|
+
"help": "Maximum box height in lines"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"type": "checkbox",
|
|
165
|
+
"label": "Wrap Text",
|
|
166
|
+
"config_path": "plugins.enhanced_input.wrap_text",
|
|
167
|
+
"help": "Enable text wrapping in input box"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"type": "slider",
|
|
171
|
+
"label": "Cursor Blink Rate",
|
|
172
|
+
"config_path": "plugins.enhanced_input.cursor_blink_rate",
|
|
173
|
+
"min_value": 0.1,
|
|
174
|
+
"max_value": 2.0,
|
|
175
|
+
"step": 0.1,
|
|
176
|
+
"help": "Cursor blink speed in seconds"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"type": "checkbox",
|
|
180
|
+
"label": "Show Status",
|
|
181
|
+
"config_path": "plugins.enhanced_input.show_status",
|
|
182
|
+
"help": "Display status information below input box"
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
def __init__(self, name: str, state_manager: 'StateManager', event_bus: 'EventBus',
|
|
188
|
+
renderer: 'TerminalRenderer', config: 'ConfigManager') -> None:
|
|
189
|
+
"""Initialize the enhanced input plugin.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
name: Plugin name.
|
|
193
|
+
state_manager: State management system.
|
|
194
|
+
event_bus: Event bus for hook registration.
|
|
195
|
+
renderer: Terminal renderer.
|
|
196
|
+
config: Configuration manager.
|
|
197
|
+
"""
|
|
198
|
+
self.name = name
|
|
199
|
+
self.state_manager = state_manager
|
|
200
|
+
self.event_bus = event_bus
|
|
201
|
+
self.renderer = renderer
|
|
202
|
+
|
|
203
|
+
# Initialize configuration
|
|
204
|
+
self.config = InputConfig.from_config_manager(config)
|
|
205
|
+
|
|
206
|
+
# Initialize state management
|
|
207
|
+
self.state = PluginState(self.config)
|
|
208
|
+
|
|
209
|
+
# Initialize modular components
|
|
210
|
+
self.box_styles = BoxStyleRegistry()
|
|
211
|
+
self.color_engine = ColorEngine(self.config)
|
|
212
|
+
self.geometry = GeometryCalculator(self.config)
|
|
213
|
+
self.text_processor = TextProcessor(self.config)
|
|
214
|
+
self.cursor_manager = CursorManager(self.config)
|
|
215
|
+
self.box_renderer = BoxRenderer(self.box_styles, self.color_engine, self.geometry, self.text_processor)
|
|
216
|
+
|
|
217
|
+
# Register hooks for input rendering
|
|
218
|
+
self.hooks = [
|
|
219
|
+
Hook(
|
|
220
|
+
name="render_fancy_input",
|
|
221
|
+
plugin_name=self.name,
|
|
222
|
+
event_type=EventType.INPUT_RENDER,
|
|
223
|
+
priority=HookPriority.DISPLAY.value,
|
|
224
|
+
callback=self._render_fancy_input
|
|
225
|
+
)
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
def get_status_lines(self) -> Dict[str, List[str]]:
|
|
229
|
+
"""Get status lines for the enhanced input plugin organized by area.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Dictionary with status lines organized by area A, B, C.
|
|
233
|
+
"""
|
|
234
|
+
# Check if status display is enabled for this plugin
|
|
235
|
+
if not self.config.show_status:
|
|
236
|
+
return {"A": [], "B": [], "C": []}
|
|
237
|
+
|
|
238
|
+
# UI-related status goes in area C
|
|
239
|
+
if not self.config.enabled:
|
|
240
|
+
return {"A": [], "B": [], "C": ["Enhanced Input: Off"]}
|
|
241
|
+
|
|
242
|
+
style_display = self.state.get_status_display()
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
"A": [],
|
|
246
|
+
"B": [],
|
|
247
|
+
"C": [
|
|
248
|
+
f"Input: {style_display}",
|
|
249
|
+
f"Width: {self.config.width_mode}"
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async def _render_fancy_input(self, data: Dict[str, Any], event: Event) -> Dict[str, Any]:
|
|
254
|
+
"""Render the fancy input box using modular components.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
data: Event data containing input information.
|
|
258
|
+
event: The event object.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Modified event data.
|
|
262
|
+
"""
|
|
263
|
+
if not self.config.enabled:
|
|
264
|
+
return {"status": "disabled"}
|
|
265
|
+
|
|
266
|
+
# Get input buffer and cursor position from renderer
|
|
267
|
+
input_text = getattr(self.renderer, 'input_buffer', '')
|
|
268
|
+
cursor_position = getattr(self.renderer, 'cursor_position', len(input_text))
|
|
269
|
+
|
|
270
|
+
# Determine if input is active and update cursor state
|
|
271
|
+
input_is_active = self.state.is_input_active(self.renderer)
|
|
272
|
+
self.state.update_cursor_blink(input_is_active)
|
|
273
|
+
|
|
274
|
+
# Get cursor character
|
|
275
|
+
cursor_char = self.state.get_cursor_char(input_is_active)
|
|
276
|
+
|
|
277
|
+
# Insert cursor at correct position
|
|
278
|
+
text_with_cursor = self.cursor_manager.insert_cursor(
|
|
279
|
+
input_text, cursor_position, cursor_char
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Prepare content with prompt
|
|
283
|
+
if input_text:
|
|
284
|
+
content_with_cursor = f"> {text_with_cursor}"
|
|
285
|
+
elif self.config.show_placeholder:
|
|
286
|
+
colored_placeholder = self.color_engine.apply_color(
|
|
287
|
+
self.config.placeholder, 'placeholder'
|
|
288
|
+
)
|
|
289
|
+
content_with_cursor = f"> {cursor_char}{colored_placeholder}"
|
|
290
|
+
else:
|
|
291
|
+
content_with_cursor = f"> {cursor_char}"
|
|
292
|
+
|
|
293
|
+
# Calculate box dimensions
|
|
294
|
+
box_width = self.geometry.calculate_box_width()
|
|
295
|
+
content_width = self.geometry.calculate_content_width(box_width)
|
|
296
|
+
|
|
297
|
+
# Process text (wrapping if enabled)
|
|
298
|
+
content_lines = self.text_processor.wrap_text(content_with_cursor, content_width)
|
|
299
|
+
|
|
300
|
+
# Calculate dynamic height
|
|
301
|
+
box_height = self.geometry.calculate_box_height(content_lines)
|
|
302
|
+
|
|
303
|
+
# Get current style
|
|
304
|
+
current_style = self.state.get_current_style()
|
|
305
|
+
|
|
306
|
+
# Render the complete box
|
|
307
|
+
fancy_lines = self.box_renderer.render_box(
|
|
308
|
+
content_lines=content_lines,
|
|
309
|
+
box_width=box_width,
|
|
310
|
+
style_name=current_style
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Store the fancy input lines for the renderer to use
|
|
314
|
+
data["fancy_input_lines"] = fancy_lines
|
|
315
|
+
|
|
316
|
+
return {"status": "rendered", "lines": len(fancy_lines), "fancy_input_lines": fancy_lines}
|
|
317
|
+
|
|
318
|
+
async def initialize(self) -> None:
|
|
319
|
+
"""Initialize the plugin."""
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
async def register_hooks(self) -> None:
|
|
323
|
+
"""Register all plugin hooks with the event bus."""
|
|
324
|
+
for hook in self.hooks:
|
|
325
|
+
await self.event_bus.register_hook(hook)
|
|
326
|
+
|
|
327
|
+
# Register status view
|
|
328
|
+
await self._register_status_view()
|
|
329
|
+
|
|
330
|
+
async def _register_status_view(self) -> None:
|
|
331
|
+
"""Register enhanced input status view."""
|
|
332
|
+
try:
|
|
333
|
+
# Check if renderer has status registry
|
|
334
|
+
if (hasattr(self.renderer, 'status_renderer') and
|
|
335
|
+
self.renderer.status_renderer and
|
|
336
|
+
hasattr(self.renderer.status_renderer, 'status_registry') and
|
|
337
|
+
self.renderer.status_renderer.status_registry):
|
|
338
|
+
|
|
339
|
+
from core.io.status_renderer import StatusViewConfig, BlockConfig
|
|
340
|
+
|
|
341
|
+
# Create input UI view
|
|
342
|
+
input_view = StatusViewConfig(
|
|
343
|
+
name="Input UI",
|
|
344
|
+
plugin_source="enhanced_input",
|
|
345
|
+
priority=200, # Lower than core views
|
|
346
|
+
blocks=[
|
|
347
|
+
BlockConfig(
|
|
348
|
+
width_fraction=1.0,
|
|
349
|
+
content_provider=self._get_input_ui_content,
|
|
350
|
+
title="Input UI",
|
|
351
|
+
priority=100
|
|
352
|
+
)
|
|
353
|
+
]
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
registry = self.renderer.status_renderer.status_registry
|
|
357
|
+
registry.register_status_view("enhanced_input", input_view)
|
|
358
|
+
logger.info(" Enhanced Input registered 'Input UI' status view")
|
|
359
|
+
|
|
360
|
+
else:
|
|
361
|
+
logger.debug("Status registry not available - cannot register status view")
|
|
362
|
+
|
|
363
|
+
except Exception as e:
|
|
364
|
+
logger.error(f"Failed to register enhanced input status view: {e}")
|
|
365
|
+
|
|
366
|
+
def _get_input_ui_content(self) -> List[str]:
|
|
367
|
+
"""Get input UI content for status view."""
|
|
368
|
+
try:
|
|
369
|
+
if not self.config.enabled:
|
|
370
|
+
return ["Input: - Off"]
|
|
371
|
+
|
|
372
|
+
style_display = self.state.get_status_display()
|
|
373
|
+
|
|
374
|
+
return [
|
|
375
|
+
f"Input: {style_display} ··· Width: {self.config.width_mode}"
|
|
376
|
+
]
|
|
377
|
+
|
|
378
|
+
except Exception as e:
|
|
379
|
+
logger.error(f"Error getting input UI content: {e}")
|
|
380
|
+
return ["Input UI: Error"]
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
async def shutdown(self) -> None:
|
|
384
|
+
"""Shutdown the plugin."""
|
|
385
|
+
pass
|