agentcrew-ai 0.8.13__py3-none-any.whl → 0.9.1__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.
- AgentCrew/__init__.py +1 -1
- AgentCrew/app.py +46 -634
- AgentCrew/main_docker.py +1 -30
- AgentCrew/modules/a2a/common/client/card_resolver.py +27 -8
- AgentCrew/modules/a2a/server.py +5 -0
- AgentCrew/modules/a2a/task_manager.py +1 -0
- AgentCrew/modules/agents/local_agent.py +2 -2
- AgentCrew/modules/chat/message/command_processor.py +33 -8
- AgentCrew/modules/chat/message/conversation.py +18 -1
- AgentCrew/modules/chat/message/handler.py +5 -1
- AgentCrew/modules/code_analysis/service.py +50 -7
- AgentCrew/modules/code_analysis/tool.py +9 -8
- AgentCrew/modules/console/completers.py +5 -1
- AgentCrew/modules/console/console_ui.py +23 -11
- AgentCrew/modules/console/conversation_browser/__init__.py +9 -0
- AgentCrew/modules/console/conversation_browser/browser.py +84 -0
- AgentCrew/modules/console/conversation_browser/browser_input_handler.py +279 -0
- AgentCrew/modules/console/{conversation_browser.py → conversation_browser/browser_ui.py} +249 -163
- AgentCrew/modules/console/conversation_handler.py +34 -1
- AgentCrew/modules/console/display_handlers.py +127 -7
- AgentCrew/modules/console/visual_mode/__init__.py +5 -0
- AgentCrew/modules/console/visual_mode/viewer.py +41 -0
- AgentCrew/modules/console/visual_mode/viewer_input_handler.py +315 -0
- AgentCrew/modules/console/visual_mode/viewer_ui.py +608 -0
- AgentCrew/modules/gui/components/command_handler.py +137 -29
- AgentCrew/modules/gui/components/menu_components.py +8 -7
- AgentCrew/modules/gui/themes/README.md +30 -14
- AgentCrew/modules/gui/themes/__init__.py +2 -1
- AgentCrew/modules/gui/themes/atom_light.yaml +1287 -0
- AgentCrew/modules/gui/themes/catppuccin.yaml +1276 -0
- AgentCrew/modules/gui/themes/dracula.yaml +1262 -0
- AgentCrew/modules/gui/themes/nord.yaml +1267 -0
- AgentCrew/modules/gui/themes/saigontech.yaml +1268 -0
- AgentCrew/modules/gui/themes/style_provider.py +78 -264
- AgentCrew/modules/gui/themes/theme_loader.py +379 -0
- AgentCrew/modules/gui/themes/unicorn.yaml +1276 -0
- AgentCrew/modules/gui/widgets/configs/global_settings.py +4 -4
- AgentCrew/modules/gui/widgets/history_sidebar.py +6 -1
- AgentCrew/modules/llm/constants.py +28 -9
- AgentCrew/modules/mcpclient/service.py +0 -1
- AgentCrew/modules/memory/base_service.py +13 -0
- AgentCrew/modules/memory/chroma_service.py +50 -0
- AgentCrew/setup.py +470 -0
- {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/METADATA +1 -1
- {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/RECORD +49 -40
- {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/WHEEL +1 -1
- AgentCrew/modules/gui/themes/atom_light.py +0 -1365
- AgentCrew/modules/gui/themes/catppuccin.py +0 -1404
- AgentCrew/modules/gui/themes/dracula.py +0 -1372
- AgentCrew/modules/gui/themes/nord.py +0 -1365
- AgentCrew/modules/gui/themes/saigontech.py +0 -1359
- AgentCrew/modules/gui/themes/unicorn.py +0 -1372
- {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/entry_points.txt +0 -0
- {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/licenses/LICENSE +0 -0
- {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/top_level.txt +0 -0
|
@@ -122,12 +122,125 @@ class DisplayHandlers:
|
|
|
122
122
|
)
|
|
123
123
|
|
|
124
124
|
def display_debug_info(self, debug_info):
|
|
125
|
-
"""Display debug information.
|
|
126
|
-
|
|
125
|
+
"""Display debug information with formatting and truncation.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
debug_info: Either a dict with 'type' and 'messages' keys (new format)
|
|
129
|
+
or a raw list of messages (legacy format)
|
|
130
|
+
"""
|
|
131
|
+
if (
|
|
132
|
+
isinstance(debug_info, dict)
|
|
133
|
+
and "type" in debug_info
|
|
134
|
+
and "messages" in debug_info
|
|
135
|
+
):
|
|
136
|
+
# New format with type and messages
|
|
137
|
+
msg_type = debug_info["type"]
|
|
138
|
+
messages = debug_info["messages"]
|
|
139
|
+
title = "Agent Messages" if msg_type == "agent" else "Chat Messages"
|
|
140
|
+
else:
|
|
141
|
+
# Legacy format - just raw messages
|
|
142
|
+
title = "Messages"
|
|
143
|
+
messages = debug_info
|
|
144
|
+
|
|
145
|
+
self.console.print(
|
|
146
|
+
Text(f"\n{title} ({len(messages)} messages):", style=RICH_STYLE_YELLOW)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
formatted = self._format_messages_for_debug(messages)
|
|
127
150
|
try:
|
|
128
|
-
self.console.print(json.dumps(
|
|
151
|
+
self.console.print(json.dumps(formatted, indent=2))
|
|
129
152
|
except Exception:
|
|
130
|
-
self.console.print(
|
|
153
|
+
self.console.print(str(formatted))
|
|
154
|
+
|
|
155
|
+
def _format_messages_for_debug(
|
|
156
|
+
self, messages, max_content_length: int = 200
|
|
157
|
+
) -> list:
|
|
158
|
+
"""Format messages for debug display with truncated content.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
messages: List of message dictionaries
|
|
162
|
+
max_content_length: Maximum length for message content
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List of formatted message dictionaries
|
|
166
|
+
"""
|
|
167
|
+
formatted = []
|
|
168
|
+
|
|
169
|
+
for i, msg in enumerate(messages):
|
|
170
|
+
formatted_msg = {"#": i, "content": ""}
|
|
171
|
+
|
|
172
|
+
# Copy basic fields
|
|
173
|
+
if "role" in msg:
|
|
174
|
+
formatted_msg["role"] = msg["role"]
|
|
175
|
+
if "agent" in msg:
|
|
176
|
+
formatted_msg["agent"] = msg["agent"]
|
|
177
|
+
|
|
178
|
+
# Truncate content
|
|
179
|
+
content = msg.get("content", "")
|
|
180
|
+
formatted_msg["content"] = self._truncate_content(
|
|
181
|
+
content, max_content_length
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Include tool_use/tool_result indicators if present
|
|
185
|
+
if isinstance(content, list):
|
|
186
|
+
content_types = []
|
|
187
|
+
for item in content:
|
|
188
|
+
if isinstance(item, dict):
|
|
189
|
+
item_type = item.get("type", "unknown")
|
|
190
|
+
if item_type == "tool_use":
|
|
191
|
+
tool_name = item.get("name", "unknown")
|
|
192
|
+
content_types.append(f"tool_use:{tool_name}")
|
|
193
|
+
elif item_type == "tool_result":
|
|
194
|
+
content_types.append("tool_result")
|
|
195
|
+
elif item_type not in ("text",):
|
|
196
|
+
content_types.append(item_type)
|
|
197
|
+
if content_types:
|
|
198
|
+
formatted_msg["content_types"] = content_types
|
|
199
|
+
|
|
200
|
+
formatted.append(formatted_msg)
|
|
201
|
+
|
|
202
|
+
return formatted
|
|
203
|
+
|
|
204
|
+
def _truncate_content(self, content, max_length: int = 200) -> str:
|
|
205
|
+
"""Truncate content to max_length with ellipsis.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
content: Message content (can be string, list, or dict)
|
|
209
|
+
max_length: Maximum length for the output
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Truncated string representation
|
|
213
|
+
"""
|
|
214
|
+
if isinstance(content, str):
|
|
215
|
+
text = content
|
|
216
|
+
elif isinstance(content, list):
|
|
217
|
+
# Extract text from content blocks
|
|
218
|
+
text_parts = []
|
|
219
|
+
for item in content:
|
|
220
|
+
if isinstance(item, dict):
|
|
221
|
+
if item.get("type") == "text":
|
|
222
|
+
text_parts.append(item.get("text", ""))
|
|
223
|
+
elif item.get("type") == "tool_use":
|
|
224
|
+
text_parts.append(f"[tool:{item.get('name', 'unknown')}]")
|
|
225
|
+
elif item.get("type") == "tool_result":
|
|
226
|
+
result = item.get("content", "")
|
|
227
|
+
if isinstance(result, str):
|
|
228
|
+
text_parts.append(f"[result:{result[:50]}...]")
|
|
229
|
+
else:
|
|
230
|
+
text_parts.append("[result:...]")
|
|
231
|
+
elif isinstance(item, str):
|
|
232
|
+
text_parts.append(item)
|
|
233
|
+
text = " | ".join(text_parts)
|
|
234
|
+
else:
|
|
235
|
+
text = str(content)
|
|
236
|
+
|
|
237
|
+
# Clean up whitespace
|
|
238
|
+
text = " ".join(text.split())
|
|
239
|
+
|
|
240
|
+
if len(text) <= max_length:
|
|
241
|
+
return text
|
|
242
|
+
|
|
243
|
+
return text[: max_length - 3] + "..."
|
|
131
244
|
|
|
132
245
|
def display_models(self, models_by_provider: Dict):
|
|
133
246
|
"""Display available models grouped by provider."""
|
|
@@ -161,13 +274,15 @@ class DisplayHandlers:
|
|
|
161
274
|
self,
|
|
162
275
|
conversations: List[Dict[str, Any]],
|
|
163
276
|
get_history_callback=None,
|
|
277
|
+
delete_callback=None,
|
|
164
278
|
):
|
|
165
279
|
"""Display available conversations using interactive browser.
|
|
166
|
-
|
|
280
|
+
|
|
167
281
|
Args:
|
|
168
282
|
conversations: List of conversation metadata
|
|
169
283
|
get_history_callback: Optional callback to fetch full conversation history
|
|
170
|
-
|
|
284
|
+
delete_callback: Optional callback to delete conversations by IDs
|
|
285
|
+
|
|
171
286
|
Returns:
|
|
172
287
|
Selected conversation ID or None if cancelled
|
|
173
288
|
"""
|
|
@@ -177,11 +292,12 @@ class DisplayHandlers:
|
|
|
177
292
|
)
|
|
178
293
|
return None
|
|
179
294
|
|
|
180
|
-
from .conversation_browser import ConversationBrowser
|
|
295
|
+
from .conversation_browser.browser import ConversationBrowser
|
|
181
296
|
|
|
182
297
|
browser = ConversationBrowser(
|
|
183
298
|
console=self.console,
|
|
184
299
|
get_conversation_history=get_history_callback,
|
|
300
|
+
on_delete=delete_callback,
|
|
185
301
|
)
|
|
186
302
|
browser.set_conversations(conversations)
|
|
187
303
|
return browser.show()
|
|
@@ -378,6 +494,10 @@ class DisplayHandlers:
|
|
|
378
494
|
"Use '/unconsolidate' undo last consolidated.",
|
|
379
495
|
style=RICH_STYLE_YELLOW,
|
|
380
496
|
),
|
|
497
|
+
Text(
|
|
498
|
+
"Use '/visual' to view raw message content with vim-like navigation and copy.",
|
|
499
|
+
style=RICH_STYLE_YELLOW,
|
|
500
|
+
),
|
|
381
501
|
Text(
|
|
382
502
|
"Tool calls require confirmation before execution.",
|
|
383
503
|
style=RICH_STYLE_BLUE,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Visual mode viewer for raw message content."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, List, Dict, Any, Optional, Callable
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
|
|
10
|
+
from .viewer_ui import VisualModeUI
|
|
11
|
+
from .viewer_input_handler import VisualModeInputHandler
|
|
12
|
+
from ..constants import RICH_STYLE_YELLOW
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class VisualModeViewer:
|
|
19
|
+
"""Main class for visual mode viewing of raw message content."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
console: Console,
|
|
24
|
+
on_copy: Optional[Callable[[str], None]] = None,
|
|
25
|
+
):
|
|
26
|
+
self._console = console
|
|
27
|
+
self._on_copy = on_copy
|
|
28
|
+
self._ui = VisualModeUI(console)
|
|
29
|
+
self._input_handler = VisualModeInputHandler(self._ui, on_copy=on_copy)
|
|
30
|
+
|
|
31
|
+
def set_messages(self, messages: List[Dict[str, Any]]):
|
|
32
|
+
self._ui.set_messages(messages)
|
|
33
|
+
|
|
34
|
+
def show(self):
|
|
35
|
+
if not self._ui._messages:
|
|
36
|
+
self._console.print(
|
|
37
|
+
Text("No messages to display in visual mode.", style=RICH_STYLE_YELLOW)
|
|
38
|
+
)
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
self._input_handler.run()
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Input handler for visual mode viewer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Optional, Callable
|
|
6
|
+
|
|
7
|
+
from prompt_toolkit import PromptSession
|
|
8
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
9
|
+
from prompt_toolkit.keys import Keys
|
|
10
|
+
|
|
11
|
+
from loguru import logger
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .viewer_ui import VisualModeUI
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VisualModeInputHandler:
|
|
18
|
+
"""Handles keyboard input for the visual mode viewer."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
ui: VisualModeUI,
|
|
23
|
+
on_copy: Optional[Callable[[str], None]] = None,
|
|
24
|
+
):
|
|
25
|
+
self._ui = ui
|
|
26
|
+
self._running = False
|
|
27
|
+
self._g_pressed = False
|
|
28
|
+
self._on_copy = on_copy
|
|
29
|
+
|
|
30
|
+
def _create_key_bindings(self) -> KeyBindings:
|
|
31
|
+
kb = KeyBindings()
|
|
32
|
+
|
|
33
|
+
@kb.add(Keys.Up)
|
|
34
|
+
@kb.add("k")
|
|
35
|
+
def _(event):
|
|
36
|
+
if self._ui._search_mode:
|
|
37
|
+
return
|
|
38
|
+
self._g_pressed = False
|
|
39
|
+
if self._ui.move_cursor("up"):
|
|
40
|
+
self._ui.render()
|
|
41
|
+
|
|
42
|
+
@kb.add(Keys.Down)
|
|
43
|
+
@kb.add("j")
|
|
44
|
+
def _(event):
|
|
45
|
+
if self._ui._search_mode:
|
|
46
|
+
return
|
|
47
|
+
self._g_pressed = False
|
|
48
|
+
if self._ui.move_cursor("down"):
|
|
49
|
+
self._ui.render()
|
|
50
|
+
|
|
51
|
+
@kb.add(Keys.Left)
|
|
52
|
+
@kb.add("h")
|
|
53
|
+
def _(event):
|
|
54
|
+
if self._ui._search_mode:
|
|
55
|
+
return
|
|
56
|
+
self._g_pressed = False
|
|
57
|
+
if self._ui.move_cursor("left"):
|
|
58
|
+
self._ui.render()
|
|
59
|
+
|
|
60
|
+
@kb.add(Keys.Right)
|
|
61
|
+
@kb.add("l")
|
|
62
|
+
def _(event):
|
|
63
|
+
if self._ui._search_mode:
|
|
64
|
+
return
|
|
65
|
+
self._g_pressed = False
|
|
66
|
+
if self._ui.move_cursor("right"):
|
|
67
|
+
self._ui.render()
|
|
68
|
+
|
|
69
|
+
@kb.add("w")
|
|
70
|
+
def _(event):
|
|
71
|
+
if self._ui._search_mode:
|
|
72
|
+
self._ui.append_search_char("w")
|
|
73
|
+
self._ui.render()
|
|
74
|
+
return
|
|
75
|
+
self._g_pressed = False
|
|
76
|
+
if self._ui.move_cursor("word_forward"):
|
|
77
|
+
self._ui.render()
|
|
78
|
+
|
|
79
|
+
@kb.add("b")
|
|
80
|
+
def _(event):
|
|
81
|
+
if self._ui._search_mode:
|
|
82
|
+
self._ui.append_search_char("b")
|
|
83
|
+
self._ui.render()
|
|
84
|
+
return
|
|
85
|
+
self._g_pressed = False
|
|
86
|
+
if self._ui.move_cursor("word_backward"):
|
|
87
|
+
self._ui.render()
|
|
88
|
+
|
|
89
|
+
@kb.add("0")
|
|
90
|
+
def _(event):
|
|
91
|
+
if self._ui._search_mode:
|
|
92
|
+
self._ui.append_search_char("0")
|
|
93
|
+
self._ui.render()
|
|
94
|
+
return
|
|
95
|
+
self._g_pressed = False
|
|
96
|
+
if self._ui.move_cursor("line_start"):
|
|
97
|
+
self._ui.render()
|
|
98
|
+
|
|
99
|
+
@kb.add("$")
|
|
100
|
+
def _(event):
|
|
101
|
+
if self._ui._search_mode:
|
|
102
|
+
self._ui.append_search_char("$")
|
|
103
|
+
self._ui.render()
|
|
104
|
+
return
|
|
105
|
+
self._g_pressed = False
|
|
106
|
+
if self._ui.move_cursor("line_end"):
|
|
107
|
+
self._ui.render()
|
|
108
|
+
|
|
109
|
+
@kb.add("g")
|
|
110
|
+
def _(event):
|
|
111
|
+
if self._ui._search_mode:
|
|
112
|
+
self._ui.append_search_char("g")
|
|
113
|
+
self._ui.render()
|
|
114
|
+
return
|
|
115
|
+
if self._g_pressed:
|
|
116
|
+
self._g_pressed = False
|
|
117
|
+
if self._ui.move_cursor("top"):
|
|
118
|
+
self._ui.render()
|
|
119
|
+
else:
|
|
120
|
+
self._g_pressed = True
|
|
121
|
+
|
|
122
|
+
@kb.add("G")
|
|
123
|
+
def _(event):
|
|
124
|
+
if self._ui._search_mode:
|
|
125
|
+
self._ui.append_search_char("G")
|
|
126
|
+
self._ui.render()
|
|
127
|
+
return
|
|
128
|
+
self._g_pressed = False
|
|
129
|
+
if self._ui.move_cursor("bottom"):
|
|
130
|
+
self._ui.render()
|
|
131
|
+
|
|
132
|
+
@kb.add(Keys.ControlU)
|
|
133
|
+
def _(event):
|
|
134
|
+
if self._ui._search_mode:
|
|
135
|
+
return
|
|
136
|
+
self._g_pressed = False
|
|
137
|
+
if self._ui.move_cursor("half_up"):
|
|
138
|
+
self._ui.render()
|
|
139
|
+
|
|
140
|
+
@kb.add(Keys.ControlD)
|
|
141
|
+
def _(event):
|
|
142
|
+
if self._ui._search_mode:
|
|
143
|
+
return
|
|
144
|
+
self._g_pressed = False
|
|
145
|
+
if self._ui.move_cursor("half_down"):
|
|
146
|
+
self._ui.render()
|
|
147
|
+
|
|
148
|
+
@kb.add(Keys.PageUp)
|
|
149
|
+
def _(event):
|
|
150
|
+
if self._ui._search_mode:
|
|
151
|
+
return
|
|
152
|
+
self._g_pressed = False
|
|
153
|
+
if self._ui.move_cursor("page_up"):
|
|
154
|
+
self._ui.render()
|
|
155
|
+
|
|
156
|
+
@kb.add(Keys.PageDown)
|
|
157
|
+
def _(event):
|
|
158
|
+
if self._ui._search_mode:
|
|
159
|
+
return
|
|
160
|
+
self._g_pressed = False
|
|
161
|
+
if self._ui.move_cursor("page_down"):
|
|
162
|
+
self._ui.render()
|
|
163
|
+
|
|
164
|
+
@kb.add("v")
|
|
165
|
+
def _(event):
|
|
166
|
+
if self._ui._search_mode:
|
|
167
|
+
self._ui.append_search_char("v")
|
|
168
|
+
self._ui.render()
|
|
169
|
+
return
|
|
170
|
+
self._g_pressed = False
|
|
171
|
+
self._ui.toggle_visual_mode()
|
|
172
|
+
self._ui.render()
|
|
173
|
+
|
|
174
|
+
@kb.add("y")
|
|
175
|
+
def _(event):
|
|
176
|
+
if self._ui._search_mode:
|
|
177
|
+
self._ui.append_search_char("y")
|
|
178
|
+
self._ui.render()
|
|
179
|
+
return
|
|
180
|
+
self._g_pressed = False
|
|
181
|
+
text = self._ui.get_selected_text()
|
|
182
|
+
if text and self._on_copy:
|
|
183
|
+
self._on_copy(text)
|
|
184
|
+
if self._ui._visual_mode:
|
|
185
|
+
self._ui.toggle_visual_mode()
|
|
186
|
+
self._ui.render()
|
|
187
|
+
|
|
188
|
+
@kb.add("Y")
|
|
189
|
+
def _(event):
|
|
190
|
+
if self._ui._search_mode:
|
|
191
|
+
self._ui.append_search_char("Y")
|
|
192
|
+
self._ui.render()
|
|
193
|
+
return
|
|
194
|
+
self._g_pressed = False
|
|
195
|
+
if 0 <= self._ui._cursor_line < len(self._ui._lines):
|
|
196
|
+
line_text = self._ui._lines[self._ui._cursor_line][0]
|
|
197
|
+
if self._on_copy:
|
|
198
|
+
self._on_copy(line_text)
|
|
199
|
+
|
|
200
|
+
@kb.add("/")
|
|
201
|
+
def _(event):
|
|
202
|
+
if self._ui._search_mode:
|
|
203
|
+
self._ui.append_search_char("/")
|
|
204
|
+
self._ui.render()
|
|
205
|
+
return
|
|
206
|
+
self._g_pressed = False
|
|
207
|
+
self._ui.start_search_mode()
|
|
208
|
+
self._ui.render()
|
|
209
|
+
|
|
210
|
+
@kb.add("n")
|
|
211
|
+
def _(event):
|
|
212
|
+
if self._ui._search_mode:
|
|
213
|
+
self._ui.append_search_char("n")
|
|
214
|
+
self._ui.render()
|
|
215
|
+
return
|
|
216
|
+
self._g_pressed = False
|
|
217
|
+
self._ui.next_search_match()
|
|
218
|
+
self._ui.render()
|
|
219
|
+
|
|
220
|
+
@kb.add("N")
|
|
221
|
+
def _(event):
|
|
222
|
+
if self._ui._search_mode:
|
|
223
|
+
self._ui.append_search_char("N")
|
|
224
|
+
self._ui.render()
|
|
225
|
+
return
|
|
226
|
+
self._g_pressed = False
|
|
227
|
+
self._ui.prev_search_match()
|
|
228
|
+
self._ui.render()
|
|
229
|
+
|
|
230
|
+
@kb.add(Keys.Enter)
|
|
231
|
+
def _(event):
|
|
232
|
+
if self._ui._search_mode:
|
|
233
|
+
self._ui.exit_search_mode(clear_results=False)
|
|
234
|
+
self._ui.render()
|
|
235
|
+
|
|
236
|
+
@kb.add(Keys.Backspace)
|
|
237
|
+
def _(event):
|
|
238
|
+
if self._ui._search_mode:
|
|
239
|
+
self._ui.backspace_search()
|
|
240
|
+
self._ui.render()
|
|
241
|
+
|
|
242
|
+
@kb.add(Keys.Escape)
|
|
243
|
+
def _(event):
|
|
244
|
+
self._g_pressed = False
|
|
245
|
+
if self._ui._visual_mode:
|
|
246
|
+
self._ui.toggle_visual_mode()
|
|
247
|
+
self._ui.render()
|
|
248
|
+
elif self._ui._search_mode:
|
|
249
|
+
self._ui.exit_search_mode(clear_results=True)
|
|
250
|
+
self._ui.render()
|
|
251
|
+
else:
|
|
252
|
+
event.app.exit()
|
|
253
|
+
|
|
254
|
+
@kb.add("q")
|
|
255
|
+
def _(event):
|
|
256
|
+
if self._ui._search_mode:
|
|
257
|
+
self._ui.append_search_char("q")
|
|
258
|
+
self._ui.render()
|
|
259
|
+
return
|
|
260
|
+
self._g_pressed = False
|
|
261
|
+
event.app.exit()
|
|
262
|
+
|
|
263
|
+
@kb.add(Keys.ControlC)
|
|
264
|
+
def _(event):
|
|
265
|
+
self._g_pressed = False
|
|
266
|
+
if self._ui._visual_mode:
|
|
267
|
+
text = self._ui.get_selected_text()
|
|
268
|
+
if text and self._on_copy:
|
|
269
|
+
self._on_copy(text)
|
|
270
|
+
self._ui.toggle_visual_mode()
|
|
271
|
+
self._ui.render()
|
|
272
|
+
elif self._ui._search_mode:
|
|
273
|
+
self._ui.exit_search_mode(clear_results=True)
|
|
274
|
+
self._ui.render()
|
|
275
|
+
else:
|
|
276
|
+
event.app.exit()
|
|
277
|
+
|
|
278
|
+
@kb.add(Keys.Any)
|
|
279
|
+
def _(event):
|
|
280
|
+
if self._ui._search_mode:
|
|
281
|
+
char = event.data
|
|
282
|
+
if char and char.isprintable():
|
|
283
|
+
self._ui.append_search_char(char)
|
|
284
|
+
self._ui.render()
|
|
285
|
+
return
|
|
286
|
+
self._g_pressed = False
|
|
287
|
+
|
|
288
|
+
return kb
|
|
289
|
+
|
|
290
|
+
def run(self):
|
|
291
|
+
self._running = True
|
|
292
|
+
self._g_pressed = False
|
|
293
|
+
|
|
294
|
+
self._ui.start_live()
|
|
295
|
+
|
|
296
|
+
kb = self._create_key_bindings()
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
session = PromptSession(key_bindings=kb)
|
|
300
|
+
session.prompt("")
|
|
301
|
+
except (KeyboardInterrupt, EOFError):
|
|
302
|
+
pass
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(f"Error in visual mode input handler: {e}")
|
|
305
|
+
finally:
|
|
306
|
+
self._ui.stop_live()
|
|
307
|
+
|
|
308
|
+
self._running = False
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def is_running(self) -> bool:
|
|
312
|
+
return self._running
|
|
313
|
+
|
|
314
|
+
def stop(self):
|
|
315
|
+
self._running = False
|