cct-cli 0.7.9.0__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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT v0.7.9.0 — resizers.py: real draggable split-pane handles.
|
|
3
|
+
|
|
4
|
+
Two independent resizable areas (spec sections 12, 15-16):
|
|
5
|
+
|
|
6
|
+
1. ExplorerResizeHandle — the vertical divider between the FILE
|
|
7
|
+
EXPLORER and the MAIN CHAT AREA. Drag left => explorer narrower,
|
|
8
|
+
drag right => explorer wider. Clamped to [MIN, max % of shell].
|
|
9
|
+
|
|
10
|
+
2. ComposerResizeHandle — the subtle horizontal grip between the chat
|
|
11
|
+
history and the CHAT COMPOSER. Drag up => taller composer, drag
|
|
12
|
+
down => shorter. Clamped so a usable message area always remains.
|
|
13
|
+
|
|
14
|
+
Both are plain Textual widgets using capture_mouse() drags — no timers,
|
|
15
|
+
no busy loops. Chosen sizes persist to ~/.cct_ui_layout.json so they
|
|
16
|
+
survive restarts, and are re-clamped when the window itself resizes.
|
|
17
|
+
|
|
18
|
+
The handles exist to keep the layout architecture honest: the composer
|
|
19
|
+
is a flow child of the main chat column (NEVER docked/positioned across
|
|
20
|
+
the whole screen), so it can never overlap the explorer again.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
print("This is a library file and is not meant to be run directly.")
|
|
25
|
+
import sys
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
|
|
31
|
+
TEXTUAL_AVAILABLE = True
|
|
32
|
+
try:
|
|
33
|
+
from textual.widgets import Static
|
|
34
|
+
from textual.containers import Vertical
|
|
35
|
+
except Exception:
|
|
36
|
+
TEXTUAL_AVAILABLE = False
|
|
37
|
+
|
|
38
|
+
LAYOUT_PATH = os.path.join(os.path.expanduser("~"), ".cct_ui_layout.json")
|
|
39
|
+
|
|
40
|
+
# ---- Explorer width bounds ------------------------------------------------
|
|
41
|
+
EXPLORER_MIN_WIDTH = 14 # never disappears by accident while dragging
|
|
42
|
+
EXPLORER_MAX_FRACTION = 0.55 # never wider than ~half the shell
|
|
43
|
+
EXPLORER_DEFAULT_WIDTH = 32
|
|
44
|
+
|
|
45
|
+
# ---- Composer height bounds ----------------------------------------------
|
|
46
|
+
COMPOSER_MIN_HEIGHT = 5 # input + footer stay usable
|
|
47
|
+
COMPOSER_MAX_FRACTION = 0.35 # compact bounds — chat history keeps majority of shell
|
|
48
|
+
COMPOSER_MARGIN_ROWS = 2 # composer's vertical margin rows (1 top + 1 bottom)
|
|
49
|
+
|
|
50
|
+
# ---- Right pane (Code / Preview) width bounds ------------------------------
|
|
51
|
+
RIGHTPANE_MIN_WIDTH = 24 # code/preview stays usable
|
|
52
|
+
RIGHTPANE_MAX_FRACTION = 0.60
|
|
53
|
+
RIGHTPANE_DEFAULT_WIDTH = 44
|
|
54
|
+
CHAT_MIN_FRACTION = 0.25 # chat column keeps at least this much of the shell
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def load_layout():
|
|
58
|
+
"""Persisted {explorer_width, composer_height, rightpane_width}
|
|
59
|
+
(ints or None)."""
|
|
60
|
+
data = {}
|
|
61
|
+
try:
|
|
62
|
+
with open(LAYOUT_PATH, "r", encoding="utf-8") as f:
|
|
63
|
+
data = json.load(f)
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
out = {}
|
|
67
|
+
for key in ("explorer_width", "composer_height", "rightpane_width"):
|
|
68
|
+
try:
|
|
69
|
+
v = data.get(key)
|
|
70
|
+
if key == "composer_height" and (v is None or int(v) <= COMPOSER_MIN_HEIGHT or int(v) > 14):
|
|
71
|
+
out[key] = None
|
|
72
|
+
else:
|
|
73
|
+
out[key] = int(v) if v else None
|
|
74
|
+
except Exception:
|
|
75
|
+
out[key] = None
|
|
76
|
+
return out
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def save_layout(explorer_width=None, composer_height=None,
|
|
80
|
+
rightpane_width=None):
|
|
81
|
+
try:
|
|
82
|
+
current = {}
|
|
83
|
+
if os.path.exists(LAYOUT_PATH):
|
|
84
|
+
with open(LAYOUT_PATH, "r", encoding="utf-8") as f:
|
|
85
|
+
current = json.load(f)
|
|
86
|
+
if explorer_width is not None:
|
|
87
|
+
current["explorer_width"] = int(explorer_width)
|
|
88
|
+
if composer_height is not None:
|
|
89
|
+
current["composer_height"] = int(composer_height)
|
|
90
|
+
if rightpane_width is not None:
|
|
91
|
+
current["rightpane_width"] = int(rightpane_width)
|
|
92
|
+
with open(LAYOUT_PATH, "w", encoding="utf-8") as f:
|
|
93
|
+
json.dump(current, f)
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if TEXTUAL_AVAILABLE:
|
|
99
|
+
|
|
100
|
+
class _DragHandle(Static):
|
|
101
|
+
"""Shared mouse-drag plumbing: capture on press, track moves,
|
|
102
|
+
release + persist on drop. Subclasses implement `_apply(x, y)`
|
|
103
|
+
and `_persist()`."""
|
|
104
|
+
|
|
105
|
+
def __init__(self, renderable="", **kwargs):
|
|
106
|
+
super().__init__(renderable, **kwargs)
|
|
107
|
+
self._dragging = False
|
|
108
|
+
|
|
109
|
+
def _apply(self, event):
|
|
110
|
+
raise NotImplementedError
|
|
111
|
+
|
|
112
|
+
def _persist(self):
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
def on_mouse_down(self, event):
|
|
116
|
+
event.stop()
|
|
117
|
+
self._dragging = True
|
|
118
|
+
self.add_class("dragging")
|
|
119
|
+
# Grab the pointer even when it leaves this 1-cell widget —
|
|
120
|
+
# that's what makes dragging past the handle smooth.
|
|
121
|
+
self.capture_mouse()
|
|
122
|
+
|
|
123
|
+
def on_mouse_move(self, event):
|
|
124
|
+
if not self._dragging:
|
|
125
|
+
return
|
|
126
|
+
event.stop()
|
|
127
|
+
self._apply(event)
|
|
128
|
+
|
|
129
|
+
def on_mouse_up(self, event):
|
|
130
|
+
if not self._dragging:
|
|
131
|
+
return
|
|
132
|
+
event.stop()
|
|
133
|
+
self._dragging = False
|
|
134
|
+
self.remove_class("dragging")
|
|
135
|
+
try:
|
|
136
|
+
# NOTE: Widget.release_mouse() takes no arguments.
|
|
137
|
+
self.release_mouse()
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
self._persist()
|
|
141
|
+
|
|
142
|
+
def on_unmount(self):
|
|
143
|
+
self._dragging = False
|
|
144
|
+
|
|
145
|
+
class ExplorerResizeHandle(_DragHandle):
|
|
146
|
+
"""1-column divider between Explorer and the main area."""
|
|
147
|
+
|
|
148
|
+
DEFAULT_CSS = """
|
|
149
|
+
ExplorerResizeHandle {
|
|
150
|
+
width: 1;
|
|
151
|
+
height: 100%;
|
|
152
|
+
background: $border;
|
|
153
|
+
transition: background 100ms;
|
|
154
|
+
}
|
|
155
|
+
ExplorerResizeHandle:hover,
|
|
156
|
+
ExplorerResizeHandle.dragging {
|
|
157
|
+
background: $accent;
|
|
158
|
+
}
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def __init__(self, shell, **kwargs):
|
|
162
|
+
super().__init__(id="cct-explorer-resizer", **kwargs)
|
|
163
|
+
self._shell = shell
|
|
164
|
+
|
|
165
|
+
def _clamp(self, width):
|
|
166
|
+
total = max(10, self._shell.size.width) if self._shell.is_attached \
|
|
167
|
+
else EXPLORER_DEFAULT_WIDTH * 2
|
|
168
|
+
ceiling = max(EXPLORER_MIN_WIDTH + 4,
|
|
169
|
+
int(total * EXPLORER_MAX_FRACTION))
|
|
170
|
+
return max(EXPLORER_MIN_WIDTH, min(ceiling, width))
|
|
171
|
+
|
|
172
|
+
def _apply(self, event):
|
|
173
|
+
explorer = self._shell.explorer
|
|
174
|
+
left_edge = explorer.region.x if explorer.region.x >= 0 else 0
|
|
175
|
+
target = event.screen_x - left_edge
|
|
176
|
+
explorer.set_width(self._clamp(target))
|
|
177
|
+
self._shell.sync_resizer()
|
|
178
|
+
try:
|
|
179
|
+
if not self._shell._fullscreen:
|
|
180
|
+
self._shell._relayout()
|
|
181
|
+
except Exception:
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
def _persist(self):
|
|
185
|
+
save_layout(explorer_width=self._shell.explorer.width)
|
|
186
|
+
|
|
187
|
+
class ComposerResizeHandle(_DragHandle):
|
|
188
|
+
"""Subtle full-width grip between chat history and composer."""
|
|
189
|
+
|
|
190
|
+
DEFAULT_CSS = """
|
|
191
|
+
ComposerResizeHandle {
|
|
192
|
+
width: 100%;
|
|
193
|
+
height: 1;
|
|
194
|
+
color: $text-faint;
|
|
195
|
+
content-align: center middle;
|
|
196
|
+
background: transparent;
|
|
197
|
+
transition: color 100ms;
|
|
198
|
+
}
|
|
199
|
+
ComposerResizeHandle:hover,
|
|
200
|
+
ComposerResizeHandle.dragging {
|
|
201
|
+
color: $accent;
|
|
202
|
+
background: transparent;
|
|
203
|
+
}
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
GRIP = "\u2500 \u00b7 \u2500 \u00b7 \u2500 \u00b7 \u2500"
|
|
207
|
+
|
|
208
|
+
def __init__(self, shell, **kwargs):
|
|
209
|
+
super().__init__(self.GRIP, id="cct-composer-resizer", **kwargs)
|
|
210
|
+
self._shell = shell
|
|
211
|
+
|
|
212
|
+
def reset_to_default(self):
|
|
213
|
+
"""Reset composer to normal default compact height."""
|
|
214
|
+
from .composer import StickyComposer
|
|
215
|
+
try:
|
|
216
|
+
composer = self._shell.query_one(StickyComposer)
|
|
217
|
+
composer.set_explicit_height(None)
|
|
218
|
+
except Exception:
|
|
219
|
+
pass
|
|
220
|
+
save_layout(composer_height=None)
|
|
221
|
+
|
|
222
|
+
def on_click(self, event):
|
|
223
|
+
import time
|
|
224
|
+
now = time.monotonic()
|
|
225
|
+
if hasattr(self, "_last_click_time") and (now - self._last_click_time) < 0.6:
|
|
226
|
+
self._last_click_time = 0.0
|
|
227
|
+
event.stop()
|
|
228
|
+
self.reset_to_default()
|
|
229
|
+
return
|
|
230
|
+
self._last_click_time = now
|
|
231
|
+
|
|
232
|
+
def on_mouse_down(self, event):
|
|
233
|
+
import time
|
|
234
|
+
now = time.monotonic()
|
|
235
|
+
if hasattr(self, "_last_down_time") and (now - self._last_down_time) < 0.8:
|
|
236
|
+
# Double-click resets to normal default compact height
|
|
237
|
+
self._last_down_time = 0.0
|
|
238
|
+
event.stop()
|
|
239
|
+
self._dragging = False
|
|
240
|
+
self.remove_class("dragging")
|
|
241
|
+
try:
|
|
242
|
+
self.release_mouse()
|
|
243
|
+
except Exception:
|
|
244
|
+
pass
|
|
245
|
+
self.reset_to_default()
|
|
246
|
+
return
|
|
247
|
+
self._last_down_time = now
|
|
248
|
+
super().on_mouse_down(event)
|
|
249
|
+
|
|
250
|
+
def _bounds(self):
|
|
251
|
+
"""(min_height, max_height) for the composer inside the main
|
|
252
|
+
column right now."""
|
|
253
|
+
try:
|
|
254
|
+
main = self._shell.query_one("#cct-workspace-main", Vertical)
|
|
255
|
+
available = max(20, main.region.height)
|
|
256
|
+
except Exception:
|
|
257
|
+
available = 20
|
|
258
|
+
max_h = min(14, max(COMPOSER_MIN_HEIGHT + 4, int(available * COMPOSER_MAX_FRACTION)))
|
|
259
|
+
return COMPOSER_MIN_HEIGHT, max_h
|
|
260
|
+
|
|
261
|
+
def _apply(self, event):
|
|
262
|
+
from .composer import StickyComposer
|
|
263
|
+
try:
|
|
264
|
+
composer = self._shell.query_one(StickyComposer)
|
|
265
|
+
except Exception:
|
|
266
|
+
return
|
|
267
|
+
# Derive the new height from the MAIN COLUMN's static
|
|
268
|
+
# geometry (never from the composer's live region — reading
|
|
269
|
+
# a widget you are simultaneously resizing feeds the next
|
|
270
|
+
# layout pass back into the math and oscillates).
|
|
271
|
+
try:
|
|
272
|
+
main = self._shell.query_one("#cct-workspace-main", Vertical)
|
|
273
|
+
main_bottom = main.region.y + main.region.height
|
|
274
|
+
except Exception:
|
|
275
|
+
return
|
|
276
|
+
lo, hi = self._bounds()
|
|
277
|
+
# Rows reserved below the drag point: the grip row itself +
|
|
278
|
+
# the composer's bottom margin. Height grows as the handle
|
|
279
|
+
# is dragged up, shrinks as it is dragged down.
|
|
280
|
+
target = int(main_bottom - COMPOSER_MARGIN_ROWS - event.screen_y)
|
|
281
|
+
if target <= lo + 1:
|
|
282
|
+
composer.set_explicit_height(None)
|
|
283
|
+
else:
|
|
284
|
+
target = max(lo, min(hi, target))
|
|
285
|
+
composer.set_explicit_height(target)
|
|
286
|
+
|
|
287
|
+
def _persist(self):
|
|
288
|
+
from .composer import StickyComposer
|
|
289
|
+
try:
|
|
290
|
+
composer = self._shell.query_one(StickyComposer)
|
|
291
|
+
except Exception:
|
|
292
|
+
return
|
|
293
|
+
h = getattr(composer, "_explicit_height", None)
|
|
294
|
+
save_layout(composer_height=h)
|
|
295
|
+
|
|
296
|
+
class RightPaneResizeHandle(_DragHandle):
|
|
297
|
+
"""v0.7.10: 1-column divider between the CHAT column and the
|
|
298
|
+
CODE / PREVIEW pane. Drag left => right pane wider, right =>
|
|
299
|
+
narrower. Mirrors ExplorerResizeHandle's contract and persists
|
|
300
|
+
to the same layout file (rightpane_width)."""
|
|
301
|
+
|
|
302
|
+
DEFAULT_CSS = """
|
|
303
|
+
RightPaneResizeHandle {
|
|
304
|
+
width: 1;
|
|
305
|
+
height: 100%;
|
|
306
|
+
background: $border;
|
|
307
|
+
transition: background 100ms;
|
|
308
|
+
}
|
|
309
|
+
RightPaneResizeHandle:hover,
|
|
310
|
+
RightPaneResizeHandle.dragging {
|
|
311
|
+
background: $accent;
|
|
312
|
+
}
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
def __init__(self, shell, **kwargs):
|
|
316
|
+
super().__init__(id="cct-rightpane-resizer", **kwargs)
|
|
317
|
+
self._shell = shell
|
|
318
|
+
|
|
319
|
+
def _clamp(self, width):
|
|
320
|
+
total = max(20, self._shell.size.width) if self._shell.is_attached \
|
|
321
|
+
else RIGHTPANE_DEFAULT_WIDTH * 2
|
|
322
|
+
ceiling = max(RIGHTPANE_MIN_WIDTH + 4,
|
|
323
|
+
int(total * RIGHTPANE_MAX_FRACTION))
|
|
324
|
+
return max(RIGHTPANE_MIN_WIDTH, min(ceiling, width))
|
|
325
|
+
|
|
326
|
+
def _apply(self, event):
|
|
327
|
+
pane = self._shell.right_pane
|
|
328
|
+
if pane is None:
|
|
329
|
+
return
|
|
330
|
+
# Width grows as the handle moves LEFT of its own right edge.
|
|
331
|
+
right_edge = pane.region.x + pane.region.width
|
|
332
|
+
target = right_edge - event.screen_x
|
|
333
|
+
# Route through the shell's width owner (clamps + remembers
|
|
334
|
+
# the value for persistence). The raw Vertical container has
|
|
335
|
+
# no set_width of its own.
|
|
336
|
+
self._shell.set_right_width(target)
|
|
337
|
+
self._shell.sync_right_resizer()
|
|
338
|
+
|
|
339
|
+
def _persist(self):
|
|
340
|
+
# The shell remembers the last clamped width — never depend
|
|
341
|
+
# on a layout pass having landed for the live region read.
|
|
342
|
+
save_layout(rightpane_width=getattr(self._shell,
|
|
343
|
+
"_rightpane_width", None))
|
|
344
|
+
|
|
345
|
+
class SplitPreviewResizer(_DragHandle):
|
|
346
|
+
"""v0.7.9 split view divider between editor and preview inside right pane.
|
|
347
|
+
Drag left => preview wider, right => editor wider. Only visible in SPLIT mode."""
|
|
348
|
+
|
|
349
|
+
DEFAULT_CSS = """
|
|
350
|
+
SplitPreviewResizer {
|
|
351
|
+
width: 1;
|
|
352
|
+
height: 100%;
|
|
353
|
+
background: $border;
|
|
354
|
+
transition: background 100ms;
|
|
355
|
+
}
|
|
356
|
+
SplitPreviewResizer:hover,
|
|
357
|
+
SplitPreviewResizer.dragging {
|
|
358
|
+
background: $accent;
|
|
359
|
+
}
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def __init__(self, shell, **kwargs):
|
|
363
|
+
super().__init__(id="cct-split-resizer", **kwargs)
|
|
364
|
+
self._shell = shell
|
|
365
|
+
|
|
366
|
+
def _apply(self, event):
|
|
367
|
+
# Adjust split ratio based on handle position inside right pane
|
|
368
|
+
right = self._shell.right_pane
|
|
369
|
+
if right is None:
|
|
370
|
+
return
|
|
371
|
+
# Right pane's left edge
|
|
372
|
+
left_edge = right.region.x if right.region.x >=0 else 0
|
|
373
|
+
total_w = max(20, right.region.width)
|
|
374
|
+
# Position of handle relative to right pane left
|
|
375
|
+
pos = event.screen_x - left_edge
|
|
376
|
+
# Clamp so both editor and preview keep min width
|
|
377
|
+
min_w = 12
|
|
378
|
+
pos = max(min_w, min(total_w - min_w, pos))
|
|
379
|
+
ratio = pos / max(1, total_w)
|
|
380
|
+
# Apply as widths to editor and preview
|
|
381
|
+
try:
|
|
382
|
+
editor = self._shell.editor
|
|
383
|
+
preview = self._shell.preview_panel
|
|
384
|
+
if editor and preview:
|
|
385
|
+
# Use integer widths based on total
|
|
386
|
+
ed_w = max(min_w, int(total_w * ratio))
|
|
387
|
+
pv_w = max(min_w, total_w - ed_w - 1) # -1 for handle
|
|
388
|
+
editor.styles.width = ed_w
|
|
389
|
+
preview.styles.width = pv_w
|
|
390
|
+
except Exception:
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
def _persist(self):
|
|
394
|
+
pass
|
|
395
|
+
|
|
396
|
+
else:
|
|
397
|
+
|
|
398
|
+
_DragHandle = None
|
|
399
|
+
ExplorerResizeHandle = None
|
|
400
|
+
ComposerResizeHandle = None
|
|
401
|
+
RightPaneResizeHandle = None
|
|
402
|
+
SplitPreviewResizer = None
|