tau-coding-agent 0.1.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.
- tau/__init__.py +0 -0
- tau/agent/__init__.py +11 -0
- tau/agent/prompt/__init__.py +10 -0
- tau/agent/prompt/builder.py +302 -0
- tau/agent/prompt/types.py +33 -0
- tau/agent/service.py +369 -0
- tau/agent/types.py +61 -0
- tau/auth/manager.py +247 -0
- tau/auth/storage.py +82 -0
- tau/auth/types.py +41 -0
- tau/builtins/__init__.py +4 -0
- tau/builtins/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/commands/__init__.py +41 -0
- tau/builtins/commands/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/clear.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/clear.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/compact.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/compact.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/reload.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/reload.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/session.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/session.cpython-314.pyc +0 -0
- tau/builtins/commands/clear.py +16 -0
- tau/builtins/commands/compact.py +28 -0
- tau/builtins/commands/reload.py +27 -0
- tau/builtins/commands/session.py +19 -0
- tau/builtins/extensions/footer/__init__.py +76 -0
- tau/builtins/extensions/footer/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/git.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/model.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/utils.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/git.py +26 -0
- tau/builtins/extensions/footer/model.py +69 -0
- tau/builtins/extensions/footer/utils.py +44 -0
- tau/builtins/extensions/header/__init__.py +18 -0
- tau/builtins/extensions/header/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/models/__init__.py +0 -0
- tau/builtins/models/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/models/__pycache__/text.cpython-313.pyc +0 -0
- tau/builtins/models/audio.py +43 -0
- tau/builtins/models/image.py +43 -0
- tau/builtins/models/text.py +482 -0
- tau/builtins/models/video.py +40 -0
- tau/builtins/prompts/commit.md +7 -0
- tau/builtins/prompts/docs.md +7 -0
- tau/builtins/prompts/explain.md +7 -0
- tau/builtins/prompts/fix.md +7 -0
- tau/builtins/prompts/refactor.md +7 -0
- tau/builtins/prompts/review.md +7 -0
- tau/builtins/prompts/test.md +7 -0
- tau/builtins/providers/__init__.py +0 -0
- tau/builtins/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/providers/__pycache__/text.cpython-313.pyc +0 -0
- tau/builtins/providers/audio.py +10 -0
- tau/builtins/providers/image.py +9 -0
- tau/builtins/providers/text.py +33 -0
- tau/builtins/providers/video.py +6 -0
- tau/builtins/skills/code-review/SKILL.md +4 -0
- tau/builtins/skills/debug/SKILL.md +4 -0
- tau/builtins/skills/git-commit/SKILL.md +4 -0
- tau/builtins/themes/dark.yaml +1 -0
- tau/builtins/themes/light.yaml +46 -0
- tau/builtins/tools/__init__.py +73 -0
- tau/builtins/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/bash.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/bash.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/edit.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/edit.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/glob.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/glob.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/grep.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/grep.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/ls.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/ls.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/read.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/read.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/terminal.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/terminal.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/write.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/write.cpython-314.pyc +0 -0
- tau/builtins/tools/edit.py +215 -0
- tau/builtins/tools/glob.py +112 -0
- tau/builtins/tools/grep.py +146 -0
- tau/builtins/tools/ls.py +135 -0
- tau/builtins/tools/read.py +122 -0
- tau/builtins/tools/terminal.py +150 -0
- tau/builtins/tools/write.py +105 -0
- tau/commands/__init__.py +10 -0
- tau/commands/registry.py +71 -0
- tau/commands/types.py +33 -0
- tau/console/__init__.py +0 -0
- tau/console/cli.py +266 -0
- tau/console/commands/__init__.py +0 -0
- tau/console/commands/auth.py +193 -0
- tau/console/commands/packages.py +104 -0
- tau/console/commands/update.py +76 -0
- tau/core/__init__.py +0 -0
- tau/core/registry.py +102 -0
- tau/engine/__init__.py +47 -0
- tau/engine/service.py +768 -0
- tau/engine/types.py +163 -0
- tau/extensions/__init__.py +28 -0
- tau/extensions/api.py +928 -0
- tau/extensions/context.py +462 -0
- tau/extensions/events.py +70 -0
- tau/extensions/loader.py +386 -0
- tau/extensions/runtime.py +184 -0
- tau/extensions/settings.py +137 -0
- tau/hooks/__init__.py +112 -0
- tau/hooks/engine.py +237 -0
- tau/hooks/inference.py +21 -0
- tau/hooks/runtime.py +126 -0
- tau/hooks/service.py +121 -0
- tau/hooks/session.py +117 -0
- tau/hooks/tui.py +61 -0
- tau/hooks/types.py +72 -0
- tau/inference/__init__.py +180 -0
- tau/inference/api/__init__.py +0 -0
- tau/inference/api/audio/__init__.py +0 -0
- tau/inference/api/audio/base.py +29 -0
- tau/inference/api/audio/builtins.py +15 -0
- tau/inference/api/audio/elevenlabs_audio.py +183 -0
- tau/inference/api/audio/gemini_audio.py +95 -0
- tau/inference/api/audio/openai_audio.py +159 -0
- tau/inference/api/audio/registry.py +15 -0
- tau/inference/api/audio/sarvam_audio.py +163 -0
- tau/inference/api/audio/service.py +103 -0
- tau/inference/api/audio/utils.py +47 -0
- tau/inference/api/image/__init__.py +0 -0
- tau/inference/api/image/base.py +17 -0
- tau/inference/api/image/builtins.py +8 -0
- tau/inference/api/image/gemini_image.py +77 -0
- tau/inference/api/image/openai_image.py +103 -0
- tau/inference/api/image/openrouter.py +144 -0
- tau/inference/api/image/registry.py +15 -0
- tau/inference/api/image/service.py +71 -0
- tau/inference/api/registry.py +82 -0
- tau/inference/api/text/__init__.py +0 -0
- tau/inference/api/text/anthropic_claude_code.py +222 -0
- tau/inference/api/text/anthropic_messages.py +196 -0
- tau/inference/api/text/base.py +40 -0
- tau/inference/api/text/builtins.py +19 -0
- tau/inference/api/text/gemini_generate.py +234 -0
- tau/inference/api/text/github_copilot_chat.py +172 -0
- tau/inference/api/text/google_antigravity.py +522 -0
- tau/inference/api/text/mistral_chat.py +284 -0
- tau/inference/api/text/ollama_chat.py +200 -0
- tau/inference/api/text/openai_codex_responses.py +497 -0
- tau/inference/api/text/openai_completions.py +227 -0
- tau/inference/api/text/openai_responses.py +235 -0
- tau/inference/api/text/registry.py +50 -0
- tau/inference/api/text/service.py +297 -0
- tau/inference/api/text/types.py +7 -0
- tau/inference/api/text/utils.py +228 -0
- tau/inference/api/video/__init__.py +0 -0
- tau/inference/api/video/base.py +26 -0
- tau/inference/api/video/builtins.py +7 -0
- tau/inference/api/video/fal_video.py +119 -0
- tau/inference/api/video/openrouter_video.py +142 -0
- tau/inference/api/video/registry.py +15 -0
- tau/inference/api/video/service.py +72 -0
- tau/inference/model/__init__.py +0 -0
- tau/inference/model/registry.py +102 -0
- tau/inference/model/types.py +65 -0
- tau/inference/provider/__init__.py +0 -0
- tau/inference/provider/oauth/__init__.py +35 -0
- tau/inference/provider/oauth/anthropic_claude_code.py +286 -0
- tau/inference/provider/oauth/github_copilot.py +333 -0
- tau/inference/provider/oauth/google_antigravity.py +258 -0
- tau/inference/provider/oauth/openai_codex.py +309 -0
- tau/inference/provider/oauth/pkce.py +14 -0
- tau/inference/provider/oauth/types.py +46 -0
- tau/inference/provider/oauth/utils.py +154 -0
- tau/inference/provider/registry.py +141 -0
- tau/inference/provider/types.py +114 -0
- tau/inference/types.py +549 -0
- tau/inference/utils.py +219 -0
- tau/message/__init__.py +0 -0
- tau/message/types.py +482 -0
- tau/message/utils.py +178 -0
- tau/packages/__init__.py +11 -0
- tau/packages/manager.py +190 -0
- tau/packages/types.py +20 -0
- tau/packages/utils.py +67 -0
- tau/prompts/expand.py +58 -0
- tau/prompts/loader.py +69 -0
- tau/prompts/registry.py +45 -0
- tau/prompts/types.py +24 -0
- tau/rpc/__init__.py +8 -0
- tau/rpc/mode.py +783 -0
- tau/rpc/types.py +252 -0
- tau/runtime/service.py +759 -0
- tau/runtime/types.py +303 -0
- tau/session/branch_summarization.py +312 -0
- tau/session/compaction.py +646 -0
- tau/session/manager.py +652 -0
- tau/session/types.py +188 -0
- tau/session/utils.py +233 -0
- tau/settings/manager.py +1077 -0
- tau/settings/paths.py +150 -0
- tau/settings/storage.py +63 -0
- tau/settings/types.py +173 -0
- tau/settings/utils.py +25 -0
- tau/skills/loader.py +91 -0
- tau/skills/registry.py +70 -0
- tau/skills/types.py +25 -0
- tau/themes/loader.py +238 -0
- tau/themes/registry.py +108 -0
- tau/themes/types.py +19 -0
- tau/tool/__init__.py +3 -0
- tau/tool/registry.py +117 -0
- tau/tool/render.py +21 -0
- tau/tool/types.py +244 -0
- tau/trust/__init__.py +13 -0
- tau/trust/manager.py +80 -0
- tau/trust/types.py +14 -0
- tau/trust/utils.py +72 -0
- tau/tui/__init__.py +54 -0
- tau/tui/agent_hooks.py +346 -0
- tau/tui/ansi.py +330 -0
- tau/tui/app.py +540 -0
- tau/tui/autocomplete.py +33 -0
- tau/tui/capabilities.py +119 -0
- tau/tui/commands/__init__.py +3 -0
- tau/tui/commands/appearance.py +498 -0
- tau/tui/commands/auth.py +232 -0
- tau/tui/commands/context.py +38 -0
- tau/tui/commands/misc.py +82 -0
- tau/tui/commands/model.py +118 -0
- tau/tui/commands/session.py +464 -0
- tau/tui/component.py +268 -0
- tau/tui/components/__init__.py +0 -0
- tau/tui/components/autocomplete_manager.py +267 -0
- tau/tui/components/autocomplete_picker.py +143 -0
- tau/tui/components/box.py +90 -0
- tau/tui/components/command_palette.py +144 -0
- tau/tui/components/dynamic_border.py +19 -0
- tau/tui/components/file_picker.py +233 -0
- tau/tui/components/image.py +181 -0
- tau/tui/components/inline_selector.py +71 -0
- tau/tui/components/layout.py +1194 -0
- tau/tui/components/message_list.py +692 -0
- tau/tui/components/modal.py +97 -0
- tau/tui/components/model_palette.py +204 -0
- tau/tui/components/picker_overlay.py +174 -0
- tau/tui/components/prompt_overlay.py +236 -0
- tau/tui/components/resume_modal.py +372 -0
- tau/tui/components/select_list.py +222 -0
- tau/tui/components/settings_modal.py +274 -0
- tau/tui/components/settings_schema.py +203 -0
- tau/tui/components/spinner.py +119 -0
- tau/tui/components/text_input.py +396 -0
- tau/tui/components/text_prompt.py +82 -0
- tau/tui/components/tree_select_list.py +580 -0
- tau/tui/components/trust_screen.py +97 -0
- tau/tui/diff.py +114 -0
- tau/tui/fuzzy.py +99 -0
- tau/tui/input.py +496 -0
- tau/tui/input_handler.py +716 -0
- tau/tui/keybindings.py +87 -0
- tau/tui/markdown.py +286 -0
- tau/tui/message_renderers.py +31 -0
- tau/tui/overlay.py +326 -0
- tau/tui/renderer.py +378 -0
- tau/tui/terminal.py +499 -0
- tau/tui/theme.py +148 -0
- tau/tui/tui.py +544 -0
- tau/tui/ui_context.py +768 -0
- tau/tui/utils.py +20 -0
- tau/utils/__init__.py +0 -0
- tau/utils/http_proxy.py +221 -0
- tau/utils/image_processing.py +172 -0
- tau/utils/secrets.py +59 -0
- tau/utils/version_check.py +60 -0
- tau_coding_agent-0.1.0.dist-info/METADATA +177 -0
- tau_coding_agent-0.1.0.dist-info/RECORD +283 -0
- tau_coding_agent-0.1.0.dist-info/WHEEL +5 -0
- tau_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
- tau_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
- tau_coding_agent-0.1.0.dist-info/top_level.txt +1 -0
tau/tui/overlay.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Callable, Literal, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from tau.tui.component import Component
|
|
8
|
+
from tau.tui.input import InputEvent
|
|
9
|
+
|
|
10
|
+
# ---------------------------------------------------------------------------
|
|
11
|
+
# Types
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
# A size value: absolute column/row count or a percentage string like "60%"
|
|
15
|
+
SizeValue = int | str
|
|
16
|
+
|
|
17
|
+
# All nine anchor positions
|
|
18
|
+
OverlayAnchor = Literal[
|
|
19
|
+
"center",
|
|
20
|
+
"top-left", "top-center", "top-right",
|
|
21
|
+
"left-center", "right-center",
|
|
22
|
+
"bottom-left", "bottom-center", "bottom-right",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parse_size(value: SizeValue, reference: int) -> int:
|
|
27
|
+
"""Resolve a SizeValue against a reference dimension."""
|
|
28
|
+
if isinstance(value, str) and value.endswith("%"):
|
|
29
|
+
return int(reference * float(value[:-1]) / 100.0)
|
|
30
|
+
return int(value)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# OverlayOptions
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class OverlayOptions:
|
|
39
|
+
"""
|
|
40
|
+
Positioning and sizing options for a floating overlay window.
|
|
41
|
+
|
|
42
|
+
Positioning and sizing options for a floating overlay window with full anchor support, percentage sizes,
|
|
43
|
+
min/max constraints, explicit row/col positioning, responsive visibility,
|
|
44
|
+
and per-side margin control.
|
|
45
|
+
|
|
46
|
+
Examples::
|
|
47
|
+
|
|
48
|
+
# Centred dialog, 60% wide, max 80% tall
|
|
49
|
+
OverlayOptions(width="60%", max_height="80%", anchor="center")
|
|
50
|
+
|
|
51
|
+
# Right-side panel pinned to the bottom-right
|
|
52
|
+
OverlayOptions(width=40, anchor="bottom-right", margin=1)
|
|
53
|
+
|
|
54
|
+
# Responsive: hide when terminal is narrower than 80 cols
|
|
55
|
+
OverlayOptions(visible=lambda w, h: w >= 80)
|
|
56
|
+
|
|
57
|
+
# Explicit absolute position
|
|
58
|
+
OverlayOptions(row=5, col=10, width=30)
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# ── Size ─────────────────────────────────────────────────────────────────
|
|
62
|
+
# Width of the overlay (columns). Defaults to "60%".
|
|
63
|
+
width: SizeValue = "60%"
|
|
64
|
+
# Explicit height (rows). When None, the overlay's natural render height is used.
|
|
65
|
+
height: SizeValue | None = None
|
|
66
|
+
# Lower bound on width after percentage resolution.
|
|
67
|
+
min_width: int | None = None
|
|
68
|
+
# Upper bound on width.
|
|
69
|
+
max_width: SizeValue | None = None
|
|
70
|
+
# Lower bound on height.
|
|
71
|
+
min_height: int | None = None
|
|
72
|
+
# Upper bound on height. Defaults to "80%" so very tall components stay on screen.
|
|
73
|
+
max_height: SizeValue | None = "80%"
|
|
74
|
+
|
|
75
|
+
# ── Position ─────────────────────────────────────────────────────────────
|
|
76
|
+
# Named anchor point for automatic positioning. Overridden by row/col.
|
|
77
|
+
anchor: OverlayAnchor = "center"
|
|
78
|
+
# Fine-tune position after anchor calculation (signed, in rows/cols).
|
|
79
|
+
offset_x: int = 0
|
|
80
|
+
offset_y: int = 0
|
|
81
|
+
# Explicit row (0-indexed from top). Overrides anchor row calculation.
|
|
82
|
+
row: SizeValue | None = None
|
|
83
|
+
# Explicit col (0-indexed from left). Overrides anchor col calculation.
|
|
84
|
+
col: SizeValue | None = None
|
|
85
|
+
|
|
86
|
+
# ── Margin ───────────────────────────────────────────────────────────────
|
|
87
|
+
# Minimum gap from each terminal edge. Either a uniform int or a dict
|
|
88
|
+
# with optional keys "top", "right", "bottom", "left".
|
|
89
|
+
margin: int | dict[str, int] = 1
|
|
90
|
+
|
|
91
|
+
# ── Behaviour ────────────────────────────────────────────────────────────
|
|
92
|
+
# Called each render cycle with (term_width, term_height).
|
|
93
|
+
# Return False to hide the overlay on small terminals.
|
|
94
|
+
visible: Callable[[int, int], bool] | None = None
|
|
95
|
+
# If True the overlay is painted but does NOT capture keyboard focus.
|
|
96
|
+
non_capturing: bool = False
|
|
97
|
+
|
|
98
|
+
# ── Margins helper ───────────────────────────────────────────────────────
|
|
99
|
+
def _margins(self) -> tuple[int, int, int, int]:
|
|
100
|
+
"""Return (top, right, bottom, left) margin values."""
|
|
101
|
+
m = self.margin
|
|
102
|
+
if isinstance(m, int):
|
|
103
|
+
return m, m, m, m
|
|
104
|
+
return (
|
|
105
|
+
m.get("top", 1),
|
|
106
|
+
m.get("right", 1),
|
|
107
|
+
m.get("bottom", 1),
|
|
108
|
+
m.get("left", 1),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# OverlayHandle
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
class OverlayHandle:
|
|
117
|
+
"""
|
|
118
|
+
Returned by TUI.show_overlay() — controls a live overlay.
|
|
119
|
+
|
|
120
|
+
Overlay handle API::
|
|
121
|
+
|
|
122
|
+
handle = tui.show_overlay(MyDialog(), opts)
|
|
123
|
+
handle.set_hidden(True) # temporarily hide
|
|
124
|
+
handle.show() # make visible again
|
|
125
|
+
handle.focus() # steal keyboard focus
|
|
126
|
+
handle.unfocus() # release focus back
|
|
127
|
+
handle.close() # permanently remove
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
close_fn: Callable[[], None],
|
|
133
|
+
set_hidden_fn: Callable[[bool], None],
|
|
134
|
+
focus_fn: Callable[[], None],
|
|
135
|
+
unfocus_fn: Callable[["Component | None"], None],
|
|
136
|
+
is_focused_fn: Callable[[], bool],
|
|
137
|
+
is_hidden_fn: Callable[[], bool],
|
|
138
|
+
) -> None:
|
|
139
|
+
self._close_fn = close_fn
|
|
140
|
+
self._set_hidden_fn = set_hidden_fn
|
|
141
|
+
self._focus_fn = focus_fn
|
|
142
|
+
self._unfocus_fn = unfocus_fn
|
|
143
|
+
self._is_focused_fn = is_focused_fn
|
|
144
|
+
self._is_hidden_fn = is_hidden_fn
|
|
145
|
+
self._closed = False
|
|
146
|
+
|
|
147
|
+
# ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
def close(self) -> None:
|
|
150
|
+
"""Permanently remove this overlay from the screen."""
|
|
151
|
+
if not self._closed:
|
|
152
|
+
self._closed = True
|
|
153
|
+
self._close_fn()
|
|
154
|
+
|
|
155
|
+
# Alias — calls it hide() when it means close
|
|
156
|
+
hide = close
|
|
157
|
+
|
|
158
|
+
# ── Visibility ────────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
def set_hidden(self, hidden: bool) -> None:
|
|
161
|
+
"""Temporarily hide (True) or show (False) without closing."""
|
|
162
|
+
if not self._closed:
|
|
163
|
+
self._set_hidden_fn(hidden)
|
|
164
|
+
|
|
165
|
+
def show(self) -> None:
|
|
166
|
+
"""Make the overlay visible (undo a set_hidden(True))."""
|
|
167
|
+
self.set_hidden(False)
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def hidden(self) -> bool:
|
|
171
|
+
"""True while the overlay is temporarily hidden."""
|
|
172
|
+
return self._is_hidden_fn()
|
|
173
|
+
|
|
174
|
+
# ── Focus ─────────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
def focus(self) -> None:
|
|
177
|
+
"""Give keyboard focus to this overlay's component."""
|
|
178
|
+
if not self._closed:
|
|
179
|
+
self._focus_fn()
|
|
180
|
+
|
|
181
|
+
def unfocus(self, target: "Component | None" = None) -> None:
|
|
182
|
+
"""
|
|
183
|
+
Release focus from this overlay.
|
|
184
|
+
|
|
185
|
+
``target`` optionally specifies which component should receive
|
|
186
|
+
focus next; if None, TUI restores the previous focus target.
|
|
187
|
+
"""
|
|
188
|
+
if not self._closed:
|
|
189
|
+
self._unfocus_fn(target)
|
|
190
|
+
|
|
191
|
+
def is_focused(self) -> bool:
|
|
192
|
+
"""True when this overlay's component currently holds keyboard focus."""
|
|
193
|
+
return self._is_focused_fn()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
# OverlayEntry (internal)
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
@dataclass
|
|
201
|
+
class OverlayEntry:
|
|
202
|
+
"""Internal: one entry on the TUI overlay stack."""
|
|
203
|
+
|
|
204
|
+
component: "Component"
|
|
205
|
+
options: OverlayOptions = field(default_factory=OverlayOptions)
|
|
206
|
+
hidden: bool = False
|
|
207
|
+
|
|
208
|
+
def is_visible(self, term_w: int, term_h: int) -> bool:
|
|
209
|
+
"""Return False if the responsive visible() callback hides this overlay."""
|
|
210
|
+
if self.hidden:
|
|
211
|
+
return False
|
|
212
|
+
fn = self.options.visible
|
|
213
|
+
return fn(term_w, term_h) if fn is not None else True
|
|
214
|
+
|
|
215
|
+
def resolve_width(self, term_w: int) -> int:
|
|
216
|
+
"""Compute overlay width from options, applying min/max constraints."""
|
|
217
|
+
opt = self.options
|
|
218
|
+
mt, mr, mb, ml = opt._margins()
|
|
219
|
+
h_margin = ml + mr
|
|
220
|
+
|
|
221
|
+
w = _parse_size(opt.width, term_w)
|
|
222
|
+
|
|
223
|
+
if opt.min_width is not None:
|
|
224
|
+
w = max(w, opt.min_width)
|
|
225
|
+
if opt.max_width is not None:
|
|
226
|
+
w = min(w, _parse_size(opt.max_width, term_w))
|
|
227
|
+
|
|
228
|
+
return max(10, min(w, term_w - h_margin))
|
|
229
|
+
|
|
230
|
+
def resolve(
|
|
231
|
+
self,
|
|
232
|
+
term_w: int,
|
|
233
|
+
term_h: int,
|
|
234
|
+
natural_h: int,
|
|
235
|
+
) -> tuple[int, int, int, int]:
|
|
236
|
+
"""
|
|
237
|
+
Return (width, height, row, col) — all 0-indexed.
|
|
238
|
+
|
|
239
|
+
``natural_h`` is the component's unconstrained render line count.
|
|
240
|
+
"""
|
|
241
|
+
opt = self.options
|
|
242
|
+
mt, mr, mb, ml = opt._margins()
|
|
243
|
+
|
|
244
|
+
# ── Width ─────────────────────────────────────────────────────────
|
|
245
|
+
width = self.resolve_width(term_w)
|
|
246
|
+
|
|
247
|
+
# ── Height ────────────────────────────────────────────────────────
|
|
248
|
+
if opt.height is not None:
|
|
249
|
+
height = _parse_size(opt.height, term_h)
|
|
250
|
+
else:
|
|
251
|
+
height = natural_h
|
|
252
|
+
|
|
253
|
+
if opt.min_height is not None:
|
|
254
|
+
height = max(height, opt.min_height)
|
|
255
|
+
if opt.max_height is not None:
|
|
256
|
+
height = min(height, _parse_size(opt.max_height, term_h))
|
|
257
|
+
|
|
258
|
+
# Clamp to what the terminal can fit accounting for margins
|
|
259
|
+
max_h = max(3, term_h - mt - mb)
|
|
260
|
+
height = min(height, max_h)
|
|
261
|
+
|
|
262
|
+
# ── Position via anchor ───────────────────────────────────────────
|
|
263
|
+
anchor = opt.anchor
|
|
264
|
+
if anchor == "top-left":
|
|
265
|
+
row = mt
|
|
266
|
+
col = ml
|
|
267
|
+
elif anchor == "top-center":
|
|
268
|
+
row = mt
|
|
269
|
+
col = max(ml, (term_w - width) // 2)
|
|
270
|
+
elif anchor == "top-right":
|
|
271
|
+
row = mt
|
|
272
|
+
col = max(ml, term_w - width - mr)
|
|
273
|
+
elif anchor == "left-center":
|
|
274
|
+
row = max(mt, (term_h - height) // 2)
|
|
275
|
+
col = ml
|
|
276
|
+
elif anchor == "right-center":
|
|
277
|
+
row = max(mt, (term_h - height) // 2)
|
|
278
|
+
col = max(ml, term_w - width - mr)
|
|
279
|
+
elif anchor == "bottom-left":
|
|
280
|
+
row = max(mt, term_h - height - mb)
|
|
281
|
+
col = ml
|
|
282
|
+
elif anchor == "bottom-center":
|
|
283
|
+
row = max(mt, term_h - height - mb)
|
|
284
|
+
col = max(ml, (term_w - width) // 2)
|
|
285
|
+
elif anchor == "bottom-right":
|
|
286
|
+
row = max(mt, term_h - height - mb)
|
|
287
|
+
col = max(ml, term_w - width - mr)
|
|
288
|
+
else: # "center" — default
|
|
289
|
+
row = max(mt, (term_h - height) // 2)
|
|
290
|
+
col = max(ml, (term_w - width) // 2)
|
|
291
|
+
|
|
292
|
+
# ── Explicit row/col overrides anchor ─────────────────────────────
|
|
293
|
+
if opt.row is not None:
|
|
294
|
+
row = _parse_size(opt.row, term_h)
|
|
295
|
+
if opt.col is not None:
|
|
296
|
+
col = _parse_size(opt.col, term_w)
|
|
297
|
+
|
|
298
|
+
# ── Fine-tune with offset ─────────────────────────────────────────
|
|
299
|
+
row = max(0, min(row + opt.offset_y, term_h - height))
|
|
300
|
+
col = max(0, min(col + opt.offset_x, term_w - width))
|
|
301
|
+
|
|
302
|
+
return width, height, row, col
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
# CustomOptions (for Layout.custom())
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
@dataclass
|
|
310
|
+
class CustomOptions:
|
|
311
|
+
"""
|
|
312
|
+
Options for ``Layout.custom()`` — controls how the factory component
|
|
313
|
+
is displayed.
|
|
314
|
+
|
|
315
|
+
overlay=False (default) swaps the TUI root to the custom component
|
|
316
|
+
for a full-screen takeover; when the done() callback fires the
|
|
317
|
+
Layout is restored.
|
|
318
|
+
|
|
319
|
+
overlay=True renders the component as a floating overlay on top of
|
|
320
|
+
the existing layout, using overlay_options for positioning.
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
overlay: bool = False
|
|
324
|
+
overlay_options: OverlayOptions = field(default_factory=OverlayOptions)
|
|
325
|
+
# Called with the OverlayHandle immediately after the overlay is shown
|
|
326
|
+
on_handle: Callable[["OverlayHandle"], None] | None = None
|