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,356 @@
|
|
|
1
|
+
"""Slash command completion overlay widget - 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 Static
|
|
15
|
+
|
|
16
|
+
from superqode.utils.fuzzy import FuzzySearch
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class SlashCommand:
|
|
21
|
+
"""A slash command definition."""
|
|
22
|
+
|
|
23
|
+
command: str # e.g., "/handoff"
|
|
24
|
+
description: str # e.g., "Hand off work to another role"
|
|
25
|
+
shortcut: str = "" # e.g., "Ctrl+H"
|
|
26
|
+
category: str = "general" # For grouping
|
|
27
|
+
action: Callable | None = None # Optional action callback
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Default slash commands
|
|
31
|
+
DEFAULT_COMMANDS: list[SlashCommand] = [
|
|
32
|
+
# Role commands
|
|
33
|
+
SlashCommand("/dev", "Switch to development mode", category="roles"),
|
|
34
|
+
SlashCommand("/dev fullstack", "Start full-stack development", category="roles"),
|
|
35
|
+
# Agent commands
|
|
36
|
+
SlashCommand("/agents", "List available agents", "Ctrl+A", category="agents"),
|
|
37
|
+
SlashCommand("/store", "Open agent marketplace", "Ctrl+S", category="agents"),
|
|
38
|
+
SlashCommand("/agents store", "Browse agent marketplace", category="agents"),
|
|
39
|
+
SlashCommand("/agents connect", "Connect to an agent", category="agents"),
|
|
40
|
+
SlashCommand("/agents install", "Install an agent", category="agents"),
|
|
41
|
+
# File commands
|
|
42
|
+
SlashCommand("/files", "Show project files", "Ctrl+O", category="files"),
|
|
43
|
+
SlashCommand("/find", "Fuzzy search files", "Ctrl+F", category="files"),
|
|
44
|
+
SlashCommand("/recent", "Show recent files", category="files"),
|
|
45
|
+
SlashCommand("/open", "Open a file or bookmark", category="files"),
|
|
46
|
+
SlashCommand("/bookmark", "Manage bookmarks", category="files"),
|
|
47
|
+
# Workflow commands
|
|
48
|
+
SlashCommand("/handoff", "Hand off work to another role", "Ctrl+H", category="workflow"),
|
|
49
|
+
SlashCommand("/context", "View/update work context", "Ctrl+I", category="workflow"),
|
|
50
|
+
SlashCommand("/approve", "Approve work for deployment", category="workflow"),
|
|
51
|
+
# System commands
|
|
52
|
+
SlashCommand("/settings", "Open settings", "Ctrl+,", category="system"),
|
|
53
|
+
SlashCommand("/help", "Show help", "?", category="system"),
|
|
54
|
+
SlashCommand("/disconnect", "Disconnect from agent", "Ctrl+D", category="system"),
|
|
55
|
+
SlashCommand("/exit", "Exit SuperQode", "Ctrl+C", category="system"),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SlashCompleteItem(Widget):
|
|
60
|
+
"""A single slash command completion item - high contrast design."""
|
|
61
|
+
|
|
62
|
+
DEFAULT_CSS = """
|
|
63
|
+
SlashCompleteItem {
|
|
64
|
+
height: 2;
|
|
65
|
+
padding: 0 1;
|
|
66
|
+
layout: horizontal;
|
|
67
|
+
background: #0a0a0a;
|
|
68
|
+
border-bottom: solid #1a1a1a;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
SlashCompleteItem:hover {
|
|
72
|
+
background: #1a3a5a;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
SlashCompleteItem.selected {
|
|
76
|
+
background: #00ffff;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
SlashCompleteItem .command {
|
|
80
|
+
color: #ffff00;
|
|
81
|
+
text-style: bold;
|
|
82
|
+
min-width: 24;
|
|
83
|
+
width: 24;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
SlashCompleteItem.selected .command {
|
|
87
|
+
color: #000000;
|
|
88
|
+
text-style: bold;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
SlashCompleteItem .description {
|
|
92
|
+
color: #ffffff;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
SlashCompleteItem.selected .description {
|
|
96
|
+
color: #000000;
|
|
97
|
+
text-style: bold;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
SlashCompleteItem .shortcut {
|
|
101
|
+
dock: right;
|
|
102
|
+
color: #00ff00;
|
|
103
|
+
text-style: bold;
|
|
104
|
+
min-width: 10;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
SlashCompleteItem.selected .shortcut {
|
|
108
|
+
color: #004400;
|
|
109
|
+
}
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
class Click(Message):
|
|
113
|
+
"""Message sent when item is clicked."""
|
|
114
|
+
|
|
115
|
+
def __init__(self, widget: "SlashCompleteItem") -> None:
|
|
116
|
+
self.widget = widget
|
|
117
|
+
super().__init__()
|
|
118
|
+
|
|
119
|
+
selected: reactive[bool] = reactive(False)
|
|
120
|
+
|
|
121
|
+
def __init__(self, command: SlashCommand, **kwargs) -> None:
|
|
122
|
+
super().__init__(**kwargs)
|
|
123
|
+
self.command = command
|
|
124
|
+
|
|
125
|
+
def compose(self) -> ComposeResult:
|
|
126
|
+
yield Static(self.command.command, classes="command")
|
|
127
|
+
yield Static(self.command.description, classes="description")
|
|
128
|
+
if self.command.shortcut:
|
|
129
|
+
yield Static(self.command.shortcut, classes="shortcut")
|
|
130
|
+
|
|
131
|
+
def watch_selected(self, selected: bool) -> None:
|
|
132
|
+
self.set_class(selected, "selected")
|
|
133
|
+
|
|
134
|
+
def on_click(self) -> None:
|
|
135
|
+
self.post_message(self.Click(self))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class SlashComplete(Widget):
|
|
139
|
+
"""
|
|
140
|
+
Slash command completion overlay - High contrast, accessible design.
|
|
141
|
+
|
|
142
|
+
Shows when user types "/" and provides fuzzy-filtered command suggestions.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
DEFAULT_CSS = """
|
|
146
|
+
SlashComplete {
|
|
147
|
+
layer: overlay;
|
|
148
|
+
dock: bottom;
|
|
149
|
+
height: auto;
|
|
150
|
+
max-height: 16;
|
|
151
|
+
margin: 0 2 4 2;
|
|
152
|
+
background: #000000;
|
|
153
|
+
border: double #00ffff;
|
|
154
|
+
display: none;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
SlashComplete.visible {
|
|
158
|
+
display: block;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
SlashComplete #slash-header {
|
|
162
|
+
height: 2;
|
|
163
|
+
background: #001a33;
|
|
164
|
+
color: #00ffff;
|
|
165
|
+
padding: 0 1;
|
|
166
|
+
text-style: bold;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
SlashComplete #slash-title {
|
|
170
|
+
color: #00ffff;
|
|
171
|
+
text-style: bold;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
SlashComplete #slash-hint {
|
|
175
|
+
color: #888888;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
SlashComplete #slash-list {
|
|
179
|
+
height: auto;
|
|
180
|
+
max-height: 12;
|
|
181
|
+
background: #0a0a0a;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
SlashComplete .no-results {
|
|
185
|
+
padding: 1;
|
|
186
|
+
color: #ffff00;
|
|
187
|
+
text-style: bold;
|
|
188
|
+
text-align: center;
|
|
189
|
+
background: #1a1a00;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
SlashComplete #slash-footer {
|
|
193
|
+
height: 1;
|
|
194
|
+
background: #1a1a1a;
|
|
195
|
+
color: #00ff00;
|
|
196
|
+
padding: 0 1;
|
|
197
|
+
text-align: center;
|
|
198
|
+
border-top: solid #333333;
|
|
199
|
+
}
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
class CommandSelected(Message):
|
|
203
|
+
"""Message sent when a command is selected."""
|
|
204
|
+
|
|
205
|
+
def __init__(self, command: SlashCommand) -> None:
|
|
206
|
+
self.command = command
|
|
207
|
+
super().__init__()
|
|
208
|
+
|
|
209
|
+
class Dismissed(Message):
|
|
210
|
+
"""Message sent when the overlay is dismissed."""
|
|
211
|
+
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
# State
|
|
215
|
+
is_visible: reactive[bool] = reactive(False)
|
|
216
|
+
search_query: reactive[str] = reactive("")
|
|
217
|
+
selected_index: reactive[int] = reactive(0)
|
|
218
|
+
|
|
219
|
+
def __init__(
|
|
220
|
+
self,
|
|
221
|
+
commands: list[SlashCommand] | None = None,
|
|
222
|
+
**kwargs,
|
|
223
|
+
) -> None:
|
|
224
|
+
super().__init__(**kwargs)
|
|
225
|
+
self.commands = commands or DEFAULT_COMMANDS
|
|
226
|
+
self.filtered_commands: list[SlashCommand] = []
|
|
227
|
+
self.fuzzy = FuzzySearch()
|
|
228
|
+
self._render_counter = 0 # Unique ID counter to prevent duplicates
|
|
229
|
+
|
|
230
|
+
def compose(self) -> ComposeResult:
|
|
231
|
+
with Vertical(id="slash-header"):
|
|
232
|
+
yield Static("⚡ SLASH COMMANDS", id="slash-title")
|
|
233
|
+
yield Static("Type to filter commands", id="slash-hint")
|
|
234
|
+
yield VerticalScroll(id="slash-list")
|
|
235
|
+
yield Static("↑↓ Navigate │ Enter Select │ Esc Close", id="slash-footer")
|
|
236
|
+
|
|
237
|
+
def on_mount(self) -> None:
|
|
238
|
+
"""Initialize on mount."""
|
|
239
|
+
self._update_filtered_commands()
|
|
240
|
+
|
|
241
|
+
def show(self, initial_query: str = "/") -> None:
|
|
242
|
+
"""Show slash completion overlay."""
|
|
243
|
+
self.selected_index = 0
|
|
244
|
+
self.is_visible = True
|
|
245
|
+
self.add_class("visible")
|
|
246
|
+
# Force update if query is the same (watcher won't trigger on same value)
|
|
247
|
+
if self.search_query == initial_query:
|
|
248
|
+
self._update_filtered_commands()
|
|
249
|
+
else:
|
|
250
|
+
# Setting search_query triggers watch_search_query which calls _update_filtered_commands
|
|
251
|
+
self.search_query = initial_query
|
|
252
|
+
self.focus()
|
|
253
|
+
|
|
254
|
+
def hide(self) -> None:
|
|
255
|
+
"""Hide slash completion overlay."""
|
|
256
|
+
self.is_visible = False
|
|
257
|
+
self.remove_class("visible")
|
|
258
|
+
self.post_message(self.Dismissed())
|
|
259
|
+
|
|
260
|
+
def watch_search_query(self, search_query: str) -> None:
|
|
261
|
+
"""React to search query changes."""
|
|
262
|
+
if not self.is_mounted:
|
|
263
|
+
return
|
|
264
|
+
self.selected_index = 0
|
|
265
|
+
self._update_filtered_commands()
|
|
266
|
+
|
|
267
|
+
def watch_selected_index(self, index: int) -> None:
|
|
268
|
+
"""React to selection changes."""
|
|
269
|
+
if not self.is_mounted:
|
|
270
|
+
return
|
|
271
|
+
self._update_selection()
|
|
272
|
+
|
|
273
|
+
def _update_filtered_commands(self) -> None:
|
|
274
|
+
"""Update the filtered command list based on query."""
|
|
275
|
+
# Remove leading "/" for search
|
|
276
|
+
search_text = self.search_query.lstrip("/")
|
|
277
|
+
|
|
278
|
+
# Build searchable items
|
|
279
|
+
items = [(cmd.command.lstrip("/"), cmd) for cmd in self.commands]
|
|
280
|
+
|
|
281
|
+
if search_text:
|
|
282
|
+
# Fuzzy search
|
|
283
|
+
results = self.fuzzy.search_with_data(
|
|
284
|
+
search_text,
|
|
285
|
+
items,
|
|
286
|
+
max_results=10,
|
|
287
|
+
)
|
|
288
|
+
self.filtered_commands = [cmd for _, cmd in results]
|
|
289
|
+
else:
|
|
290
|
+
# Show all commands (limited)
|
|
291
|
+
self.filtered_commands = self.commands[:10]
|
|
292
|
+
|
|
293
|
+
self._render_commands()
|
|
294
|
+
|
|
295
|
+
def _render_commands(self) -> None:
|
|
296
|
+
"""Render the filtered commands in the list."""
|
|
297
|
+
self._render_counter += 1
|
|
298
|
+
render_id = self._render_counter
|
|
299
|
+
|
|
300
|
+
container = self.query_one("#slash-list", VerticalScroll)
|
|
301
|
+
container.remove_children()
|
|
302
|
+
|
|
303
|
+
if not self.filtered_commands:
|
|
304
|
+
container.mount(Static("No matching commands found", classes="no-results"))
|
|
305
|
+
return
|
|
306
|
+
|
|
307
|
+
for i, cmd in enumerate(self.filtered_commands):
|
|
308
|
+
# Use render counter in ID to ensure uniqueness across renders
|
|
309
|
+
item = SlashCompleteItem(cmd, id=f"slash-item-{render_id}-{i}")
|
|
310
|
+
item.selected = i == self.selected_index
|
|
311
|
+
container.mount(item)
|
|
312
|
+
|
|
313
|
+
def _update_selection(self) -> None:
|
|
314
|
+
"""Update the visual selection state."""
|
|
315
|
+
for i, item in enumerate(self.query("#slash-list SlashCompleteItem")):
|
|
316
|
+
if isinstance(item, SlashCompleteItem):
|
|
317
|
+
item.selected = i == self.selected_index
|
|
318
|
+
|
|
319
|
+
def move_selection(self, delta: int) -> None:
|
|
320
|
+
"""Move selection up or down."""
|
|
321
|
+
if not self.filtered_commands:
|
|
322
|
+
return
|
|
323
|
+
new_index = (self.selected_index + delta) % len(self.filtered_commands)
|
|
324
|
+
self.selected_index = new_index
|
|
325
|
+
|
|
326
|
+
# Scroll to make selection visible
|
|
327
|
+
try:
|
|
328
|
+
container = self.query_one("#slash-list", VerticalScroll)
|
|
329
|
+
items = list(self.query("#slash-list SlashCompleteItem"))
|
|
330
|
+
if 0 <= self.selected_index < len(items):
|
|
331
|
+
container.scroll_visible(items[self.selected_index])
|
|
332
|
+
except Exception:
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
def select_current(self) -> SlashCommand | None:
|
|
336
|
+
"""Select the currently highlighted command."""
|
|
337
|
+
if self.filtered_commands and 0 <= self.selected_index < len(self.filtered_commands):
|
|
338
|
+
cmd = self.filtered_commands[self.selected_index]
|
|
339
|
+
self.post_message(self.CommandSelected(cmd))
|
|
340
|
+
self.hide()
|
|
341
|
+
return cmd
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
def update_query(self, query: str) -> None:
|
|
345
|
+
"""Update the search query."""
|
|
346
|
+
self.search_query = query
|
|
347
|
+
|
|
348
|
+
@on(SlashCompleteItem.Click)
|
|
349
|
+
def on_item_click(self, event: SlashCompleteItem.Click) -> None:
|
|
350
|
+
"""Handle item click."""
|
|
351
|
+
# Find the clicked item's index
|
|
352
|
+
for i, item in enumerate(self.query("#slash-list SlashCompleteItem")):
|
|
353
|
+
if item is event.widget:
|
|
354
|
+
self.selected_index = i
|
|
355
|
+
self.select_current()
|
|
356
|
+
break
|