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
core/io/terminal_renderer.py
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
"""Terminal rendering system for Kollabor CLI.
|
|
1
|
+
"""Terminal rendering system for Kollabor CLI.
|
|
2
2
|
|
|
3
|
+
This module provides comprehensive terminal rendering for the Kollabor CLI
|
|
4
|
+
application, including visual effects, layout management, message
|
|
5
|
+
display, and terminal state management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
3
9
|
import logging
|
|
4
10
|
from collections import deque
|
|
5
|
-
from typing import List, Optional
|
|
11
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
6
12
|
|
|
7
|
-
from .visual_effects import VisualEffects
|
|
8
|
-
from .terminal_state import TerminalState
|
|
9
13
|
from .layout import LayoutManager, ThinkingAnimationManager
|
|
10
|
-
from .status_renderer import StatusRenderer
|
|
11
|
-
from .message_renderer import MessageRenderer
|
|
12
14
|
from .message_coordinator import MessageDisplayCoordinator
|
|
15
|
+
from .message_renderer import MessageRenderer
|
|
16
|
+
from .status_renderer import StatusRenderer
|
|
17
|
+
from .terminal_state import TerminalState
|
|
18
|
+
from .visual_effects import VisualEffects
|
|
13
19
|
|
|
14
20
|
if TYPE_CHECKING:
|
|
15
21
|
from ..config.manager import ConfigManager
|
|
@@ -59,7 +65,6 @@ class TerminalRenderer:
|
|
|
59
65
|
# Interface properties
|
|
60
66
|
self.input_buffer = ""
|
|
61
67
|
self.cursor_position = 0
|
|
62
|
-
self.status_areas = {"A": [], "B": [], "C": []}
|
|
63
68
|
self.thinking_active = False
|
|
64
69
|
|
|
65
70
|
# State management
|
|
@@ -165,9 +170,9 @@ class TerminalRenderer:
|
|
|
165
170
|
"""Set the thinking text effect.
|
|
166
171
|
|
|
167
172
|
Args:
|
|
168
|
-
effect: Effect type - "dim", "shimmer", or "
|
|
173
|
+
effect: Effect type - "dim", "shimmer", "pulse", "scramble", or "none"
|
|
169
174
|
"""
|
|
170
|
-
if effect in ["dim", "shimmer", "normal"]:
|
|
175
|
+
if effect in ["dim", "shimmer", "pulse", "scramble", "none", "normal"]:
|
|
171
176
|
self.thinking_effect = effect
|
|
172
177
|
self.visual_effects.configure_effect("thinking", enabled=True)
|
|
173
178
|
logger.debug(f"Set thinking effect to: {effect}")
|
|
@@ -181,9 +186,7 @@ class TerminalRenderer:
|
|
|
181
186
|
speed: Number of frames between shimmer updates
|
|
182
187
|
wave_width: Number of characters in the shimmer wave
|
|
183
188
|
"""
|
|
184
|
-
self.visual_effects.configure_effect(
|
|
185
|
-
"thinking", speed=speed, width=wave_width
|
|
186
|
-
)
|
|
189
|
+
self.visual_effects.configure_effect("thinking", speed=speed, width=wave_width)
|
|
187
190
|
logger.debug(f"Configured shimmer: speed={speed}, wave_width={wave_width}")
|
|
188
191
|
|
|
189
192
|
def configure_thinking_limit(self, limit: int) -> None:
|
|
@@ -201,14 +204,15 @@ class TerminalRenderer:
|
|
|
201
204
|
This method renders dynamic interface parts:
|
|
202
205
|
thinking animation, input prompt, and status lines.
|
|
203
206
|
"""
|
|
204
|
-
# logger.info("[START] render_active_area() called")
|
|
205
|
-
|
|
206
207
|
# CRITICAL: Skip ALL rendering when modal is active to prevent interference
|
|
207
208
|
if hasattr(self, "input_handler") and self.input_handler:
|
|
208
209
|
try:
|
|
209
210
|
from ..events.models import CommandMode
|
|
210
211
|
|
|
211
|
-
if self.input_handler.command_mode in (
|
|
212
|
+
if self.input_handler.command_mode in (
|
|
213
|
+
CommandMode.MODAL,
|
|
214
|
+
CommandMode.LIVE_MODAL,
|
|
215
|
+
):
|
|
212
216
|
return
|
|
213
217
|
except Exception as e:
|
|
214
218
|
logger.error(f"Error checking modal state: {e}")
|
|
@@ -243,23 +247,51 @@ class TerminalRenderer:
|
|
|
243
247
|
if not has_enhanced_input:
|
|
244
248
|
return
|
|
245
249
|
|
|
246
|
-
# Update terminal size and invalidate cache if resized
|
|
250
|
+
# Update terminal size and invalidate cache if resized (with 0.9s debouncing)
|
|
247
251
|
old_size = self.terminal_state.get_size()
|
|
248
|
-
self.terminal_state.update_size()
|
|
249
|
-
terminal_width, terminal_height = self.terminal_state.get_size()
|
|
250
252
|
|
|
251
|
-
# Check
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
253
|
+
# Check if resize has settled (0.9s debounce to prevent rapid re-renders)
|
|
254
|
+
resize_settled = self.terminal_state.check_and_clear_resize_flag()
|
|
255
|
+
|
|
256
|
+
size_changed = False
|
|
257
|
+
if resize_settled:
|
|
258
|
+
# Resize has settled - poll actual terminal size
|
|
259
|
+
self.terminal_state.update_size()
|
|
260
|
+
terminal_width, terminal_height = self.terminal_state.get_size()
|
|
261
|
+
|
|
262
|
+
# Only trigger aggressive clearing if width reduced by 10% or more
|
|
263
|
+
# Small reductions don't cause artifacts, so we skip clearing for minor changes
|
|
264
|
+
# Height changes don't matter for clearing (only width affects layout)
|
|
265
|
+
width_reduction = (old_size[0] - terminal_width) / old_size[0]
|
|
266
|
+
if terminal_width < old_size[0] and width_reduction >= 0.1:
|
|
267
|
+
size_changed = True
|
|
268
|
+
self.invalidate_render_cache()
|
|
269
|
+
|
|
270
|
+
# Note: Input buffer and cursor position are preserved on resize
|
|
271
|
+
# Active area will be cleared by _render_lines() using aggressive clearing
|
|
272
|
+
logger.debug(
|
|
273
|
+
f"Terminal width reduced by {width_reduction*100:.1f}% ({old_size[0]} -> {terminal_width}) - will use aggressive clearing"
|
|
274
|
+
)
|
|
275
|
+
elif old_size != (terminal_width, terminal_height):
|
|
276
|
+
# Size increased or small reduction - just invalidate cache, no aggressive clearing
|
|
277
|
+
self.invalidate_render_cache()
|
|
278
|
+
logger.debug(
|
|
279
|
+
f"Terminal size changed: {old_size[0]}x{old_size[1]} -> {terminal_width}x{terminal_height} - cache invalidated, no clearing"
|
|
280
|
+
)
|
|
281
|
+
else:
|
|
282
|
+
# No resize or still debouncing - use current size
|
|
283
|
+
terminal_width, terminal_height = old_size
|
|
257
284
|
|
|
258
285
|
self.layout_manager.set_terminal_size(terminal_width, terminal_height)
|
|
259
286
|
self.status_renderer.set_terminal_width(terminal_width)
|
|
260
287
|
|
|
261
288
|
lines = []
|
|
262
289
|
|
|
290
|
+
# Add safety buffer line at top of active area
|
|
291
|
+
# This provides a "clear zone" for aggressive clearing during resize
|
|
292
|
+
# without deleting conversation content above the active area
|
|
293
|
+
lines.append("")
|
|
294
|
+
|
|
263
295
|
# Add thinking animation if active
|
|
264
296
|
if self.thinking_active:
|
|
265
297
|
thinking_lines = self.thinking_animation.get_display_lines(
|
|
@@ -290,15 +322,15 @@ class TerminalRenderer:
|
|
|
290
322
|
# Replace status with status modal
|
|
291
323
|
lines.extend(status_modal_lines)
|
|
292
324
|
else:
|
|
293
|
-
#
|
|
294
|
-
self._update_status_areas()
|
|
325
|
+
# Render status views
|
|
295
326
|
status_lines = self.status_renderer.render_horizontal_layout(
|
|
296
327
|
self.visual_effects.apply_status_colors
|
|
297
328
|
)
|
|
298
329
|
lines.extend(status_lines)
|
|
299
330
|
|
|
300
331
|
# Clear previous render and write new content
|
|
301
|
-
|
|
332
|
+
# Pass resize flag to ensure aggressive clearing is used when size changed
|
|
333
|
+
await self._render_lines(lines, size_changed=size_changed)
|
|
302
334
|
|
|
303
335
|
async def _render_input_area(self, lines: List[str]) -> None:
|
|
304
336
|
"""Render the input area, checking for plugin overrides.
|
|
@@ -346,9 +378,7 @@ class TerminalRenderer:
|
|
|
346
378
|
)
|
|
347
379
|
|
|
348
380
|
# Insert cursor character at position
|
|
349
|
-
text_with_cursor =
|
|
350
|
-
buffer_text[:cursor_pos] + "▌" + buffer_text[cursor_pos:]
|
|
351
|
-
)
|
|
381
|
+
text_with_cursor = buffer_text[:cursor_pos] + "▌" + buffer_text[cursor_pos:]
|
|
352
382
|
lines.append(f"> {text_with_cursor}")
|
|
353
383
|
|
|
354
384
|
def _write(self, text: str) -> None:
|
|
@@ -358,7 +388,7 @@ class TerminalRenderer:
|
|
|
358
388
|
text: Text to write.
|
|
359
389
|
"""
|
|
360
390
|
# Collect in buffer if buffered mode is active
|
|
361
|
-
if hasattr(self,
|
|
391
|
+
if hasattr(self, "_write_buffer") and self._write_buffer is not None:
|
|
362
392
|
self._write_buffer.append(text)
|
|
363
393
|
else:
|
|
364
394
|
self.terminal_state.write_raw(text)
|
|
@@ -369,9 +399,9 @@ class TerminalRenderer:
|
|
|
369
399
|
|
|
370
400
|
def _flush_buffered_write(self) -> None:
|
|
371
401
|
"""Flush all buffered writes at once to reduce flickering."""
|
|
372
|
-
if hasattr(self,
|
|
402
|
+
if hasattr(self, "_write_buffer") and self._write_buffer:
|
|
373
403
|
# Join all buffered content and write in one operation
|
|
374
|
-
self.terminal_state.write_raw(
|
|
404
|
+
self.terminal_state.write_raw("".join(self._write_buffer))
|
|
375
405
|
self._write_buffer = None
|
|
376
406
|
|
|
377
407
|
def _get_terminal_width(self) -> int:
|
|
@@ -462,23 +492,17 @@ class TerminalRenderer:
|
|
|
462
492
|
|
|
463
493
|
return []
|
|
464
494
|
|
|
465
|
-
def
|
|
466
|
-
"""Update status areas for rendering."""
|
|
467
|
-
for area_name, content in self.status_areas.items():
|
|
468
|
-
self.status_renderer.update_area_content(area_name, content)
|
|
469
|
-
|
|
470
|
-
async def _render_lines(self, lines: List[str]) -> None:
|
|
495
|
+
async def _render_lines(self, lines: List[str], size_changed: bool = False) -> None:
|
|
471
496
|
"""Render lines to terminal with proper clearing.
|
|
472
497
|
|
|
473
498
|
Args:
|
|
474
499
|
lines: Lines to render.
|
|
500
|
+
size_changed: True if terminal size changed (triggers aggressive clearing).
|
|
475
501
|
"""
|
|
476
502
|
# RENDER OPTIMIZATION: Only render if content actually changed
|
|
477
503
|
# Check if render caching is enabled via config
|
|
478
504
|
if self._app_config is not None:
|
|
479
|
-
cache_enabled = self._app_config.get(
|
|
480
|
-
"terminal.render_cache_enabled", True
|
|
481
|
-
)
|
|
505
|
+
cache_enabled = self._app_config.get("terminal.render_cache_enabled", True)
|
|
482
506
|
else:
|
|
483
507
|
cache_enabled = self._render_cache_enabled # Fallback to local setting
|
|
484
508
|
|
|
@@ -491,25 +515,32 @@ class TerminalRenderer:
|
|
|
491
515
|
|
|
492
516
|
current_line_count = len(lines)
|
|
493
517
|
|
|
494
|
-
# Check if terminal was resized - if so, use aggressive clearing
|
|
495
|
-
resize_occurred = self.terminal_state.check_and_clear_resize_flag()
|
|
496
|
-
|
|
497
518
|
# Use buffered write to reduce flickering (especially on Windows)
|
|
498
519
|
# Start buffering BEFORE clearing so clear+redraw happens atomically
|
|
499
520
|
self._start_buffered_write()
|
|
521
|
+
|
|
522
|
+
if size_changed:
|
|
523
|
+
await asyncio.sleep(1)
|
|
500
524
|
|
|
501
525
|
# Clear previous active area (now buffered to reduce flicker)
|
|
502
526
|
if self.input_line_written and hasattr(self, "last_line_count"):
|
|
503
|
-
if
|
|
527
|
+
if size_changed:
|
|
504
528
|
# RESIZE FIX: On resize, restore to saved cursor position (where active area started)
|
|
505
529
|
# and clear everything from there to bottom of screen
|
|
506
530
|
logger.debug(
|
|
507
|
-
"🔄 Terminal resize detected - restoring cursor and clearing"
|
|
531
|
+
f"🔄 Terminal resize detected (size_changed={size_changed}) - restoring cursor and clearing"
|
|
508
532
|
)
|
|
509
533
|
|
|
510
534
|
if self.active_area_start_position:
|
|
511
535
|
# Restore to where active area started before resize
|
|
512
536
|
self._write("\033[u") # Restore cursor position
|
|
537
|
+
# Move up extra lines to catch box drawing artifacts above saved position
|
|
538
|
+
self._write("\033[6A") # Move up 1 line (into safety buffer zone)
|
|
539
|
+
self._write("\033[1A") # Move up 1 line (into safety buffer zone)
|
|
540
|
+
self._write("\n")
|
|
541
|
+
self._write("\n")
|
|
542
|
+
self._write("\n")
|
|
543
|
+
self._write("\n")
|
|
513
544
|
# Clear from that position to end of screen
|
|
514
545
|
self._write("\033[J") # Clear from cursor to end of screen
|
|
515
546
|
else:
|
|
@@ -531,20 +562,35 @@ class TerminalRenderer:
|
|
|
531
562
|
if i > 0:
|
|
532
563
|
self._write("\n")
|
|
533
564
|
self._write(f"\r{line}")
|
|
565
|
+
|
|
534
566
|
|
|
535
567
|
# Hide cursor
|
|
536
568
|
self._write("\033[?25l") # Write hide cursor to buffer too
|
|
537
|
-
|
|
569
|
+
|
|
570
|
+
# Add small sleep to let terminal process ANSI escape sequences on resize
|
|
571
|
+
# This prevents visual artifacts when the terminal is still processing
|
|
572
|
+
if size_changed:
|
|
573
|
+
await asyncio.sleep(.1)
|
|
538
574
|
# Flush all writes at once
|
|
539
575
|
self._flush_buffered_write()
|
|
540
576
|
|
|
577
|
+
# Add small sleep to let terminal process ANSI escape sequences on resize
|
|
578
|
+
# This prevents visual artifacts when the terminal is still processing
|
|
579
|
+
if size_changed:
|
|
580
|
+
await asyncio.sleep(.4)
|
|
581
|
+
|
|
541
582
|
# Remember line count for next render
|
|
542
583
|
self.last_line_count = current_line_count
|
|
543
584
|
self.input_line_written = True
|
|
544
585
|
|
|
545
|
-
def clear_active_area(self) -> None:
|
|
546
|
-
"""Clear the active area before writing conversation messages.
|
|
547
|
-
|
|
586
|
+
def clear_active_area(self, force: bool = False) -> None:
|
|
587
|
+
"""Clear the active area before writing conversation messages.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
force: If True, clear regardless of input_line_written state.
|
|
591
|
+
Use for exit cleanup.
|
|
592
|
+
"""
|
|
593
|
+
if (force or self.input_line_written) and hasattr(self, "last_line_count"):
|
|
548
594
|
self.terminal_state.clear_line()
|
|
549
595
|
for _ in range(self.last_line_count - 1):
|
|
550
596
|
self.terminal_state.move_cursor_up(1)
|
core/io/terminal_state.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
"""Terminal state management for rendering system.
|
|
1
|
+
"""Terminal state management for rendering system.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive terminal state management for rendering
|
|
4
|
+
systems, including mode switching, capability detection, and
|
|
5
|
+
cross-platform terminal control.
|
|
6
|
+
"""
|
|
2
7
|
|
|
3
8
|
import logging
|
|
4
9
|
import os
|
|
@@ -145,7 +150,7 @@ class TerminalState:
|
|
|
145
150
|
self._last_size = (0, 0)
|
|
146
151
|
self._resize_occurred = False # Track if terminal resize happened
|
|
147
152
|
self._last_resize_time = 0 # Track when last resize signal arrived
|
|
148
|
-
self._resize_debounce_delay =
|
|
153
|
+
self._resize_debounce_delay = .9 # Wait 200ms for resize to settle
|
|
149
154
|
|
|
150
155
|
# Initialize terminal state
|
|
151
156
|
self._initialize_terminal()
|
|
@@ -275,8 +280,20 @@ class TerminalState:
|
|
|
275
280
|
new_mode |= ENABLE_VIRTUAL_TERMINAL_INPUT
|
|
276
281
|
kernel32.SetConsoleMode(stdin_handle, new_mode)
|
|
277
282
|
else:
|
|
278
|
-
# Unix:
|
|
279
|
-
tty.setraw(
|
|
283
|
+
# Unix: Set raw mode with flow control disabled
|
|
284
|
+
# tty.setraw() doesn't disable IXON, so Ctrl+S (XOFF) gets
|
|
285
|
+
# intercepted by terminal driver. We need to manually clear IXON.
|
|
286
|
+
fd = sys.stdin.fileno()
|
|
287
|
+
tty.setraw(fd)
|
|
288
|
+
# Now disable XON/XOFF flow control so Ctrl+S reaches the app
|
|
289
|
+
attrs = termios.tcgetattr(fd)
|
|
290
|
+
attrs[0] &= ~termios.IXON # Disable XOFF (Ctrl+S) interception
|
|
291
|
+
attrs[0] &= ~termios.IXOFF # Disable XON (Ctrl+Q) interception
|
|
292
|
+
termios.tcsetattr(fd, termios.TCSANOW, attrs)
|
|
293
|
+
# Ensure stdin is in blocking mode (not non-blocking)
|
|
294
|
+
import fcntl
|
|
295
|
+
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
|
296
|
+
fcntl.fcntl(fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
|
|
280
297
|
self.current_mode = TerminalMode.RAW
|
|
281
298
|
logger.debug("Entered raw terminal mode")
|
|
282
299
|
return True
|