shikiru-cli 0.2.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.
- shikiru/__init__.py +1 -0
- shikiru/__main__.py +8 -0
- shikiru/agents/__init__.py +3 -0
- shikiru/agents/contracts.py +590 -0
- shikiru/agents/registry.py +87 -0
- shikiru/agents/sdk/__init__.py +17 -0
- shikiru/agents/sdk/reset_time.py +137 -0
- shikiru/agents/sdk/turn.py +335 -0
- shikiru/agents/settings.py +151 -0
- shikiru/agents/testing/__init__.py +26 -0
- shikiru/agents/testing/contract_suite.py +653 -0
- shikiru/agents/testing/resume.py +15 -0
- shikiru/bootstrap.py +1277 -0
- shikiru/cli/__init__.py +1 -0
- shikiru/cli/app.py +770 -0
- shikiru/cli/context.py +63 -0
- shikiru/cli/render.py +1475 -0
- shikiru/cli/report.py +156 -0
- shikiru/cli/session_prompt.py +63 -0
- shikiru/cli/view.py +131 -0
- shikiru/clock.py +41 -0
- shikiru/config/__init__.py +8 -0
- shikiru/config/defaults.toml +131 -0
- shikiru/config/loader.py +178 -0
- shikiru/config/settings.py +392 -0
- shikiru/dashboard/__init__.py +3 -0
- shikiru/dashboard/actions_list.py +799 -0
- shikiru/dashboard/activity.py +150 -0
- shikiru/dashboard/app.py +1765 -0
- shikiru/dashboard/blocker_evidence.py +122 -0
- shikiru/dashboard/blocker_rows.py +84 -0
- shikiru/dashboard/briefing_view.py +871 -0
- shikiru/dashboard/contract.py +1086 -0
- shikiru/dashboard/covered.py +32 -0
- shikiru/dashboard/detail.py +1209 -0
- shikiru/dashboard/detail_fold.py +244 -0
- shikiru/dashboard/detail_hint.py +68 -0
- shikiru/dashboard/details_list_view.py +150 -0
- shikiru/dashboard/diagnostics.py +80 -0
- shikiru/dashboard/diagnostics_sections.py +186 -0
- shikiru/dashboard/evidence_table.py +123 -0
- shikiru/dashboard/extra_details.py +214 -0
- shikiru/dashboard/fault_guidance.py +216 -0
- shikiru/dashboard/fold_bodies.py +276 -0
- shikiru/dashboard/help_keys.py +166 -0
- shikiru/dashboard/help_screen.py +204 -0
- shikiru/dashboard/interaction.py +17 -0
- shikiru/dashboard/ladder_editor.py +544 -0
- shikiru/dashboard/live_settings.py +62 -0
- shikiru/dashboard/motion.py +368 -0
- shikiru/dashboard/orchestration_fields.py +201 -0
- shikiru/dashboard/palette.py +1606 -0
- shikiru/dashboard/palette_app.py +89 -0
- shikiru/dashboard/palette_drafts.py +33 -0
- shikiru/dashboard/resolution.py +573 -0
- shikiru/dashboard/resolution_view.py +302 -0
- shikiru/dashboard/row_list.py +256 -0
- shikiru/dashboard/section_rows.py +173 -0
- shikiru/dashboard/services.py +665 -0
- shikiru/dashboard/session_history.py +402 -0
- shikiru/dashboard/shikiru_init_ascii_art.txt +40 -0
- shikiru/dashboard/task_picker.py +358 -0
- shikiru/dashboard/tick.py +59 -0
- shikiru/dashboard/timeline_filter.py +129 -0
- shikiru/dashboard/timeline_section.py +385 -0
- shikiru/dashboard/usage_body.py +269 -0
- shikiru/dashboard/views.py +2314 -0
- shikiru/dashboard/wizard.py +1389 -0
- shikiru/domain/__init__.py +1 -0
- shikiru/domain/acceptance.py +130 -0
- shikiru/domain/activity.py +28 -0
- shikiru/domain/attempts.py +333 -0
- shikiru/domain/briefing.py +257 -0
- shikiru/domain/commands.py +219 -0
- shikiru/domain/decisions.py +231 -0
- shikiru/domain/docker.py +262 -0
- shikiru/domain/engine_uptime.py +87 -0
- shikiru/domain/escalation.py +194 -0
- shikiru/domain/escalation_evidence.py +186 -0
- shikiru/domain/escalation_panel.py +144 -0
- shikiru/domain/events.py +71 -0
- shikiru/domain/evidence_export.py +89 -0
- shikiru/domain/faults.py +86 -0
- shikiru/domain/feedback.py +180 -0
- shikiru/domain/feedback_rounds.py +172 -0
- shikiru/domain/fencing.py +31 -0
- shikiru/domain/ladder.py +102 -0
- shikiru/domain/ladder_text.py +44 -0
- shikiru/domain/levels.py +44 -0
- shikiru/domain/motion.py +199 -0
- shikiru/domain/packet_evidence.py +377 -0
- shikiru/domain/pushes.py +47 -0
- shikiru/domain/quota_blocks.py +44 -0
- shikiru/domain/quota_waits.py +219 -0
- shikiru/domain/recovery.py +194 -0
- shikiru/domain/reports.py +498 -0
- shikiru/domain/results.py +217 -0
- shikiru/domain/runs.py +85 -0
- shikiru/domain/scheduling.py +231 -0
- shikiru/domain/status_tasks.py +40 -0
- shikiru/domain/steps.py +325 -0
- shikiru/domain/tasks.py +174 -0
- shikiru/domain/timeline.py +208 -0
- shikiru/domain/timeline_phrases.py +271 -0
- shikiru/domain/versions.py +18 -0
- shikiru/domain/workflow.py +372 -0
- shikiru/engine/__init__.py +0 -0
- shikiru/engine/acceptance.py +90 -0
- shikiru/engine/agents.py +61 -0
- shikiru/engine/cosmetic_decorator.py +99 -0
- shikiru/engine/daemon.py +409 -0
- shikiru/engine/dependency.py +37 -0
- shikiru/engine/escalation_filing.py +110 -0
- shikiru/engine/escalations.py +401 -0
- shikiru/engine/events.py +285 -0
- shikiru/engine/evidence.py +81 -0
- shikiru/engine/fault_reporter.py +144 -0
- shikiru/engine/feedback.py +577 -0
- shikiru/engine/feedback_audit.py +80 -0
- shikiru/engine/feedback_intake.py +95 -0
- shikiru/engine/feedback_snapshot.py +148 -0
- shikiru/engine/flow.py +87 -0
- shikiru/engine/handlers/__init__.py +33 -0
- shikiru/engine/handlers/base.py +148 -0
- shikiru/engine/handlers/engine.py +24 -0
- shikiru/engine/handlers/escalations.py +174 -0
- shikiru/engine/handlers/operator.py +81 -0
- shikiru/engine/handlers/tasks.py +237 -0
- shikiru/engine/history.py +128 -0
- shikiru/engine/integration.py +1234 -0
- shikiru/engine/integration_turns.py +337 -0
- shikiru/engine/layout_guard.py +362 -0
- shikiru/engine/lifecycle.py +60 -0
- shikiru/engine/monitor.py +317 -0
- shikiru/engine/notification_delivery.py +124 -0
- shikiru/engine/notification_feed.py +57 -0
- shikiru/engine/notifier.py +91 -0
- shikiru/engine/pull_request_closing.py +117 -0
- shikiru/engine/push_service.py +177 -0
- shikiru/engine/quota_probe_threads.py +79 -0
- shikiru/engine/quota_reconciler.py +210 -0
- shikiru/engine/quota_schedule.py +24 -0
- shikiru/engine/quota_waits.py +102 -0
- shikiru/engine/reconcile.py +91 -0
- shikiru/engine/recovery.py +284 -0
- shikiru/engine/retention.py +102 -0
- shikiru/engine/root_preparation.py +86 -0
- shikiru/engine/scheduler.py +320 -0
- shikiru/engine/session_guard.py +46 -0
- shikiru/engine/sessions.py +95 -0
- shikiru/engine/signalled.py +75 -0
- shikiru/engine/step_runner.py +140 -0
- shikiru/engine/supervisor.py +101 -0
- shikiru/engine/takeover.py +223 -0
- shikiru/engine/task_facts.py +46 -0
- shikiru/engine/tick_guard.py +95 -0
- shikiru/engine/titles.py +297 -0
- shikiru/engine/transitions.py +79 -0
- shikiru/engine/turn_context.py +206 -0
- shikiru/engine/turn_execution.py +30 -0
- shikiru/engine/turn_loop.py +230 -0
- shikiru/engine/turn_processes.py +79 -0
- shikiru/engine/turns.py +457 -0
- shikiru/errors.py +144 -0
- shikiru/helper/__init__.py +3 -0
- shikiru/helper/helper.py +182 -0
- shikiru/helper/models.py +11 -0
- shikiru/mcp/__init__.py +1 -0
- shikiru/mcp/server.py +196 -0
- shikiru/operator/__init__.py +33 -0
- shikiru/operator/attachment.py +24 -0
- shikiru/operator/commands.py +319 -0
- shikiru/operator/detectors.py +374 -0
- shikiru/operator/doctor.py +351 -0
- shikiru/operator/events.py +57 -0
- shikiru/operator/evidence_export.py +498 -0
- shikiru/operator/evidence_writer.py +78 -0
- shikiru/operator/lifecycle.py +294 -0
- shikiru/operator/plugin_checks.py +194 -0
- shikiru/operator/probe.py +287 -0
- shikiru/operator/queries.py +1601 -0
- shikiru/operator/services.py +36 -0
- shikiru/operator/sessions.py +378 -0
- shikiru/operator/strip_views.py +162 -0
- shikiru/operator/timeline.py +416 -0
- shikiru/operator/toml_text.py +62 -0
- shikiru/palette.py +18 -0
- shikiru/paths.py +91 -0
- shikiru/plugins/__init__.py +0 -0
- shikiru/plugins/agents/__init__.py +1 -0
- shikiru/plugins/agents/claude/__init__.py +5 -0
- shikiru/plugins/agents/claude/agent.py +251 -0
- shikiru/plugins/agents/claude/context.py +116 -0
- shikiru/plugins/agents/claude/headless.py +92 -0
- shikiru/plugins/agents/claude/hook.py +57 -0
- shikiru/plugins/agents/claude/model_catalog.py +105 -0
- shikiru/plugins/agents/claude/options.py +62 -0
- shikiru/plugins/agents/claude/turn.py +1018 -0
- shikiru/plugins/agents/codex/__init__.py +5 -0
- shikiru/plugins/agents/codex/agent.py +486 -0
- shikiru/plugins/agents/codex/headless.py +166 -0
- shikiru/plugins/agents/codex/home.py +14 -0
- shikiru/plugins/agents/codex/model_catalog.py +128 -0
- shikiru/plugins/agents/codex/options.py +94 -0
- shikiru/plugins/agents/codex/quota.py +195 -0
- shikiru/plugins/agents/codex/sandbox.py +31 -0
- shikiru/plugins/agents/codex/sandbox_probe.py +97 -0
- shikiru/plugins/agents/codex/turn.py +767 -0
- shikiru/plugins/agents/codex/usage.py +106 -0
- shikiru/plugins/agents/shared_settings.py +62 -0
- shikiru/plugins/widgets/__init__.py +3 -0
- shikiru/plugins/widgets/dialogue.py +144 -0
- shikiru/plugins/widgets/disk.py +123 -0
- shikiru/plugins/widgets/docker.py +225 -0
- shikiru/plugins/widgets/escalations.py +402 -0
- shikiru/plugins/widgets/faults.py +204 -0
- shikiru/plugins/widgets/header.py +321 -0
- shikiru/plugins/widgets/queue.py +1140 -0
- shikiru/plugins/widgets/queue_hint.py +93 -0
- shikiru/plugins/widgets/resources.py +105 -0
- shikiru/plugins/widgets/strips.py +82 -0
- shikiru/plugins/widgets/usage.py +176 -0
- shikiru/private.py +94 -0
- shikiru/process.py +535 -0
- shikiru/prompts/__init__.py +3 -0
- shikiru/prompts/renderer.py +85 -0
- shikiru/prompts/templates/_base.md.j2 +17 -0
- shikiru/prompts/templates/_macros.md.j2 +61 -0
- shikiru/prompts/templates/conflicts.md.j2 +22 -0
- shikiru/prompts/templates/consult.md.j2 +5 -0
- shikiru/prompts/templates/continue.md.j2 +4 -0
- shikiru/prompts/templates/feedback_analysis.md.j2 +29 -0
- shikiru/prompts/templates/feedback_rework.md.j2 +33 -0
- shikiru/prompts/templates/feedback_verify.md.j2 +33 -0
- shikiru/prompts/templates/handoff.md.j2 +23 -0
- shikiru/prompts/templates/implement.md.j2 +13 -0
- shikiru/prompts/templates/plan.md.j2 +30 -0
- shikiru/prompts/templates/rework.md.j2 +13 -0
- shikiru/prompts/templates/triage.md.j2 +20 -0
- shikiru/prompts/templates/verify.md.j2 +29 -0
- shikiru/store/__init__.py +1 -0
- shikiru/store/commands.py +242 -0
- shikiru/store/database.py +297 -0
- shikiru/store/escalations.py +240 -0
- shikiru/store/events.py +213 -0
- shikiru/store/feedback.py +266 -0
- shikiru/store/migrations/001_initial.sql +63 -0
- shikiru/store/migrations/002_headless_turns.sql +31 -0
- shikiru/store/migrations/003_drop_command_handled_at.sql +1 -0
- shikiru/store/migrations/004_repo_base_ref.sql +3 -0
- shikiru/store/migrations/005_complexity_explicit.sql +3 -0
- shikiru/store/migrations/006_events_kind_index.sql +1 -0
- shikiru/store/migrations/007_notification_cursor.sql +1 -0
- shikiru/store/migrations/008_reported_usage.sql +1 -0
- shikiru/store/migrations/009_billing_declarations.sql +2 -0
- shikiru/store/migrations/010_canonical_quota_windows.sql +3 -0
- shikiru/store/migrations/012_drop_task_quota_evidence.sql +2 -0
- shikiru/store/migrations/013_push_targets.sql +7 -0
- shikiru/store/migrations/014_agent_block_time.sql +6 -0
- shikiru/store/migrations/015_repair_orphan_agent_blocks.sql +3 -0
- shikiru/store/migrations/016_feedback_rounds.sql +29 -0
- shikiru/store/migrations/017_feedback_round_comments.sql +5 -0
- shikiru/store/migrations/__init__.py +1 -0
- shikiru/store/notification_cursor.py +29 -0
- shikiru/store/pushes.py +117 -0
- shikiru/store/quotas.py +153 -0
- shikiru/store/reader.py +310 -0
- shikiru/store/retention.py +92 -0
- shikiru/store/runs.py +227 -0
- shikiru/store/tasks.py +949 -0
- shikiru/telemetry/__init__.py +1 -0
- shikiru/telemetry/disk.py +167 -0
- shikiru/telemetry/docker.py +164 -0
- shikiru/telemetry/resources.py +296 -0
- shikiru/tmux/__init__.py +3 -0
- shikiru/tmux/client.py +637 -0
- shikiru/tmux/decorator.py +225 -0
- shikiru/tmux/layout.py +200 -0
- shikiru/tmux/session.py +381 -0
- shikiru/ui/__init__.py +9 -0
- shikiru/ui/age.py +45 -0
- shikiru/ui/bubbles.py +41 -0
- shikiru/ui/compact.py +1209 -0
- shikiru/ui/console.py +49 -0
- shikiru/ui/fold.py +50 -0
- shikiru/ui/gated_static.py +117 -0
- shikiru/ui/gauge.py +40 -0
- shikiru/ui/glyphs.py +109 -0
- shikiru/ui/keys.py +57 -0
- shikiru/ui/live_interval.py +42 -0
- shikiru/ui/popup.py +311 -0
- shikiru/ui/quota_status.py +26 -0
- shikiru/ui/recent_window.py +78 -0
- shikiru/ui/screen.py +57 -0
- shikiru/ui/section.py +193 -0
- shikiru/ui/session.py +65 -0
- shikiru/ui/size.py +17 -0
- shikiru/ui/splash.py +42 -0
- shikiru/ui/status.py +47 -0
- shikiru/ui/status_strip.py +149 -0
- shikiru/ui/stream.py +152 -0
- shikiru/ui/strip.py +332 -0
- shikiru/ui/theme.py +83 -0
- shikiru/ui/tokens.py +51 -0
- shikiru/view_log.py +126 -0
- shikiru/workspace/__init__.py +3 -0
- shikiru/workspace/base_refs.py +160 -0
- shikiru/workspace/caches.py +136 -0
- shikiru/workspace/facts.py +118 -0
- shikiru/workspace/git.py +601 -0
- shikiru/workspace/github.py +744 -0
- shikiru/workspace/messages.py +107 -0
- shikiru/workspace/reports.py +145 -0
- shikiru/workspace/roots.py +180 -0
- shikiru_cli-0.2.0.dist-info/METADATA +428 -0
- shikiru_cli-0.2.0.dist-info/RECORD +319 -0
- shikiru_cli-0.2.0.dist-info/WHEEL +4 -0
- shikiru_cli-0.2.0.dist-info/entry_points.txt +18 -0
- shikiru_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""The Help popup: one tab per context, opened on the context it was asked from."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
7
|
+
|
|
8
|
+
from rich.cells import cell_len
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
from textual.binding import Binding
|
|
11
|
+
from textual.containers import Horizontal, VerticalScroll
|
|
12
|
+
from textual.screen import ModalScreen
|
|
13
|
+
from textual.widgets import Button, Label, Rule, Static
|
|
14
|
+
|
|
15
|
+
from shikiru.dashboard.help_keys import HelpAvailability, HelpGroup, HelpTab, help_groups
|
|
16
|
+
from shikiru.ui.keys import key_hint
|
|
17
|
+
from shikiru.ui.popup import (
|
|
18
|
+
POPUP_BODY_CLASS,
|
|
19
|
+
POPUP_CLOSE_CLASS,
|
|
20
|
+
POPUP_CSS,
|
|
21
|
+
POPUP_FRAME_CLASS,
|
|
22
|
+
POPUP_HEADER_CLASS,
|
|
23
|
+
POPUP_RULE_CLASS,
|
|
24
|
+
POPUP_TITLE_CLASS,
|
|
25
|
+
PopupFrame,
|
|
26
|
+
popup_close_label,
|
|
27
|
+
prepare_popup,
|
|
28
|
+
)
|
|
29
|
+
from shikiru.ui.section import BOLD_STEEL, Section, grey, labelled_rows
|
|
30
|
+
from shikiru.ui.tokens import STEEL_BLUE
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from rich.console import Console, ConsoleOptions, RenderResult
|
|
34
|
+
from textual import events
|
|
35
|
+
from textual.app import ComposeResult
|
|
36
|
+
from textual.binding import BindingType
|
|
37
|
+
|
|
38
|
+
HELP_CLOSE_BUTTON = "workspace-help-close"
|
|
39
|
+
HELP_CONTEXT = "workspace-help-context"
|
|
40
|
+
HELP_TABS = "workspace-help-tabs"
|
|
41
|
+
HELP_HINT = "workspace-help-hint"
|
|
42
|
+
HELP_TITLE = "Help"
|
|
43
|
+
HELP_SCROLL = "workspace-help-scroll"
|
|
44
|
+
HELP_COPY = "workspace-help-copy"
|
|
45
|
+
HELP_FRAME = "workspace-help-frame"
|
|
46
|
+
COMPACT_MAX_COLUMNS = 48
|
|
47
|
+
COMPACT_MAX_ROWS = 20
|
|
48
|
+
TINY_MAX_ROWS = 10
|
|
49
|
+
_SWITCH_ARROWS = "\N{LEFTWARDS ARROW} \N{RIGHTWARDS ARROW}"
|
|
50
|
+
_SWITCH_LABEL = " switch section"
|
|
51
|
+
TAB_CLASS = "help-tab"
|
|
52
|
+
TAB_ACTIVE_CLASS = "-active"
|
|
53
|
+
SWITCH_ACTION = "switch"
|
|
54
|
+
SELECT_ACTION = "select_tab"
|
|
55
|
+
_TABS = tuple(HelpTab)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class HelpContext:
|
|
60
|
+
"""What Help was opened from: its tab and the Home parts that exist."""
|
|
61
|
+
|
|
62
|
+
opening: HelpTab
|
|
63
|
+
availability: HelpAvailability
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class _TabLabel(Static):
|
|
67
|
+
def __init__(self, index: int, tab: HelpTab) -> None:
|
|
68
|
+
super().__init__(tab.value, classes=TAB_CLASS, markup=False)
|
|
69
|
+
self._index = index
|
|
70
|
+
|
|
71
|
+
async def on_click(self) -> None:
|
|
72
|
+
await self.run_action(f"screen.{SELECT_ACTION}({self._index})")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class _KeyTable:
|
|
76
|
+
def __init__(self, groups: tuple[HelpGroup, ...]) -> None:
|
|
77
|
+
self._groups = groups
|
|
78
|
+
|
|
79
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
80
|
+
del console
|
|
81
|
+
for index, group in enumerate(self._groups):
|
|
82
|
+
if index:
|
|
83
|
+
yield Text("")
|
|
84
|
+
label_width = max(cell_len(row.keys) for row in group.rows)
|
|
85
|
+
rows = [
|
|
86
|
+
(Text(row.keys, style=BOLD_STEEL), (key_hint(row.action),)) for row in group.rows
|
|
87
|
+
]
|
|
88
|
+
body = labelled_rows(rows, options.max_width, label_width)
|
|
89
|
+
yield Section(group.title, body, ruled=False, title_style=STEEL_BLUE)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class _SectionHeightTable:
|
|
93
|
+
def __init__(self, active: _KeyTable, sections: tuple[_KeyTable, ...]) -> None:
|
|
94
|
+
self._active = active
|
|
95
|
+
self._sections = sections
|
|
96
|
+
|
|
97
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
98
|
+
heights = [
|
|
99
|
+
len(console.render_lines(section, options, pad=False)) for section in self._sections
|
|
100
|
+
]
|
|
101
|
+
shown = len(console.render_lines(self._active, options, pad=False))
|
|
102
|
+
yield self._active
|
|
103
|
+
for _ in range(max(heights) - shown):
|
|
104
|
+
yield Text("")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class WorkspaceHelpScreen(ModalScreen[None]):
|
|
108
|
+
"""Help with a tab per context; Left and Right switch tabs and the body scrolls."""
|
|
109
|
+
|
|
110
|
+
BINDINGS: ClassVar[list[BindingType]] = [
|
|
111
|
+
Binding("escape", "close", "Close"),
|
|
112
|
+
Binding("left", f"{SWITCH_ACTION}(-1)", "Previous tab", show=False, priority=True),
|
|
113
|
+
Binding("right", f"{SWITCH_ACTION}(1)", "Next tab", show=False, priority=True),
|
|
114
|
+
]
|
|
115
|
+
DEFAULT_CSS = (
|
|
116
|
+
POPUP_CSS
|
|
117
|
+
+ f"""
|
|
118
|
+
#{HELP_TABS} {{ height: 1; overflow: hidden; }}
|
|
119
|
+
#{HELP_HINT} {{ height: 1; margin-bottom: 1; }}
|
|
120
|
+
.{TAB_CLASS} {{ width: auto; margin: 0 2 0 0; color: $muted-text; }}
|
|
121
|
+
.{TAB_CLASS}.{TAB_ACTIVE_CLASS} {{
|
|
122
|
+
color: $steel-blue; text-style: bold; background: $selected-surface;
|
|
123
|
+
}}
|
|
124
|
+
#{HELP_SCROLL} {{ max-height: 1fr; min-height: 0; }}
|
|
125
|
+
#{HELP_COPY} {{ height: auto; color: $primary-text; }}
|
|
126
|
+
#{HELP_FRAME}.compact {{ margin: 0 1; padding: 0 1; }}
|
|
127
|
+
"""
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def __init__(self, context: HelpContext) -> None:
|
|
131
|
+
super().__init__()
|
|
132
|
+
prepare_popup(self)
|
|
133
|
+
self._help = context
|
|
134
|
+
self._tab = context.opening
|
|
135
|
+
|
|
136
|
+
def compose(self) -> ComposeResult:
|
|
137
|
+
"""Render the header, the tab row and the key table of the opening tab."""
|
|
138
|
+
with PopupFrame(widget_id=HELP_FRAME, classes=POPUP_FRAME_CLASS):
|
|
139
|
+
with Horizontal(classes=POPUP_HEADER_CLASS):
|
|
140
|
+
yield Label(HELP_TITLE, id=HELP_CONTEXT, classes=POPUP_TITLE_CLASS)
|
|
141
|
+
yield Button(popup_close_label(), id=HELP_CLOSE_BUTTON, classes=POPUP_CLOSE_CLASS)
|
|
142
|
+
yield Rule(classes=POPUP_RULE_CLASS)
|
|
143
|
+
with Horizontal(id=HELP_TABS):
|
|
144
|
+
for index, tab in enumerate(_TABS):
|
|
145
|
+
yield _TabLabel(index, tab)
|
|
146
|
+
yield Static(self._hint(keys_only=False), id=HELP_HINT, markup=False)
|
|
147
|
+
with VerticalScroll(id=HELP_SCROLL, classes=POPUP_BODY_CLASS):
|
|
148
|
+
yield Static(self._table(), id=HELP_COPY, markup=False)
|
|
149
|
+
|
|
150
|
+
def _hint(self, *, keys_only: bool) -> Text:
|
|
151
|
+
hint = Text(_SWITCH_ARROWS, style=BOLD_STEEL)
|
|
152
|
+
if not keys_only:
|
|
153
|
+
hint.append_text(grey(_SWITCH_LABEL))
|
|
154
|
+
return hint
|
|
155
|
+
|
|
156
|
+
def on_mount(self) -> None:
|
|
157
|
+
self._apply_compact_layout(self.size.width, self.size.height)
|
|
158
|
+
self._paint_tabs()
|
|
159
|
+
self.set_focus(self.query_one(f"#{HELP_SCROLL}", VerticalScroll))
|
|
160
|
+
self.call_after_refresh(self.query_one(f"#{HELP_FRAME}", PopupFrame).content_ready)
|
|
161
|
+
|
|
162
|
+
def on_resize(self, event: events.Resize) -> None:
|
|
163
|
+
self._apply_compact_layout(event.size.width, event.size.height)
|
|
164
|
+
frame = self.query_one(f"#{HELP_FRAME}", PopupFrame)
|
|
165
|
+
frame.reset_preferred_size()
|
|
166
|
+
self.call_after_refresh(frame.content_ready)
|
|
167
|
+
|
|
168
|
+
def _apply_compact_layout(self, width: int, height: int) -> None:
|
|
169
|
+
frame = self.query_one(f"#{HELP_FRAME}", PopupFrame)
|
|
170
|
+
frame.set_class(width <= COMPACT_MAX_COLUMNS or height <= COMPACT_MAX_ROWS, "compact")
|
|
171
|
+
self.query_one(f"#{HELP_HINT}", Static).update(
|
|
172
|
+
self._hint(keys_only=height <= TINY_MAX_ROWS)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def _paint_tabs(self) -> None:
|
|
176
|
+
for tab, label in zip(_TABS, self.query(f".{TAB_CLASS}"), strict=True):
|
|
177
|
+
label.set_class(tab is self._tab, TAB_ACTIVE_CLASS)
|
|
178
|
+
|
|
179
|
+
def _table(self) -> _SectionHeightTable:
|
|
180
|
+
sections = tuple(_KeyTable(help_groups(tab, self._help.availability)) for tab in _TABS)
|
|
181
|
+
return _SectionHeightTable(sections[_TABS.index(self._tab)], sections)
|
|
182
|
+
|
|
183
|
+
def action_select_tab(self, index: int) -> None:
|
|
184
|
+
"""Show the tab at `index` from the top."""
|
|
185
|
+
tab = _TABS[index]
|
|
186
|
+
if tab is self._tab:
|
|
187
|
+
return
|
|
188
|
+
self._tab = tab
|
|
189
|
+
self._paint_tabs()
|
|
190
|
+
self.query_one(f"#{HELP_COPY}", Static).update(self._table())
|
|
191
|
+
self.query_one(f"#{HELP_SCROLL}", VerticalScroll).scroll_home(animate=False)
|
|
192
|
+
|
|
193
|
+
def action_switch(self, step: int) -> None:
|
|
194
|
+
"""Show the neighbouring tab, stopping at both ends."""
|
|
195
|
+
self.action_select_tab(min(max(_TABS.index(self._tab) + step, 0), len(_TABS) - 1))
|
|
196
|
+
|
|
197
|
+
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
198
|
+
"""Close help when its button is activated."""
|
|
199
|
+
if event.button.id == HELP_CLOSE_BUTTON:
|
|
200
|
+
self.dismiss()
|
|
201
|
+
|
|
202
|
+
async def action_close(self) -> None:
|
|
203
|
+
"""Dismiss contextual help."""
|
|
204
|
+
self.dismiss()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from textual.widgets import Input, Select, TextArea
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from textual.widget import Widget
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def native_editor_has_focus(focused: Widget | None) -> bool:
|
|
12
|
+
current = focused
|
|
13
|
+
while current is not None:
|
|
14
|
+
if isinstance(current, (Input, Select, TextArea)):
|
|
15
|
+
return True
|
|
16
|
+
current = current.parent
|
|
17
|
+
return False
|