kollabor 0.4.9__py3-none-any.whl → 0.4.15__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.
- agents/__init__.py +2 -0
- agents/coder/__init__.py +0 -0
- agents/coder/agent.json +4 -0
- agents/coder/api-integration.md +2150 -0
- agents/coder/cli-pretty.md +765 -0
- agents/coder/code-review.md +1092 -0
- agents/coder/database-design.md +1525 -0
- agents/coder/debugging.md +1102 -0
- agents/coder/dependency-management.md +1397 -0
- agents/coder/git-workflow.md +1099 -0
- agents/coder/refactoring.md +1454 -0
- agents/coder/security-hardening.md +1732 -0
- agents/coder/system_prompt.md +1448 -0
- agents/coder/tdd.md +1367 -0
- agents/creative-writer/__init__.py +0 -0
- agents/creative-writer/agent.json +4 -0
- agents/creative-writer/character-development.md +1852 -0
- agents/creative-writer/dialogue-craft.md +1122 -0
- agents/creative-writer/plot-structure.md +1073 -0
- agents/creative-writer/revision-editing.md +1484 -0
- agents/creative-writer/system_prompt.md +690 -0
- agents/creative-writer/worldbuilding.md +2049 -0
- agents/data-analyst/__init__.py +30 -0
- agents/data-analyst/agent.json +4 -0
- agents/data-analyst/data-visualization.md +992 -0
- agents/data-analyst/exploratory-data-analysis.md +1110 -0
- agents/data-analyst/pandas-data-manipulation.md +1081 -0
- agents/data-analyst/sql-query-optimization.md +881 -0
- agents/data-analyst/statistical-analysis.md +1118 -0
- agents/data-analyst/system_prompt.md +928 -0
- agents/default/__init__.py +0 -0
- agents/default/agent.json +4 -0
- agents/default/dead-code.md +794 -0
- agents/default/explore-agent-system.md +585 -0
- agents/default/system_prompt.md +1448 -0
- agents/kollabor/__init__.py +0 -0
- agents/kollabor/analyze-plugin-lifecycle.md +175 -0
- agents/kollabor/analyze-terminal-rendering.md +388 -0
- agents/kollabor/code-review.md +1092 -0
- agents/kollabor/debug-mcp-integration.md +521 -0
- agents/kollabor/debug-plugin-hooks.md +547 -0
- agents/kollabor/debugging.md +1102 -0
- agents/kollabor/dependency-management.md +1397 -0
- agents/kollabor/git-workflow.md +1099 -0
- agents/kollabor/inspect-llm-conversation.md +148 -0
- agents/kollabor/monitor-event-bus.md +558 -0
- agents/kollabor/profile-performance.md +576 -0
- agents/kollabor/refactoring.md +1454 -0
- agents/kollabor/system_prompt copy.md +1448 -0
- agents/kollabor/system_prompt.md +757 -0
- agents/kollabor/trace-command-execution.md +178 -0
- agents/kollabor/validate-config.md +879 -0
- agents/research/__init__.py +0 -0
- agents/research/agent.json +4 -0
- agents/research/architecture-mapping.md +1099 -0
- agents/research/codebase-analysis.md +1077 -0
- agents/research/dependency-audit.md +1027 -0
- agents/research/performance-profiling.md +1047 -0
- agents/research/security-review.md +1359 -0
- agents/research/system_prompt.md +492 -0
- agents/technical-writer/__init__.py +0 -0
- agents/technical-writer/agent.json +4 -0
- agents/technical-writer/api-documentation.md +2328 -0
- agents/technical-writer/changelog-management.md +1181 -0
- agents/technical-writer/readme-writing.md +1360 -0
- agents/technical-writer/style-guide.md +1410 -0
- agents/technical-writer/system_prompt.md +653 -0
- agents/technical-writer/tutorial-creation.md +1448 -0
- core/__init__.py +0 -2
- core/application.py +343 -88
- core/cli.py +229 -10
- core/commands/menu_renderer.py +463 -59
- core/commands/registry.py +14 -9
- core/commands/system_commands.py +2461 -14
- core/config/loader.py +151 -37
- core/config/service.py +18 -6
- core/events/bus.py +29 -9
- core/events/executor.py +205 -75
- core/events/models.py +27 -8
- core/fullscreen/command_integration.py +20 -24
- core/fullscreen/components/__init__.py +10 -1
- core/fullscreen/components/matrix_components.py +1 -2
- core/fullscreen/components/space_shooter_components.py +654 -0
- core/fullscreen/plugin.py +5 -0
- core/fullscreen/renderer.py +52 -13
- core/fullscreen/session.py +52 -15
- core/io/__init__.py +29 -5
- core/io/buffer_manager.py +6 -1
- core/io/config_status_view.py +7 -29
- core/io/core_status_views.py +267 -347
- core/io/input/__init__.py +25 -0
- core/io/input/command_mode_handler.py +711 -0
- core/io/input/display_controller.py +128 -0
- core/io/input/hook_registrar.py +286 -0
- core/io/input/input_loop_manager.py +421 -0
- core/io/input/key_press_handler.py +502 -0
- core/io/input/modal_controller.py +1011 -0
- core/io/input/paste_processor.py +339 -0
- core/io/input/status_modal_renderer.py +184 -0
- core/io/input_errors.py +5 -1
- core/io/input_handler.py +211 -2452
- core/io/key_parser.py +7 -0
- core/io/layout.py +15 -3
- core/io/message_coordinator.py +111 -2
- core/io/message_renderer.py +129 -4
- core/io/status_renderer.py +147 -607
- core/io/terminal_renderer.py +97 -51
- core/io/terminal_state.py +21 -4
- core/io/visual_effects.py +816 -165
- core/llm/agent_manager.py +1063 -0
- core/llm/api_adapters/__init__.py +44 -0
- core/llm/api_adapters/anthropic_adapter.py +432 -0
- core/llm/api_adapters/base.py +241 -0
- core/llm/api_adapters/openai_adapter.py +326 -0
- core/llm/api_communication_service.py +167 -113
- core/llm/conversation_logger.py +322 -16
- core/llm/conversation_manager.py +556 -30
- core/llm/file_operations_executor.py +84 -32
- core/llm/llm_service.py +934 -103
- core/llm/mcp_integration.py +541 -57
- core/llm/message_display_service.py +135 -18
- core/llm/plugin_sdk.py +1 -2
- core/llm/profile_manager.py +1183 -0
- core/llm/response_parser.py +274 -56
- core/llm/response_processor.py +16 -3
- core/llm/tool_executor.py +6 -1
- core/logging/__init__.py +2 -0
- core/logging/setup.py +34 -6
- core/models/resume.py +54 -0
- core/plugins/__init__.py +4 -2
- core/plugins/base.py +127 -0
- core/plugins/collector.py +23 -161
- core/plugins/discovery.py +37 -3
- core/plugins/factory.py +6 -12
- core/plugins/registry.py +5 -17
- core/ui/config_widgets.py +128 -28
- core/ui/live_modal_renderer.py +2 -1
- core/ui/modal_actions.py +5 -0
- core/ui/modal_overlay_renderer.py +0 -60
- core/ui/modal_renderer.py +268 -7
- core/ui/modal_state_manager.py +29 -4
- core/ui/widgets/base_widget.py +7 -0
- core/updates/__init__.py +10 -0
- core/updates/version_check_service.py +348 -0
- core/updates/version_comparator.py +103 -0
- core/utils/config_utils.py +685 -526
- core/utils/plugin_utils.py +1 -1
- core/utils/session_naming.py +111 -0
- fonts/LICENSE +21 -0
- fonts/README.md +46 -0
- fonts/SymbolsNerdFont-Regular.ttf +0 -0
- fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
- fonts/__init__.py +44 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
- kollabor-0.4.15.dist-info/RECORD +228 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
- plugins/agent_orchestrator/__init__.py +39 -0
- plugins/agent_orchestrator/activity_monitor.py +181 -0
- plugins/agent_orchestrator/file_attacher.py +77 -0
- plugins/agent_orchestrator/message_injector.py +135 -0
- plugins/agent_orchestrator/models.py +48 -0
- plugins/agent_orchestrator/orchestrator.py +403 -0
- plugins/agent_orchestrator/plugin.py +976 -0
- plugins/agent_orchestrator/xml_parser.py +191 -0
- plugins/agent_orchestrator_plugin.py +9 -0
- plugins/enhanced_input/box_styles.py +1 -0
- plugins/enhanced_input/color_engine.py +19 -4
- plugins/enhanced_input/config.py +2 -2
- plugins/enhanced_input_plugin.py +61 -11
- plugins/fullscreen/__init__.py +6 -2
- plugins/fullscreen/example_plugin.py +1035 -222
- plugins/fullscreen/setup_wizard_plugin.py +592 -0
- plugins/fullscreen/space_shooter_plugin.py +131 -0
- plugins/hook_monitoring_plugin.py +436 -78
- plugins/query_enhancer_plugin.py +66 -30
- plugins/resume_conversation_plugin.py +1494 -0
- plugins/save_conversation_plugin.py +98 -32
- plugins/system_commands_plugin.py +70 -56
- plugins/tmux_plugin.py +154 -78
- plugins/workflow_enforcement_plugin.py +94 -92
- system_prompt/default.md +952 -886
- core/io/input_mode_manager.py +0 -402
- core/io/modal_interaction_handler.py +0 -315
- core/io/raw_input_processor.py +0 -946
- core/storage/__init__.py +0 -5
- core/storage/state_manager.py +0 -84
- core/ui/widget_integration.py +0 -222
- core/utils/key_reader.py +0 -171
- kollabor-0.4.9.dist-info/RECORD +0 -128
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"""Key press handler component for Kollabor CLI.
|
|
2
|
+
|
|
3
|
+
Responsible for processing keyboard input and dispatching to appropriate handlers.
|
|
4
|
+
Handles character processing, key press dispatch, Enter/Escape keys, and hook integration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import time
|
|
10
|
+
from typing import Dict, Any, Optional, Callable, Awaitable
|
|
11
|
+
|
|
12
|
+
from ...events import EventType
|
|
13
|
+
from ...events.models import CommandMode
|
|
14
|
+
from ..key_parser import KeyParser, KeyPress, KeyType as KeyTypeEnum
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class KeyPressHandler:
|
|
20
|
+
"""Handles key press processing and dispatch.
|
|
21
|
+
|
|
22
|
+
This component manages:
|
|
23
|
+
- Character processing with paste detection hooks
|
|
24
|
+
- Key press event emission and plugin integration
|
|
25
|
+
- Key dispatch to specific handlers (Enter, Escape, arrows, etc.)
|
|
26
|
+
- Command mode detection and routing
|
|
27
|
+
- Status view navigation (Alt+Left/Right arrows)
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
buffer_manager: Buffer manager for text manipulation.
|
|
31
|
+
key_parser: Parser for converting raw input to KeyPress objects.
|
|
32
|
+
event_bus: Event bus for emitting key press events.
|
|
33
|
+
error_handler: Error handler for key processing errors.
|
|
34
|
+
display_controller: Controller for display updates.
|
|
35
|
+
renderer: Terminal renderer for display clearing.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
buffer_manager: Any,
|
|
41
|
+
key_parser: KeyParser,
|
|
42
|
+
event_bus: Any,
|
|
43
|
+
error_handler: Any,
|
|
44
|
+
display_controller: Any,
|
|
45
|
+
paste_processor: Any,
|
|
46
|
+
renderer: Any,
|
|
47
|
+
command_mode_handler: Optional[Any] = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Initialize the key press handler.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
buffer_manager: Buffer manager instance.
|
|
53
|
+
key_parser: Key parser instance.
|
|
54
|
+
event_bus: Event bus for emitting events.
|
|
55
|
+
error_handler: Error handler for key processing errors.
|
|
56
|
+
display_controller: Display controller for UI updates.
|
|
57
|
+
paste_processor: Paste processor for paste detection.
|
|
58
|
+
renderer: Terminal renderer for display clearing.
|
|
59
|
+
command_mode_handler: Optional handler for command mode operations.
|
|
60
|
+
"""
|
|
61
|
+
self.buffer_manager = buffer_manager
|
|
62
|
+
self.key_parser = key_parser
|
|
63
|
+
self.event_bus = event_bus
|
|
64
|
+
self.error_handler = error_handler
|
|
65
|
+
self.display_controller = display_controller
|
|
66
|
+
self.paste_processor = paste_processor
|
|
67
|
+
self.renderer = renderer
|
|
68
|
+
self.command_mode_handler = command_mode_handler
|
|
69
|
+
|
|
70
|
+
# Callbacks for methods we don't own (set by parent)
|
|
71
|
+
self._enter_command_mode_callback: Optional[Callable[[], Awaitable[None]]] = None
|
|
72
|
+
self._handle_command_mode_keypress_callback: Optional[
|
|
73
|
+
Callable[[KeyPress], Awaitable[bool]]
|
|
74
|
+
] = None
|
|
75
|
+
self._handle_status_view_previous_callback: Optional[
|
|
76
|
+
Callable[[], Awaitable[None]]
|
|
77
|
+
] = None
|
|
78
|
+
self._handle_status_view_next_callback: Optional[
|
|
79
|
+
Callable[[], Awaitable[None]]
|
|
80
|
+
] = None
|
|
81
|
+
self._expand_paste_placeholders_callback: Optional[
|
|
82
|
+
Callable[[str], str]
|
|
83
|
+
] = None
|
|
84
|
+
|
|
85
|
+
# State tracking
|
|
86
|
+
self._command_mode = CommandMode.NORMAL
|
|
87
|
+
|
|
88
|
+
logger.debug("KeyPressHandler initialized")
|
|
89
|
+
|
|
90
|
+
def set_callbacks(
|
|
91
|
+
self,
|
|
92
|
+
enter_command_mode: Optional[Callable[[], Awaitable[None]]] = None,
|
|
93
|
+
handle_command_mode_keypress: Optional[
|
|
94
|
+
Callable[[KeyPress], Awaitable[bool]]
|
|
95
|
+
] = None,
|
|
96
|
+
handle_status_view_previous: Optional[Callable[[], Awaitable[None]]] = None,
|
|
97
|
+
handle_status_view_next: Optional[Callable[[], Awaitable[None]]] = None,
|
|
98
|
+
expand_paste_placeholders: Optional[Callable[[str], str]] = None,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Set callbacks for methods owned by parent InputHandler.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
enter_command_mode: Callback to enter command mode.
|
|
104
|
+
handle_command_mode_keypress: Callback to handle command mode keys.
|
|
105
|
+
handle_status_view_previous: Callback to switch to previous status view.
|
|
106
|
+
handle_status_view_next: Callback to switch to next status view.
|
|
107
|
+
expand_paste_placeholders: Callback to expand paste placeholders.
|
|
108
|
+
"""
|
|
109
|
+
self._enter_command_mode_callback = enter_command_mode
|
|
110
|
+
self._handle_command_mode_keypress_callback = handle_command_mode_keypress
|
|
111
|
+
self._handle_status_view_previous_callback = handle_status_view_previous
|
|
112
|
+
self._handle_status_view_next_callback = handle_status_view_next
|
|
113
|
+
self._expand_paste_placeholders_callback = expand_paste_placeholders
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def command_mode(self) -> CommandMode:
|
|
117
|
+
"""Get current command mode.
|
|
118
|
+
|
|
119
|
+
Delegates to command_mode_handler if available for consistent state.
|
|
120
|
+
"""
|
|
121
|
+
if self.command_mode_handler:
|
|
122
|
+
return self.command_mode_handler.command_mode
|
|
123
|
+
return self._command_mode
|
|
124
|
+
|
|
125
|
+
@command_mode.setter
|
|
126
|
+
def command_mode(self, value: CommandMode) -> None:
|
|
127
|
+
"""Set current command mode."""
|
|
128
|
+
if self.command_mode_handler:
|
|
129
|
+
self.command_mode_handler.command_mode = value
|
|
130
|
+
self._command_mode = value
|
|
131
|
+
|
|
132
|
+
async def process_character(self, char: str) -> None:
|
|
133
|
+
"""Process a single character input.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
char: Character received from terminal.
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
current_time = time.time()
|
|
140
|
+
|
|
141
|
+
# Check for slash command initiation
|
|
142
|
+
# (before parsing for immediate response)
|
|
143
|
+
if (
|
|
144
|
+
char == "/"
|
|
145
|
+
and self.buffer_manager.is_empty
|
|
146
|
+
and self.command_mode == CommandMode.NORMAL
|
|
147
|
+
):
|
|
148
|
+
if self._enter_command_mode_callback:
|
|
149
|
+
await self._enter_command_mode_callback()
|
|
150
|
+
else:
|
|
151
|
+
logger.warning(
|
|
152
|
+
"Slash command detected but no enter_command_mode callback set"
|
|
153
|
+
)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
# SECONDARY PASTE DETECTION:
|
|
157
|
+
# Character-by-character timing (DISABLED)
|
|
158
|
+
# This is a fallback system - primary chunk detection
|
|
159
|
+
# above handles most cases
|
|
160
|
+
if self.paste_processor.paste_detection_enabled:
|
|
161
|
+
# Currently False - secondary system disabled
|
|
162
|
+
paste_handled = await self.paste_processor.simple_paste_detection(
|
|
163
|
+
char, current_time
|
|
164
|
+
)
|
|
165
|
+
if paste_handled:
|
|
166
|
+
# Character consumed by paste detection,
|
|
167
|
+
# skip normal processing
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
# Parse character into structured key press
|
|
171
|
+
# (this handles escape sequences)
|
|
172
|
+
key_press = self.key_parser.parse_char(char)
|
|
173
|
+
if not key_press:
|
|
174
|
+
# For command modes, add timeout-based
|
|
175
|
+
# standalone escape detection
|
|
176
|
+
if self.command_mode in (CommandMode.MODAL, CommandMode.STATUS_MODAL, CommandMode.LIVE_MODAL, CommandMode.MENU_POPUP):
|
|
177
|
+
# Schedule delayed check for standalone escape
|
|
178
|
+
# (100ms delay)
|
|
179
|
+
async def delayed_escape_check():
|
|
180
|
+
await asyncio.sleep(0.1)
|
|
181
|
+
standalone_escape = (
|
|
182
|
+
self.key_parser.check_for_standalone_escape()
|
|
183
|
+
)
|
|
184
|
+
if standalone_escape:
|
|
185
|
+
if self._handle_command_mode_keypress_callback:
|
|
186
|
+
await self._handle_command_mode_keypress_callback(
|
|
187
|
+
standalone_escape
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
asyncio.create_task(delayed_escape_check())
|
|
191
|
+
# Incomplete escape sequence - wait for more characters
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
# Check for slash command mode handling AFTER parsing
|
|
195
|
+
# (so arrow keys work)
|
|
196
|
+
if self.command_mode != CommandMode.NORMAL:
|
|
197
|
+
logger.info(
|
|
198
|
+
f"Processing key '{key_press.name}' "
|
|
199
|
+
f"in command mode: {self.command_mode}"
|
|
200
|
+
)
|
|
201
|
+
if self._handle_command_mode_keypress_callback:
|
|
202
|
+
handled = await self._handle_command_mode_keypress_callback(
|
|
203
|
+
key_press
|
|
204
|
+
)
|
|
205
|
+
if handled:
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
# Emit key press event for plugins
|
|
209
|
+
key_result = await self.event_bus.emit_with_hooks(
|
|
210
|
+
EventType.KEY_PRESS,
|
|
211
|
+
{
|
|
212
|
+
"key": key_press.name,
|
|
213
|
+
"char_code": key_press.code,
|
|
214
|
+
"key_type": key_press.type.value,
|
|
215
|
+
"modifiers": key_press.modifiers,
|
|
216
|
+
},
|
|
217
|
+
"input",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Check if any plugin handled this key
|
|
221
|
+
prevent_default = self._check_prevent_default(key_result)
|
|
222
|
+
|
|
223
|
+
# Process key if not prevented by plugins
|
|
224
|
+
if not prevent_default:
|
|
225
|
+
await self._handle_key_press(key_press)
|
|
226
|
+
|
|
227
|
+
# Update renderer
|
|
228
|
+
await self.display_controller.update_display()
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
from ..input_errors import ErrorType, ErrorSeverity
|
|
232
|
+
|
|
233
|
+
await self.error_handler.handle_error(
|
|
234
|
+
ErrorType.PARSING_ERROR,
|
|
235
|
+
f"Error processing character: {e}",
|
|
236
|
+
ErrorSeverity.MEDIUM,
|
|
237
|
+
{"char": repr(char), "buffer_manager": self.buffer_manager},
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def _check_prevent_default(self, key_result: Dict[str, Any]) -> bool:
|
|
241
|
+
"""Check if plugins want to prevent default key handling.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
key_result: Result from key press event.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
True if default handling should be prevented.
|
|
248
|
+
"""
|
|
249
|
+
if "main" in key_result:
|
|
250
|
+
for hook_result in key_result["main"].values():
|
|
251
|
+
if isinstance(hook_result, dict) and hook_result.get(
|
|
252
|
+
"prevent_default"
|
|
253
|
+
):
|
|
254
|
+
return True
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
async def _handle_key_press(self, key_press: KeyPress) -> None:
|
|
258
|
+
"""Handle a parsed key press.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
key_press: Parsed key press event.
|
|
262
|
+
"""
|
|
263
|
+
# Process key press
|
|
264
|
+
try:
|
|
265
|
+
# Log all key presses for debugging
|
|
266
|
+
logger.info(
|
|
267
|
+
f"Key press: name='{key_press.name}', "
|
|
268
|
+
f"char='{key_press.char}', code={key_press.code}, "
|
|
269
|
+
f"type={key_press.type}, "
|
|
270
|
+
f"modifiers={getattr(key_press, 'modifiers', None)}"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# CRITICAL FIX: Modal input isolation
|
|
274
|
+
# capture ALL input when in modal mode
|
|
275
|
+
if self.command_mode == CommandMode.MODAL:
|
|
276
|
+
logger.info(
|
|
277
|
+
f"Modal mode active - routing ALL input "
|
|
278
|
+
f"to modal handler: {key_press.name}"
|
|
279
|
+
)
|
|
280
|
+
if self._handle_command_mode_keypress_callback:
|
|
281
|
+
await self._handle_command_mode_keypress_callback(key_press)
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
# Handle control keys
|
|
285
|
+
if self.key_parser.is_control_key(key_press, "Ctrl+C"):
|
|
286
|
+
logger.info("Ctrl+C received")
|
|
287
|
+
raise KeyboardInterrupt
|
|
288
|
+
|
|
289
|
+
elif self.key_parser.is_control_key(key_press, "Enter"):
|
|
290
|
+
await self._handle_enter()
|
|
291
|
+
|
|
292
|
+
elif self.key_parser.is_control_key(key_press, "Backspace"):
|
|
293
|
+
self.buffer_manager.delete_char()
|
|
294
|
+
|
|
295
|
+
elif key_press.name == "Escape":
|
|
296
|
+
await self._handle_escape()
|
|
297
|
+
|
|
298
|
+
elif key_press.name == "Delete":
|
|
299
|
+
self.buffer_manager.delete_forward()
|
|
300
|
+
|
|
301
|
+
# Handle arrow keys for cursor movement and history
|
|
302
|
+
elif key_press.name == "ArrowLeft":
|
|
303
|
+
moved = self.buffer_manager.move_cursor("left")
|
|
304
|
+
if moved:
|
|
305
|
+
logger.debug(
|
|
306
|
+
f"Arrow Left: cursor moved to position {self.buffer_manager.cursor_position}"
|
|
307
|
+
)
|
|
308
|
+
await self.display_controller.update_display(force_render=True)
|
|
309
|
+
|
|
310
|
+
elif key_press.name == "ArrowRight":
|
|
311
|
+
moved = self.buffer_manager.move_cursor("right")
|
|
312
|
+
if moved:
|
|
313
|
+
logger.debug(
|
|
314
|
+
f"Arrow Right: cursor moved to position {self.buffer_manager.cursor_position}"
|
|
315
|
+
)
|
|
316
|
+
await self.display_controller.update_display(force_render=True)
|
|
317
|
+
|
|
318
|
+
elif key_press.name == "ArrowUp":
|
|
319
|
+
self.buffer_manager.navigate_history("up")
|
|
320
|
+
await self.display_controller.update_display(force_render=True)
|
|
321
|
+
|
|
322
|
+
elif key_press.name == "ArrowDown":
|
|
323
|
+
self.buffer_manager.navigate_history("down")
|
|
324
|
+
await self.display_controller.update_display(force_render=True)
|
|
325
|
+
|
|
326
|
+
# Handle Home/End keys
|
|
327
|
+
elif key_press.name == "Home":
|
|
328
|
+
self.buffer_manager.move_to_start()
|
|
329
|
+
await self.display_controller.update_display(force_render=True)
|
|
330
|
+
|
|
331
|
+
elif key_press.name == "End":
|
|
332
|
+
self.buffer_manager.move_to_end()
|
|
333
|
+
await self.display_controller.update_display(force_render=True)
|
|
334
|
+
|
|
335
|
+
# Handle Option/Alt+Arrow keys for status view navigation
|
|
336
|
+
# Support both Alt+Arrow and Alt+b/f (macOS terminals often send the latter)
|
|
337
|
+
elif key_press.name in ("Alt+ArrowLeft", "Alt+b"):
|
|
338
|
+
logger.info(
|
|
339
|
+
f"{key_press.name} detected - switching to previous status view"
|
|
340
|
+
)
|
|
341
|
+
if self._handle_status_view_previous_callback:
|
|
342
|
+
await self._handle_status_view_previous_callback()
|
|
343
|
+
|
|
344
|
+
elif key_press.name in ("Alt+ArrowRight", "Alt+f"):
|
|
345
|
+
logger.info(
|
|
346
|
+
f"{key_press.name} detected - switching to next status view"
|
|
347
|
+
)
|
|
348
|
+
if self._handle_status_view_next_callback:
|
|
349
|
+
await self._handle_status_view_next_callback()
|
|
350
|
+
|
|
351
|
+
# Handle Cmd key combinations (mapped to Ctrl sequences on macOS)
|
|
352
|
+
elif self.key_parser.is_control_key(key_press, "Ctrl+A"):
|
|
353
|
+
logger.info("Ctrl+A (Cmd+Left) - moving cursor to start")
|
|
354
|
+
self.buffer_manager.move_to_start()
|
|
355
|
+
await self.display_controller.update_display(force_render=True)
|
|
356
|
+
|
|
357
|
+
elif self.key_parser.is_control_key(key_press, "Ctrl+E"):
|
|
358
|
+
logger.info("Ctrl+E (Cmd+Right) - moving cursor to end")
|
|
359
|
+
self.buffer_manager.move_to_end()
|
|
360
|
+
await self.display_controller.update_display(force_render=True)
|
|
361
|
+
|
|
362
|
+
elif self.key_parser.is_control_key(key_press, "Ctrl+U"):
|
|
363
|
+
logger.info("Ctrl+U (Cmd+Backspace) - clearing line")
|
|
364
|
+
self.buffer_manager.clear()
|
|
365
|
+
await self.display_controller.update_display(force_render=True)
|
|
366
|
+
|
|
367
|
+
# Handle printable characters
|
|
368
|
+
elif self.key_parser.is_printable_char(key_press):
|
|
369
|
+
# Normal character processing
|
|
370
|
+
success = self.buffer_manager.insert_char(key_press.char)
|
|
371
|
+
if not success:
|
|
372
|
+
from ..input_errors import ErrorType, ErrorSeverity
|
|
373
|
+
|
|
374
|
+
await self.error_handler.handle_error(
|
|
375
|
+
ErrorType.BUFFER_ERROR,
|
|
376
|
+
"Failed to insert character - buffer limit reached",
|
|
377
|
+
ErrorSeverity.LOW,
|
|
378
|
+
{
|
|
379
|
+
"char": key_press.char,
|
|
380
|
+
"buffer_manager": self.buffer_manager,
|
|
381
|
+
},
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Handle other special keys (F1-F12, etc.)
|
|
385
|
+
elif key_press.type == KeyTypeEnum.EXTENDED:
|
|
386
|
+
logger.debug(f"Extended key pressed: {key_press.name}")
|
|
387
|
+
# Could emit special events for function keys, etc.
|
|
388
|
+
|
|
389
|
+
except Exception as e:
|
|
390
|
+
from ..input_errors import ErrorType, ErrorSeverity
|
|
391
|
+
|
|
392
|
+
await self.error_handler.handle_error(
|
|
393
|
+
ErrorType.EVENT_ERROR,
|
|
394
|
+
f"Error handling key press: {e}",
|
|
395
|
+
ErrorSeverity.MEDIUM,
|
|
396
|
+
{
|
|
397
|
+
"key_press": key_press,
|
|
398
|
+
"buffer_manager": self.buffer_manager,
|
|
399
|
+
},
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
async def _handle_enter(self) -> None:
|
|
403
|
+
"""Handle Enter key press with enhanced validation."""
|
|
404
|
+
try:
|
|
405
|
+
if self.buffer_manager.is_empty:
|
|
406
|
+
return
|
|
407
|
+
|
|
408
|
+
# Validate input before processing
|
|
409
|
+
validation_errors = self.buffer_manager.validate_content()
|
|
410
|
+
if validation_errors:
|
|
411
|
+
for error in validation_errors:
|
|
412
|
+
logger.warning(f"Input validation warning: {error}")
|
|
413
|
+
|
|
414
|
+
# Get message and clear buffer
|
|
415
|
+
message = self.buffer_manager.get_content_and_clear()
|
|
416
|
+
|
|
417
|
+
# Check if this is a slash command - handle immediately without paste expansion
|
|
418
|
+
if message.strip().startswith("/"):
|
|
419
|
+
logger.info(
|
|
420
|
+
f"Detected slash command, bypassing paste expansion: '{message}'"
|
|
421
|
+
)
|
|
422
|
+
expanded_message = message
|
|
423
|
+
else:
|
|
424
|
+
# GENIUS PASTE BUCKET: Immediate expansion - no waiting needed!
|
|
425
|
+
logger.debug(f"GENIUS SUBMIT: Original message: '{message}'")
|
|
426
|
+
logger.debug(
|
|
427
|
+
f"GENIUS SUBMIT: Paste bucket contains: {list(self.paste_processor.paste_bucket.keys())}"
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if self._expand_paste_placeholders_callback:
|
|
431
|
+
expanded_message = self._expand_paste_placeholders_callback(
|
|
432
|
+
message
|
|
433
|
+
)
|
|
434
|
+
else:
|
|
435
|
+
# Fallback to direct expansion if callback not set
|
|
436
|
+
expanded_message = self.paste_processor.expand_paste_placeholders(
|
|
437
|
+
message
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
logger.debug(
|
|
441
|
+
f"GENIUS SUBMIT: Final expanded: '{expanded_message[:100]}...' ({len(expanded_message)} chars)"
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Add to history (with expanded content)
|
|
445
|
+
self.buffer_manager.add_to_history(expanded_message)
|
|
446
|
+
|
|
447
|
+
# CRITICAL: Clear the input display before emitting event
|
|
448
|
+
# This matches the original InputHandler._handle_enter behavior
|
|
449
|
+
self.renderer.input_buffer = ""
|
|
450
|
+
self.renderer.clear_active_area()
|
|
451
|
+
|
|
452
|
+
# Emit user input event (with expanded content!)
|
|
453
|
+
await self.event_bus.emit_with_hooks(
|
|
454
|
+
EventType.USER_INPUT,
|
|
455
|
+
{
|
|
456
|
+
"message": expanded_message,
|
|
457
|
+
"validation_errors": validation_errors,
|
|
458
|
+
},
|
|
459
|
+
"user",
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
logger.debug(
|
|
463
|
+
f"Processed user input: {message[:100]}..."
|
|
464
|
+
if len(message) > 100
|
|
465
|
+
else f"Processed user input: {message}"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
except Exception as e:
|
|
469
|
+
from ..input_errors import ErrorType, ErrorSeverity
|
|
470
|
+
|
|
471
|
+
await self.error_handler.handle_error(
|
|
472
|
+
ErrorType.EVENT_ERROR,
|
|
473
|
+
f"Error handling Enter key: {e}",
|
|
474
|
+
ErrorSeverity.HIGH,
|
|
475
|
+
{"buffer_manager": self.buffer_manager},
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
async def _handle_escape(self) -> None:
|
|
479
|
+
"""Handle Escape key press for request cancellation."""
|
|
480
|
+
try:
|
|
481
|
+
logger.info("_handle_escape called - emitting CANCEL_REQUEST event")
|
|
482
|
+
|
|
483
|
+
# Emit cancellation event
|
|
484
|
+
result = await self.event_bus.emit_with_hooks(
|
|
485
|
+
EventType.CANCEL_REQUEST,
|
|
486
|
+
{"reason": "user_escape", "source": "input_handler"},
|
|
487
|
+
"input",
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
logger.info(
|
|
491
|
+
f"ESC key pressed - cancellation request sent, result: {result}"
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
except Exception as e:
|
|
495
|
+
from ..input_errors import ErrorType, ErrorSeverity
|
|
496
|
+
|
|
497
|
+
await self.error_handler.handle_error(
|
|
498
|
+
ErrorType.EVENT_ERROR,
|
|
499
|
+
f"Error handling Escape key: {e}",
|
|
500
|
+
ErrorSeverity.MEDIUM,
|
|
501
|
+
{"buffer_manager": self.buffer_manager},
|
|
502
|
+
)
|