superqode 0.1.5__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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- superqode-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""Command palette widget (Ctrl+K) for quick command access - Redesigned for accessibility."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
from textual import on
|
|
9
|
+
from textual.app import ComposeResult
|
|
10
|
+
from textual.containers import Vertical, VerticalScroll, Horizontal
|
|
11
|
+
from textual.message import Message
|
|
12
|
+
from textual.reactive import reactive
|
|
13
|
+
from textual.widget import Widget
|
|
14
|
+
from textual.widgets import Input, Static
|
|
15
|
+
|
|
16
|
+
from superqode.utils.fuzzy import FuzzySearch
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class PaletteCommand:
|
|
21
|
+
"""A command palette item."""
|
|
22
|
+
|
|
23
|
+
id: str
|
|
24
|
+
label: str
|
|
25
|
+
description: str
|
|
26
|
+
icon: str = ""
|
|
27
|
+
shortcut: str = ""
|
|
28
|
+
category: str = "general"
|
|
29
|
+
action: Callable | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Default palette commands
|
|
33
|
+
DEFAULT_PALETTE_COMMANDS: list[PaletteCommand] = [
|
|
34
|
+
# Agent commands
|
|
35
|
+
PaletteCommand(
|
|
36
|
+
"connect_agent",
|
|
37
|
+
"Connect to Agent",
|
|
38
|
+
"Connect to an AI coding agent",
|
|
39
|
+
"🤖",
|
|
40
|
+
"Ctrl+A",
|
|
41
|
+
"agents",
|
|
42
|
+
),
|
|
43
|
+
PaletteCommand(
|
|
44
|
+
"agent_store", "Agent Store", "Browse available agents", "🛍️", "Ctrl+S", "agents"
|
|
45
|
+
),
|
|
46
|
+
PaletteCommand(
|
|
47
|
+
"disconnect", "Disconnect", "Disconnect from current agent", "🔌", "Ctrl+D", "agents"
|
|
48
|
+
),
|
|
49
|
+
# Role commands
|
|
50
|
+
PaletteCommand(
|
|
51
|
+
"dev_mode", "Development Mode", "Switch to dev.fullstack role", "💻", "", "roles"
|
|
52
|
+
),
|
|
53
|
+
# File commands
|
|
54
|
+
PaletteCommand("open_file", "Open File", "Open a file from project", "📁", "Ctrl+O", "files"),
|
|
55
|
+
PaletteCommand("find_file", "Find File", "Fuzzy search for files", "🔍", "Ctrl+F", "files"),
|
|
56
|
+
PaletteCommand("recent_files", "Recent Files", "Show recently opened files", "📋", "", "files"),
|
|
57
|
+
PaletteCommand("bookmarks", "Bookmarks", "Manage file bookmarks", "🔖", "", "files"),
|
|
58
|
+
# Workflow commands
|
|
59
|
+
PaletteCommand(
|
|
60
|
+
"handoff", "Handoff Work", "Hand off work to another role", "🤝", "Ctrl+H", "workflow"
|
|
61
|
+
),
|
|
62
|
+
PaletteCommand(
|
|
63
|
+
"context", "View Context", "Show current work context", "📋", "Ctrl+I", "workflow"
|
|
64
|
+
),
|
|
65
|
+
PaletteCommand("approve", "Approve Work", "Approve work for deployment", "✅", "", "workflow"),
|
|
66
|
+
PaletteCommand("sessions", "View Sessions", "Show pending handoffs", "📂", "", "workflow"),
|
|
67
|
+
# System commands
|
|
68
|
+
PaletteCommand("settings", "Settings", "Open settings", "⚙️", "Ctrl+,", "system"),
|
|
69
|
+
PaletteCommand("help", "Help", "Show help documentation", "❓", "?", "system"),
|
|
70
|
+
PaletteCommand("exit", "Exit", "Exit SuperQode", "🚪", "Ctrl+C", "system"),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PaletteItem(Widget):
|
|
75
|
+
"""A single command palette item - high contrast design."""
|
|
76
|
+
|
|
77
|
+
DEFAULT_CSS = """
|
|
78
|
+
PaletteItem {
|
|
79
|
+
height: 3;
|
|
80
|
+
padding: 0 1;
|
|
81
|
+
layout: horizontal;
|
|
82
|
+
background: #0a0a0a;
|
|
83
|
+
border-bottom: solid #222222;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
PaletteItem:hover {
|
|
87
|
+
background: #1a3a5a;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
PaletteItem.selected {
|
|
91
|
+
background: #00aaff;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
PaletteItem .icon {
|
|
95
|
+
width: 4;
|
|
96
|
+
height: 3;
|
|
97
|
+
content-align: center middle;
|
|
98
|
+
color: #ffffff;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
PaletteItem .content {
|
|
102
|
+
height: 3;
|
|
103
|
+
padding-left: 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
PaletteItem .label {
|
|
107
|
+
text-style: bold;
|
|
108
|
+
color: #ffffff;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
PaletteItem.selected .label {
|
|
112
|
+
color: #000000;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
PaletteItem .description {
|
|
116
|
+
color: #aaaaaa;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
PaletteItem.selected .description {
|
|
120
|
+
color: #000000;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
PaletteItem .shortcut {
|
|
124
|
+
dock: right;
|
|
125
|
+
color: #00ff00;
|
|
126
|
+
text-style: bold;
|
|
127
|
+
width: auto;
|
|
128
|
+
padding-right: 1;
|
|
129
|
+
content-align: center middle;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
PaletteItem.selected .shortcut {
|
|
133
|
+
color: #004400;
|
|
134
|
+
}
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
class Selected(Message):
|
|
138
|
+
"""Message sent when item is selected."""
|
|
139
|
+
|
|
140
|
+
def __init__(self, command: PaletteCommand) -> None:
|
|
141
|
+
self.command = command
|
|
142
|
+
super().__init__()
|
|
143
|
+
|
|
144
|
+
selected: reactive[bool] = reactive(False)
|
|
145
|
+
|
|
146
|
+
def __init__(self, command: PaletteCommand, **kwargs) -> None:
|
|
147
|
+
super().__init__(**kwargs)
|
|
148
|
+
self.command = command
|
|
149
|
+
|
|
150
|
+
def compose(self) -> ComposeResult:
|
|
151
|
+
yield Static(self.command.icon, classes="icon")
|
|
152
|
+
with Vertical(classes="content"):
|
|
153
|
+
yield Static(self.command.label, classes="label")
|
|
154
|
+
yield Static(self.command.description, classes="description")
|
|
155
|
+
if self.command.shortcut:
|
|
156
|
+
yield Static(self.command.shortcut, classes="shortcut")
|
|
157
|
+
|
|
158
|
+
def watch_selected(self, selected: bool) -> None:
|
|
159
|
+
self.set_class(selected, "selected")
|
|
160
|
+
|
|
161
|
+
def on_click(self) -> None:
|
|
162
|
+
self.post_message(self.Selected(self.command))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class CommandPalette(Widget):
|
|
166
|
+
"""
|
|
167
|
+
Command palette overlay (Ctrl+K) - High contrast, accessible design.
|
|
168
|
+
|
|
169
|
+
Provides fuzzy-searchable access to all commands.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
DEFAULT_CSS = """
|
|
173
|
+
CommandPalette {
|
|
174
|
+
layer: overlay;
|
|
175
|
+
align: center top;
|
|
176
|
+
margin-top: 3;
|
|
177
|
+
width: 70;
|
|
178
|
+
height: auto;
|
|
179
|
+
max-height: 25;
|
|
180
|
+
background: #000000;
|
|
181
|
+
border: double #00ffff;
|
|
182
|
+
display: none;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
CommandPalette.show-palette {
|
|
186
|
+
display: block;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
CommandPalette #palette-title-bar {
|
|
190
|
+
height: 3;
|
|
191
|
+
background: #001a33;
|
|
192
|
+
padding: 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
CommandPalette #palette-title {
|
|
196
|
+
text-style: bold;
|
|
197
|
+
color: #00ffff;
|
|
198
|
+
text-align: center;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
CommandPalette #palette-subtitle {
|
|
202
|
+
color: #888888;
|
|
203
|
+
text-align: center;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
CommandPalette #palette-search-container {
|
|
207
|
+
height: 3;
|
|
208
|
+
padding: 0 1;
|
|
209
|
+
background: #0a0a0a;
|
|
210
|
+
border-bottom: solid #333333;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
CommandPalette #palette-search {
|
|
214
|
+
width: 100%;
|
|
215
|
+
background: #1a1a1a;
|
|
216
|
+
border: tall #00ffff;
|
|
217
|
+
color: #ffffff;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
CommandPalette #palette-search:focus {
|
|
221
|
+
border: tall #00ff00;
|
|
222
|
+
background: #0a1a0a;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
CommandPalette #palette-results {
|
|
226
|
+
height: auto;
|
|
227
|
+
max-height: 16;
|
|
228
|
+
background: #0a0a0a;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
CommandPalette .no-results {
|
|
232
|
+
padding: 2;
|
|
233
|
+
color: #ffff00;
|
|
234
|
+
text-style: bold;
|
|
235
|
+
text-align: center;
|
|
236
|
+
background: #1a1a00;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
CommandPalette #palette-footer {
|
|
240
|
+
height: 2;
|
|
241
|
+
background: #1a1a1a;
|
|
242
|
+
color: #00ff00;
|
|
243
|
+
padding: 0 1;
|
|
244
|
+
border-top: solid #333333;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
CommandPalette #footer-hints {
|
|
248
|
+
text-align: center;
|
|
249
|
+
color: #00ff00;
|
|
250
|
+
}
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
class CommandSelected(Message):
|
|
254
|
+
"""Message sent when a command is selected."""
|
|
255
|
+
|
|
256
|
+
def __init__(self, command: PaletteCommand) -> None:
|
|
257
|
+
self.command = command
|
|
258
|
+
super().__init__()
|
|
259
|
+
|
|
260
|
+
class Dismissed(Message):
|
|
261
|
+
"""Message sent when palette is dismissed."""
|
|
262
|
+
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
# State
|
|
266
|
+
is_visible: reactive[bool] = reactive(False)
|
|
267
|
+
search_text: reactive[str] = reactive("")
|
|
268
|
+
selected_index: reactive[int] = reactive(0)
|
|
269
|
+
|
|
270
|
+
def __init__(
|
|
271
|
+
self,
|
|
272
|
+
commands: list[PaletteCommand] | None = None,
|
|
273
|
+
**kwargs,
|
|
274
|
+
) -> None:
|
|
275
|
+
super().__init__(**kwargs)
|
|
276
|
+
self.commands = commands or DEFAULT_PALETTE_COMMANDS
|
|
277
|
+
self.filtered_commands: list[PaletteCommand] = []
|
|
278
|
+
self.fuzzy = FuzzySearch()
|
|
279
|
+
|
|
280
|
+
def compose(self) -> ComposeResult:
|
|
281
|
+
with Vertical(id="palette-title-bar"):
|
|
282
|
+
yield Static("🔍 COMMAND PALETTE", id="palette-title")
|
|
283
|
+
yield Static("Type to search commands", id="palette-subtitle")
|
|
284
|
+
with Vertical(id="palette-search-container"):
|
|
285
|
+
yield Input(placeholder="Search commands...", id="palette-search")
|
|
286
|
+
yield VerticalScroll(id="palette-results")
|
|
287
|
+
with Vertical(id="palette-footer"):
|
|
288
|
+
yield Static("↑↓ Navigate │ Enter Select │ Esc Close", id="footer-hints")
|
|
289
|
+
|
|
290
|
+
def on_mount(self) -> None:
|
|
291
|
+
"""Initialize on mount."""
|
|
292
|
+
self._update_filtered_commands()
|
|
293
|
+
|
|
294
|
+
def show(self) -> None:
|
|
295
|
+
"""Show command palette."""
|
|
296
|
+
self.search_text = ""
|
|
297
|
+
self.selected_index = 0
|
|
298
|
+
self.is_visible = True
|
|
299
|
+
self.add_class("show-palette")
|
|
300
|
+
self._update_filtered_commands()
|
|
301
|
+
|
|
302
|
+
# Focus search input
|
|
303
|
+
search_input = self.query_one("#palette-search", Input)
|
|
304
|
+
search_input.value = ""
|
|
305
|
+
search_input.focus()
|
|
306
|
+
|
|
307
|
+
def hide(self) -> None:
|
|
308
|
+
"""Hide command palette."""
|
|
309
|
+
self.is_visible = False
|
|
310
|
+
self.remove_class("show-palette")
|
|
311
|
+
self.post_message(self.Dismissed())
|
|
312
|
+
|
|
313
|
+
def toggle(self) -> None:
|
|
314
|
+
"""Toggle palette visibility."""
|
|
315
|
+
if self.is_visible:
|
|
316
|
+
self.hide()
|
|
317
|
+
else:
|
|
318
|
+
self.show()
|
|
319
|
+
|
|
320
|
+
@on(Input.Changed, "#palette-search")
|
|
321
|
+
def on_search_changed(self, event: Input.Changed) -> None:
|
|
322
|
+
"""Handle search input changes."""
|
|
323
|
+
self.search_text = event.value
|
|
324
|
+
self.selected_index = 0
|
|
325
|
+
self._update_filtered_commands()
|
|
326
|
+
|
|
327
|
+
@on(Input.Submitted, "#palette-search")
|
|
328
|
+
def on_search_submitted(self, event: Input.Submitted) -> None:
|
|
329
|
+
"""Handle search submission."""
|
|
330
|
+
self.select_current()
|
|
331
|
+
|
|
332
|
+
def _update_filtered_commands(self) -> None:
|
|
333
|
+
"""Update filtered commands based on search text."""
|
|
334
|
+
if self.search_text:
|
|
335
|
+
# Build searchable items (search both label and description)
|
|
336
|
+
items = [(f"{cmd.label} {cmd.description}", cmd) for cmd in self.commands]
|
|
337
|
+
results = self.fuzzy.search_with_data(self.search_text, items, max_results=10)
|
|
338
|
+
self.filtered_commands = [cmd for _, cmd in results]
|
|
339
|
+
else:
|
|
340
|
+
self.filtered_commands = self.commands[:10]
|
|
341
|
+
|
|
342
|
+
self._render_commands()
|
|
343
|
+
|
|
344
|
+
def _render_commands(self) -> None:
|
|
345
|
+
"""Render filtered commands."""
|
|
346
|
+
container = self.query_one("#palette-results", VerticalScroll)
|
|
347
|
+
container.remove_children()
|
|
348
|
+
|
|
349
|
+
if not self.filtered_commands:
|
|
350
|
+
container.mount(Static("No matching commands found", classes="no-results"))
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
for i, cmd in enumerate(self.filtered_commands):
|
|
354
|
+
item = PaletteItem(cmd, id=f"palette-item-{i}")
|
|
355
|
+
item.selected = i == self.selected_index
|
|
356
|
+
container.mount(item)
|
|
357
|
+
|
|
358
|
+
def _update_selection(self) -> None:
|
|
359
|
+
"""Update visual selection state."""
|
|
360
|
+
for i, item in enumerate(self.query("#palette-results PaletteItem")):
|
|
361
|
+
if isinstance(item, PaletteItem):
|
|
362
|
+
item.selected = i == self.selected_index
|
|
363
|
+
|
|
364
|
+
def move_selection(self, delta: int) -> None:
|
|
365
|
+
"""Move selection up or down."""
|
|
366
|
+
if not self.filtered_commands:
|
|
367
|
+
return
|
|
368
|
+
new_index = (self.selected_index + delta) % len(self.filtered_commands)
|
|
369
|
+
self.selected_index = new_index
|
|
370
|
+
self._update_selection()
|
|
371
|
+
|
|
372
|
+
# Scroll to make selection visible
|
|
373
|
+
try:
|
|
374
|
+
container = self.query_one("#palette-results", VerticalScroll)
|
|
375
|
+
selected_item = self.query_one(f"#palette-item-{self.selected_index}")
|
|
376
|
+
if selected_item:
|
|
377
|
+
container.scroll_visible(selected_item)
|
|
378
|
+
except Exception:
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
def select_current(self) -> PaletteCommand | None:
|
|
382
|
+
"""Select current command."""
|
|
383
|
+
if self.filtered_commands and 0 <= self.selected_index < len(self.filtered_commands):
|
|
384
|
+
cmd = self.filtered_commands[self.selected_index]
|
|
385
|
+
self.post_message(self.CommandSelected(cmd))
|
|
386
|
+
self.hide()
|
|
387
|
+
return cmd
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
def on_key(self, event) -> None:
|
|
391
|
+
"""Handle key events."""
|
|
392
|
+
if not self.is_visible:
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
if event.key == "escape":
|
|
396
|
+
self.hide()
|
|
397
|
+
event.stop()
|
|
398
|
+
elif event.key == "up":
|
|
399
|
+
self.move_selection(-1)
|
|
400
|
+
event.stop()
|
|
401
|
+
elif event.key == "down":
|
|
402
|
+
self.move_selection(1)
|
|
403
|
+
event.stop()
|
|
404
|
+
elif event.key == "enter":
|
|
405
|
+
self.select_current()
|
|
406
|
+
event.stop()
|
|
407
|
+
|
|
408
|
+
@on(PaletteItem.Selected)
|
|
409
|
+
def on_item_selected(self, event: PaletteItem.Selected) -> None:
|
|
410
|
+
"""Handle item selection via click."""
|
|
411
|
+
self.post_message(self.CommandSelected(event.command))
|
|
412
|
+
self.hide()
|