induscode 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.
- induscode/__init__.py +56 -0
- induscode/addons/__init__.py +176 -0
- induscode/addons/contract.py +923 -0
- induscode/addons/dispatch/__init__.py +43 -0
- induscode/addons/dispatch/event_dispatcher.py +348 -0
- induscode/addons/dispatch/tool_interceptor.py +349 -0
- induscode/addons/host.py +469 -0
- induscode/addons/loader.py +314 -0
- induscode/addons/manifest.py +232 -0
- induscode/addons/surface.py +199 -0
- induscode/boot/__init__.py +108 -0
- induscode/boot/auth_vault.py +323 -0
- induscode/boot/boot.py +210 -0
- induscode/boot/contract.py +223 -0
- induscode/boot/invocation.py +117 -0
- induscode/boot/runners/__init__.py +42 -0
- induscode/boot/runners/link_runner.py +82 -0
- induscode/boot/runners/oneshot_runner.py +85 -0
- induscode/boot/runners/registry.py +46 -0
- induscode/boot/runners/repl_runner.py +340 -0
- induscode/boot/runners/session.py +549 -0
- induscode/boot/stages.py +198 -0
- induscode/boot/upgrade/__init__.py +36 -0
- induscode/boot/upgrade/apply.py +125 -0
- induscode/boot/upgrade/upgrades.py +136 -0
- induscode/briefing/__init__.py +115 -0
- induscode/briefing/compose.py +414 -0
- induscode/briefing/contract.py +528 -0
- induscode/briefing/macros.py +721 -0
- induscode/briefing/skills.py +417 -0
- induscode/capability_deck/__init__.py +233 -0
- induscode/capability_deck/bridge_ledger/__init__.py +66 -0
- induscode/capability_deck/bridge_ledger/key.py +181 -0
- induscode/capability_deck/bridge_ledger/ledger.py +276 -0
- induscode/capability_deck/bridge_ledger/network.py +336 -0
- induscode/capability_deck/builtin_bridge.py +358 -0
- induscode/capability_deck/cards/__init__.py +116 -0
- induscode/capability_deck/cards/bg_process.py +482 -0
- induscode/capability_deck/cards/memory.py +226 -0
- induscode/capability_deck/cards/saas.py +280 -0
- induscode/capability_deck/cards/task.py +256 -0
- induscode/capability_deck/cards/todo.py +312 -0
- induscode/capability_deck/contract.py +450 -0
- induscode/capability_deck/manifest.py +126 -0
- induscode/capability_deck/provision.py +217 -0
- induscode/channels/__init__.py +146 -0
- induscode/channels/contract.py +585 -0
- induscode/channels/framer.py +132 -0
- induscode/channels/link/__init__.py +50 -0
- induscode/channels/link/dialog.py +246 -0
- induscode/channels/link/driver.py +308 -0
- induscode/channels/link/server.py +217 -0
- induscode/channels/oneshot.py +178 -0
- induscode/channels/ops.py +140 -0
- induscode/channels/session_ops.py +172 -0
- induscode/conductor/__init__.py +240 -0
- induscode/conductor/catalog.py +309 -0
- induscode/conductor/conductor.py +1084 -0
- induscode/conductor/contract.py +1035 -0
- induscode/conductor/matcher.py +291 -0
- induscode/conductor/serialize.py +575 -0
- induscode/conductor/signal_hub.py +382 -0
- induscode/conductor/skill_parse.py +294 -0
- induscode/conductor/transcript_store.py +449 -0
- induscode/console/__init__.py +236 -0
- induscode/console/app.py +1677 -0
- induscode/console/components/__init__.py +62 -0
- induscode/console/components/banner.py +499 -0
- induscode/console/components/banner_sweep.py +188 -0
- induscode/console/components/emblem.py +181 -0
- induscode/console/components/status_bar.py +102 -0
- induscode/console/contract.py +836 -0
- induscode/console/input/__init__.py +107 -0
- induscode/console/input/chord.py +197 -0
- induscode/console/input/dir_reader.py +113 -0
- induscode/console/input/intents.py +258 -0
- induscode/console/input/providers.py +469 -0
- induscode/console/mount.py +137 -0
- induscode/console/overlays/__init__.py +94 -0
- induscode/console/overlays/auth.py +503 -0
- induscode/console/overlays/pickers.py +526 -0
- induscode/console/overlays/router.py +129 -0
- induscode/console/overlays/sessions.py +232 -0
- induscode/console/reducer.py +145 -0
- induscode/console/resume_picker.py +156 -0
- induscode/console/slash_commands/__init__.py +78 -0
- induscode/console/slash_commands/builtins.py +254 -0
- induscode/console/slash_commands/dynamic.py +217 -0
- induscode/console/slash_commands/integrations.py +949 -0
- induscode/console/slash_commands/transcript.py +404 -0
- induscode/console/slash_commands/workbench.py +430 -0
- induscode/console/startup.py +434 -0
- induscode/console/theme/__init__.py +44 -0
- induscode/console/theme/adapter.py +168 -0
- induscode/console/theme/palette.py +128 -0
- induscode/console/theme/resolve.py +123 -0
- induscode/console/theme/tokens.py +185 -0
- induscode/console_slash/__init__.py +111 -0
- induscode/console_slash/contract.py +185 -0
- induscode/console_slash/registry.py +140 -0
- induscode/console_slash/resolve.py +194 -0
- induscode/console_slash/shared.py +172 -0
- induscode/entry.py +108 -0
- induscode/insight/__init__.py +153 -0
- induscode/insight/collector.py +73 -0
- induscode/insight/replay.py +305 -0
- induscode/insight/wrapper.py +1115 -0
- induscode/kit/__init__.py +82 -0
- induscode/kit/clipboard_image.py +215 -0
- induscode/kit/external_editor.py +120 -0
- induscode/kit/image.py +188 -0
- induscode/kit/shell.py +89 -0
- induscode/kit/tool_fetch.py +288 -0
- induscode/launch/__init__.py +224 -0
- induscode/launch/catalog.py +310 -0
- induscode/launch/contract.py +569 -0
- induscode/launch/credentials.py +852 -0
- induscode/launch/invocation/__init__.py +39 -0
- induscode/launch/invocation/attachments.py +281 -0
- induscode/launch/invocation/flags.py +210 -0
- induscode/launch/invocation/read.py +369 -0
- induscode/launch/invocation/usage.py +110 -0
- induscode/launch/oauth.py +808 -0
- induscode/launch/packages.py +299 -0
- induscode/launch/pickers.py +291 -0
- induscode/py.typed +0 -0
- induscode/runtime_bridge/__init__.py +166 -0
- induscode/runtime_bridge/bridges/__init__.py +66 -0
- induscode/runtime_bridge/bridges/_drive.py +268 -0
- induscode/runtime_bridge/bridges/builtins.py +177 -0
- induscode/runtime_bridge/bridges/claude_cli.py +198 -0
- induscode/runtime_bridge/bridges/codex_cli.py +203 -0
- induscode/runtime_bridge/bridges/indusagi_cli.py +217 -0
- induscode/runtime_bridge/broker.py +397 -0
- induscode/runtime_bridge/contract.py +734 -0
- induscode/runtime_bridge/sink.py +351 -0
- induscode/sessions/__init__.py +25 -0
- induscode/sessions/contract.py +119 -0
- induscode/sessions/library.py +350 -0
- induscode/settings/__init__.py +47 -0
- induscode/settings/contract.py +313 -0
- induscode/settings/manager.py +268 -0
- induscode/transcript_export/__init__.py +109 -0
- induscode/transcript_export/contract.py +522 -0
- induscode/transcript_export/publish.py +455 -0
- induscode/transcript_export/sgr.py +566 -0
- induscode/transcript_export/template.py +319 -0
- induscode/transcript_export/theme_bridge.py +325 -0
- induscode/window_budget/__init__.py +76 -0
- induscode/window_budget/budget/__init__.py +26 -0
- induscode/window_budget/budget/estimate.py +273 -0
- induscode/window_budget/budget/gate.py +60 -0
- induscode/window_budget/budget/slice.py +145 -0
- induscode/window_budget/condenser.py +170 -0
- induscode/window_budget/contract.py +329 -0
- induscode/window_budget/summarize/__init__.py +33 -0
- induscode/window_budget/summarize/condense.py +212 -0
- induscode/window_budget/summarize/prompt.py +241 -0
- induscode/workspace/__init__.py +30 -0
- induscode/workspace/brand.py +96 -0
- induscode/workspace/locator.py +269 -0
- induscode-0.1.0.dist-info/METADATA +97 -0
- induscode-0.1.0.dist-info/RECORD +167 -0
- induscode-0.1.0.dist-info/WHEEL +4 -0
- induscode-0.1.0.dist-info/entry_points.txt +3 -0
- induscode-0.1.0.dist-info/licenses/CREDITS.md +22 -0
- induscode-0.1.0.dist-info/licenses/NOTICE +7 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Console input — public barrel for the composer-input modules (M5 wave 1).
|
|
2
|
+
|
|
3
|
+
Port of TS ``src/console/input`` (``keymap.ts`` / ``complete.ts`` /
|
|
4
|
+
``dir-reader.ts`` / ``index.ts``), reshaped around the framework editor per
|
|
5
|
+
the port plan (analysis 02 §6):
|
|
6
|
+
|
|
7
|
+
- **intents** — the :data:`ConsoleVerb` vocabulary (TS verbatim) split into
|
|
8
|
+
the app-level chord→intent :data:`INTENT_TABLE` (the data the wave-3
|
|
9
|
+
``ConsoleApp`` BINDINGS derive from) and the :data:`EDITOR_DELEGATED` map
|
|
10
|
+
documenting which TS verbs collapsed into the framework
|
|
11
|
+
``EditorKeybindingsManager``/``EditorCore``. The TS ``readKey``
|
|
12
|
+
classifier itself is not ported — Textual + the framework key decoder do
|
|
13
|
+
that job.
|
|
14
|
+
- **chord** — the double-tap latch (:func:`advance_chord`: Esc×2,
|
|
15
|
+
Ctrl+U×2) ported verbatim, plus the Ctrl+C exit window
|
|
16
|
+
(:func:`advance_exit_window`) extracted from the TS surface as a pure
|
|
17
|
+
helper with an injectable clock. The app owns the live latch values and
|
|
18
|
+
the timers.
|
|
19
|
+
- **providers** — the slash + ``@``/path completion engine
|
|
20
|
+
(:func:`complete_at` / :func:`apply_suggestion`, pure and verbatim) and
|
|
21
|
+
its framework ``AutocompleteProvider`` adapters
|
|
22
|
+
(:class:`ConsoleAutocompleteProvider` et al.) feeding ``PromptEditor``.
|
|
23
|
+
- **dir_reader** — the live ``os.scandir`` binding of the
|
|
24
|
+
:data:`DirReader` seam.
|
|
25
|
+
|
|
26
|
+
TS ``paste.ts`` is **deleted by design**: the paste vault, the
|
|
27
|
+
``‹clip N · K lines›`` markers, the burst debounce, and the
|
|
28
|
+
bracketed-paste stripping all collapse into the framework
|
|
29
|
+
``EditorCore`` paste markers + Textual's native ``events.Paste``
|
|
30
|
+
(pinned in ``tests/console/test_input.py``).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from .chord import (
|
|
34
|
+
CHORD_VERBS,
|
|
35
|
+
CHORD_WINDOW_MS,
|
|
36
|
+
CTRL_C_EXIT_WINDOW_MS,
|
|
37
|
+
ChordLatch,
|
|
38
|
+
ChordOutcome,
|
|
39
|
+
ChordStep,
|
|
40
|
+
ExitAction,
|
|
41
|
+
ExitStep,
|
|
42
|
+
ExitWindow,
|
|
43
|
+
NO_CHORD,
|
|
44
|
+
NO_EXIT_WINDOW,
|
|
45
|
+
advance_chord,
|
|
46
|
+
advance_exit_window,
|
|
47
|
+
)
|
|
48
|
+
from .dir_reader import DirEntry, DirReader, create_dir_reader
|
|
49
|
+
from .intents import (
|
|
50
|
+
CONSOLE_VERBS,
|
|
51
|
+
ConsoleIntent,
|
|
52
|
+
ConsoleVerb,
|
|
53
|
+
EDITOR_DELEGATED,
|
|
54
|
+
INTENT_TABLE,
|
|
55
|
+
NO_INTENT,
|
|
56
|
+
)
|
|
57
|
+
from .providers import (
|
|
58
|
+
AppliedSuggestion,
|
|
59
|
+
CompletionKind,
|
|
60
|
+
CompletionResult,
|
|
61
|
+
ConsoleAutocompleteProvider,
|
|
62
|
+
PathCompletionProvider,
|
|
63
|
+
SlashCommandProvider,
|
|
64
|
+
Suggestion,
|
|
65
|
+
TokenSpan,
|
|
66
|
+
active_token,
|
|
67
|
+
apply_suggestion,
|
|
68
|
+
complete_at,
|
|
69
|
+
to_autocomplete_item,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
"AppliedSuggestion",
|
|
74
|
+
"CHORD_VERBS",
|
|
75
|
+
"CHORD_WINDOW_MS",
|
|
76
|
+
"CONSOLE_VERBS",
|
|
77
|
+
"CTRL_C_EXIT_WINDOW_MS",
|
|
78
|
+
"ChordLatch",
|
|
79
|
+
"ChordOutcome",
|
|
80
|
+
"ChordStep",
|
|
81
|
+
"CompletionKind",
|
|
82
|
+
"CompletionResult",
|
|
83
|
+
"ConsoleAutocompleteProvider",
|
|
84
|
+
"ConsoleIntent",
|
|
85
|
+
"ConsoleVerb",
|
|
86
|
+
"DirEntry",
|
|
87
|
+
"DirReader",
|
|
88
|
+
"EDITOR_DELEGATED",
|
|
89
|
+
"ExitAction",
|
|
90
|
+
"ExitStep",
|
|
91
|
+
"ExitWindow",
|
|
92
|
+
"INTENT_TABLE",
|
|
93
|
+
"NO_CHORD",
|
|
94
|
+
"NO_EXIT_WINDOW",
|
|
95
|
+
"NO_INTENT",
|
|
96
|
+
"PathCompletionProvider",
|
|
97
|
+
"SlashCommandProvider",
|
|
98
|
+
"Suggestion",
|
|
99
|
+
"TokenSpan",
|
|
100
|
+
"active_token",
|
|
101
|
+
"advance_chord",
|
|
102
|
+
"advance_exit_window",
|
|
103
|
+
"apply_suggestion",
|
|
104
|
+
"complete_at",
|
|
105
|
+
"create_dir_reader",
|
|
106
|
+
"to_autocomplete_item",
|
|
107
|
+
]
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Chord latching — the stateful layer over the pure intent vocabulary.
|
|
2
|
+
|
|
3
|
+
Two small pure machines the wave-3 ``ConsoleApp`` owns the live values of:
|
|
4
|
+
|
|
5
|
+
1. **The double-tap latch** (TS ``keymap.ts`` ``advanceChord`` — verbatim).
|
|
6
|
+
A first tap of ``flow:dismiss`` (Escape) primes a chord; a second tap
|
|
7
|
+
within the surface's window fires ``dismiss×2`` (the configurable
|
|
8
|
+
double-Escape action: tree / fork / clear). A first tap of
|
|
9
|
+
``edit:clearLine`` (Ctrl+U on an already-empty buffer) primes a chord
|
|
10
|
+
whose second tap fires ``clear×2`` (request exit). Any other intent
|
|
11
|
+
breaks the latch. :func:`advance_chord` is pure; the app owns the latch
|
|
12
|
+
value and the :data:`CHORD_WINDOW_MS` timer that expires it (simply
|
|
13
|
+
resetting to :data:`NO_CHORD` — ``App.set_timer`` in Textual).
|
|
14
|
+
|
|
15
|
+
2. **The Ctrl+C exit window** (TS ``TerminalConsole.tsx`` ``flow:interrupt``
|
|
16
|
+
case — extracted here as a pure helper with an injectable clock).
|
|
17
|
+
Ctrl+C is clear-then-exit: a busy turn is aborted first; otherwise a
|
|
18
|
+
press on a non-empty buffer clears it, and a press on an empty buffer
|
|
19
|
+
arms a window — a second empty-buffer press within
|
|
20
|
+
:data:`CTRL_C_EXIT_WINDOW_MS` asks the host to leave.
|
|
21
|
+
:func:`advance_exit_window` folds one press into the carried
|
|
22
|
+
:class:`ExitWindow`; the caller injects ``now_ms`` (the clock seam), so
|
|
23
|
+
the whole machine is deterministic under test.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from types import MappingProxyType
|
|
30
|
+
from typing import Literal, Mapping, TypeAlias
|
|
31
|
+
|
|
32
|
+
from .intents import ConsoleIntent
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"CHORD_VERBS",
|
|
36
|
+
"CHORD_WINDOW_MS",
|
|
37
|
+
"CTRL_C_EXIT_WINDOW_MS",
|
|
38
|
+
"ChordLatch",
|
|
39
|
+
"ChordOutcome",
|
|
40
|
+
"ChordStep",
|
|
41
|
+
"ExitAction",
|
|
42
|
+
"ExitStep",
|
|
43
|
+
"ExitWindow",
|
|
44
|
+
"NO_CHORD",
|
|
45
|
+
"NO_EXIT_WINDOW",
|
|
46
|
+
"advance_chord",
|
|
47
|
+
"advance_exit_window",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Double-tap latch (TS ``advanceChord`` — verbatim)
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
#: How long a primed double-tap chord stays armed before the surface expires
|
|
56
|
+
#: it back to :data:`NO_CHORD` (TS ``CHORD_WINDOW_MS``).
|
|
57
|
+
CHORD_WINDOW_MS = 600
|
|
58
|
+
|
|
59
|
+
#: Which verbs participate in a double-tap chord, and what a repeat fires.
|
|
60
|
+
ChordOutcome: TypeAlias = Literal["dismiss×2", "clear×2"]
|
|
61
|
+
|
|
62
|
+
#: The verbs that can prime a chord, mapped to the outcome a repeat fires.
|
|
63
|
+
CHORD_VERBS: Mapping[str, ChordOutcome] = MappingProxyType(
|
|
64
|
+
{
|
|
65
|
+
"flow:dismiss": "dismiss×2",
|
|
66
|
+
"edit:clearLine": "clear×2",
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True, slots=True)
|
|
72
|
+
class ChordLatch:
|
|
73
|
+
"""The cross-keystroke latch the surface carries between key events.
|
|
74
|
+
|
|
75
|
+
``armed`` is the verb a prior keystroke primed (or ``None`` when no
|
|
76
|
+
chord is in flight). The surface resets it to ``None`` when its timeout
|
|
77
|
+
expires.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
# The verb a previous keystroke armed, or ``None`` when nothing is primed.
|
|
81
|
+
armed: str | None = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
#: A fresh, unarmed latch.
|
|
85
|
+
NO_CHORD = ChordLatch()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True, slots=True)
|
|
89
|
+
class ChordStep:
|
|
90
|
+
"""The result of folding a fresh intent into the prior chord latch."""
|
|
91
|
+
|
|
92
|
+
# The latch to carry into the next keystroke.
|
|
93
|
+
next: ChordLatch
|
|
94
|
+
# The chord that fired on this keystroke, or ``None`` if none did.
|
|
95
|
+
fired: ChordOutcome | None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def advance_chord(latch: ChordLatch, intent: ConsoleIntent) -> ChordStep:
|
|
99
|
+
"""Fold a fresh :class:`ConsoleIntent` into the prior :class:`ChordLatch`.
|
|
100
|
+
|
|
101
|
+
Pure: given the same latch and intent it always yields the same step. If
|
|
102
|
+
the incoming verb matches the armed verb and that verb is
|
|
103
|
+
chord-eligible, the chord fires and the latch resets. If the incoming
|
|
104
|
+
verb is chord-eligible but the latch was empty (or armed with a
|
|
105
|
+
different verb), it arms. Any other intent clears the latch. The timeout
|
|
106
|
+
that expires a stale latch is the surface's responsibility (it simply
|
|
107
|
+
resets to :data:`NO_CHORD`).
|
|
108
|
+
|
|
109
|
+
:param latch: the latch carried from the previous keystroke
|
|
110
|
+
:param intent: the freshly classified intent for this keystroke
|
|
111
|
+
"""
|
|
112
|
+
outcome = CHORD_VERBS.get(intent.verb)
|
|
113
|
+
if outcome is None:
|
|
114
|
+
# Non-chord key: drop any primed chord.
|
|
115
|
+
return ChordStep(next=NO_CHORD, fired=None)
|
|
116
|
+
if latch.armed == intent.verb:
|
|
117
|
+
# Second matching tap — fire and disarm.
|
|
118
|
+
return ChordStep(next=NO_CHORD, fired=outcome)
|
|
119
|
+
# First tap of a chord-eligible key — arm it.
|
|
120
|
+
return ChordStep(next=ChordLatch(armed=intent.verb), fired=None)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# Ctrl+C exit window (TS ``flow:interrupt`` case, as a pure machine)
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
#: How long after an empty-buffer Ctrl+C a second press still exits
|
|
128
|
+
#: (TS ``CTRL_C_EXIT_WINDOW_MS``).
|
|
129
|
+
CTRL_C_EXIT_WINDOW_MS = 500
|
|
130
|
+
|
|
131
|
+
#: What one Ctrl+C press does, in TS branch order: abort a busy turn, clear
|
|
132
|
+
#: a non-empty buffer, arm the window on the first empty-buffer press, exit
|
|
133
|
+
#: on the second within the window.
|
|
134
|
+
ExitAction: TypeAlias = Literal["abort", "clear", "arm", "exit"]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass(frozen=True, slots=True)
|
|
138
|
+
class ExitWindow:
|
|
139
|
+
"""The carried Ctrl+C latch: when (ms) the window was armed, or ``None``
|
|
140
|
+
when unarmed. The TS code kept a raw timestamp ref initialized to ``0``
|
|
141
|
+
as the unarmed sentinel; ``None`` expresses the same state without the
|
|
142
|
+
epoch hack."""
|
|
143
|
+
|
|
144
|
+
armed_at_ms: float | None = None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
#: A fresh, unarmed exit window.
|
|
148
|
+
NO_EXIT_WINDOW = ExitWindow()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass(frozen=True, slots=True)
|
|
152
|
+
class ExitStep:
|
|
153
|
+
"""The result of folding one Ctrl+C press into the carried window."""
|
|
154
|
+
|
|
155
|
+
# The window to carry into the next press.
|
|
156
|
+
next: ExitWindow
|
|
157
|
+
# What the surface should do for this press.
|
|
158
|
+
action: ExitAction
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def advance_exit_window(
|
|
162
|
+
window: ExitWindow,
|
|
163
|
+
*,
|
|
164
|
+
busy: bool,
|
|
165
|
+
buffer_empty: bool,
|
|
166
|
+
now_ms: float,
|
|
167
|
+
window_ms: float = CTRL_C_EXIT_WINDOW_MS,
|
|
168
|
+
) -> ExitStep:
|
|
169
|
+
"""Fold one Ctrl+C press into the prior :class:`ExitWindow`.
|
|
170
|
+
|
|
171
|
+
Pure, with the clock injected as ``now_ms`` (the caller supplies its
|
|
172
|
+
monotonic-ms reading; tests supply literals). Branch order is the TS
|
|
173
|
+
handler's exactly:
|
|
174
|
+
|
|
175
|
+
- a **busy** turn is aborted and the window disarms (the press never
|
|
176
|
+
doubles as an exit step);
|
|
177
|
+
- an **empty buffer**: a press within ``window_ms`` of the armed
|
|
178
|
+
timestamp fires ``exit`` and disarms; otherwise it (re-)arms at
|
|
179
|
+
``now_ms``;
|
|
180
|
+
- a **non-empty buffer**: the press clears the composer and disarms —
|
|
181
|
+
the window is only ever armed by a press that left an empty buffer
|
|
182
|
+
untouched.
|
|
183
|
+
|
|
184
|
+
:param window: the window carried from the previous press
|
|
185
|
+
:param busy: whether a turn is currently in flight
|
|
186
|
+
:param buffer_empty: whether the composer buffer is empty
|
|
187
|
+
:param now_ms: the injected clock reading, in milliseconds
|
|
188
|
+
:param window_ms: the exit window width (defaults to
|
|
189
|
+
:data:`CTRL_C_EXIT_WINDOW_MS`)
|
|
190
|
+
"""
|
|
191
|
+
if busy:
|
|
192
|
+
return ExitStep(next=NO_EXIT_WINDOW, action="abort")
|
|
193
|
+
if not buffer_empty:
|
|
194
|
+
return ExitStep(next=NO_EXIT_WINDOW, action="clear")
|
|
195
|
+
if window.armed_at_ms is not None and now_ms - window.armed_at_ms <= window_ms:
|
|
196
|
+
return ExitStep(next=NO_EXIT_WINDOW, action="exit")
|
|
197
|
+
return ExitStep(next=ExitWindow(armed_at_ms=now_ms), action="arm")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Directory reader — the live ``os.scandir``-backed :data:`DirReader`.
|
|
2
|
+
|
|
3
|
+
Port of TS ``src/console/input/dir-reader.ts``. The completion engine
|
|
4
|
+
(:func:`induscode.console.input.providers.complete_at`) is pure: its path
|
|
5
|
+
branch lists a directory through an injected :data:`DirReader` rather than
|
|
6
|
+
touching the disk itself, so the suggestion logic stays unit-testable
|
|
7
|
+
against a synthetic tree. This module is the one place that binds that seam
|
|
8
|
+
to a real filesystem.
|
|
9
|
+
|
|
10
|
+
:func:`create_dir_reader` returns a reader that resolves a (possibly
|
|
11
|
+
relative) directory path against a base directory — the session working
|
|
12
|
+
directory by default — and lists its immediate entries as :class:`DirEntry`
|
|
13
|
+
records. Every failure mode (a missing directory, a permission error, a
|
|
14
|
+
path that names a file) collapses to an empty list, so the composer never
|
|
15
|
+
raises while the user is mid-type. A symlink is classified by whether it
|
|
16
|
+
ultimately resolves to a directory, falling back to non-directory when the
|
|
17
|
+
target cannot be stat-ed (the TS ``statSync`` fallback, exactly).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import stat
|
|
24
|
+
from collections.abc import Callable, Sequence
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from typing import TypeAlias
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"DirEntry",
|
|
30
|
+
"DirReader",
|
|
31
|
+
"create_dir_reader",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class DirEntry:
|
|
37
|
+
"""One pure directory-listing row: a leaf name, flagged dir-or-file."""
|
|
38
|
+
|
|
39
|
+
# The leaf name (no path separators).
|
|
40
|
+
name: str
|
|
41
|
+
# Whether the entry is a directory (symlinks resolved; see module doc).
|
|
42
|
+
is_dir: bool
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
#: An injected directory listing so the path-completion branch stays pure
|
|
46
|
+
#: and testable. Given a directory path (relative to the working dir), it
|
|
47
|
+
#: returns that directory's immediate entries, or an empty list when the
|
|
48
|
+
#: path does not exist or cannot be read. The live console binds this to
|
|
49
|
+
#: :func:`create_dir_reader`; tests bind it to a synthetic tree.
|
|
50
|
+
DirReader: TypeAlias = Callable[[str], Sequence[DirEntry]]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _entry_is_dir(base: str, name: str, is_dirent: bool, is_symlink: bool) -> bool:
|
|
54
|
+
"""Decide whether a listed entry should be treated as a directory.
|
|
55
|
+
|
|
56
|
+
A plain directory dirent answers immediately. A symlink is followed with
|
|
57
|
+
a ``stat`` so a link to a directory completes with a trailing slash; if
|
|
58
|
+
the link target cannot be resolved (dangling, permission), the entry is
|
|
59
|
+
reported as a non-directory rather than raising.
|
|
60
|
+
"""
|
|
61
|
+
if is_dirent:
|
|
62
|
+
return True
|
|
63
|
+
if not is_symlink:
|
|
64
|
+
return False
|
|
65
|
+
try:
|
|
66
|
+
return stat.S_ISDIR(os.stat(os.path.join(base, name)).st_mode)
|
|
67
|
+
except OSError:
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _resolve(base: str, dir_path: str) -> str:
|
|
72
|
+
"""``path.resolve(cwd, dirPath)`` parity: an absolute request is used
|
|
73
|
+
as-is, a relative one is joined onto the base directory."""
|
|
74
|
+
if os.path.isabs(dir_path):
|
|
75
|
+
return os.path.normpath(dir_path)
|
|
76
|
+
return os.path.normpath(os.path.join(base, dir_path))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def create_dir_reader(cwd: str | None = None) -> DirReader:
|
|
80
|
+
"""Build a live :data:`DirReader` rooted at ``cwd``.
|
|
81
|
+
|
|
82
|
+
The returned reader resolves each requested directory path against
|
|
83
|
+
``cwd`` (an absolute request is used as-is), lists it, and maps each
|
|
84
|
+
child to a :class:`DirEntry`. Any error returns ``[]``.
|
|
85
|
+
|
|
86
|
+
:param cwd: the base directory relative requests resolve against
|
|
87
|
+
(defaults to the process working directory, captured here)
|
|
88
|
+
"""
|
|
89
|
+
base = cwd if cwd is not None else os.getcwd()
|
|
90
|
+
|
|
91
|
+
def read(dir_path: str) -> Sequence[DirEntry]:
|
|
92
|
+
target = _resolve(base, dir_path)
|
|
93
|
+
try:
|
|
94
|
+
with os.scandir(target) as scan:
|
|
95
|
+
dirents = list(scan)
|
|
96
|
+
except OSError:
|
|
97
|
+
return []
|
|
98
|
+
entries: list[DirEntry] = []
|
|
99
|
+
for dirent in dirents:
|
|
100
|
+
try:
|
|
101
|
+
is_dirent = dirent.is_dir(follow_symlinks=False)
|
|
102
|
+
is_symlink = dirent.is_symlink()
|
|
103
|
+
except OSError:
|
|
104
|
+
is_dirent, is_symlink = False, False
|
|
105
|
+
entries.append(
|
|
106
|
+
DirEntry(
|
|
107
|
+
name=dirent.name,
|
|
108
|
+
is_dir=_entry_is_dir(target, dirent.name, is_dirent, is_symlink),
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
return entries
|
|
112
|
+
|
|
113
|
+
return read
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Console intents — the keystroke-verb vocabulary and the chord→intent table.
|
|
2
|
+
|
|
3
|
+
Port of the *vocabulary* half of TS ``src/console/input/keymap.ts``. The TS
|
|
4
|
+
console classified raw Ink ``(input, key)`` events through a pure ``readKey``
|
|
5
|
+
into :class:`ConsoleIntent` verbs; in the Python console most of that
|
|
6
|
+
classifier **collapses into the framework**:
|
|
7
|
+
|
|
8
|
+
- Editor-level verbs (typing, deletion, caret motion, history, submit,
|
|
9
|
+
completion accept) are delegated to
|
|
10
|
+
:class:`indusagi.tui.keybindings.EditorKeybindingsManager` +
|
|
11
|
+
:class:`indusagi.tui.editor.EditorCore`, driven by the framework
|
|
12
|
+
``PromptEditor`` widget. :data:`EDITOR_DELEGATED` documents the exact
|
|
13
|
+
verb → ``EditorAction`` mapping, as data, so the delegation is pinned by
|
|
14
|
+
tests rather than prose.
|
|
15
|
+
- App-level verbs survive as :data:`INTENT_TABLE` — a chord → intent map in
|
|
16
|
+
the framework's ``KeyId`` vocabulary (``indusagi.tui.keys``). The wave-3
|
|
17
|
+
``ConsoleApp`` derives its Textual ``BINDINGS``/``action_*`` methods from
|
|
18
|
+
this table; the table itself stays pure data so the chord→verb matrix is
|
|
19
|
+
unit-tested without mounting Textual.
|
|
20
|
+
|
|
21
|
+
The verb strings are the TS union **verbatim** (they are user-visible in
|
|
22
|
+
``/keys`` and stay byte-identical); the chord spellings move from Ink's flag
|
|
23
|
+
bag to ``KeyId`` strings (``ctrl+r``, ``shift+tab``, ``alt+up``, ...), which
|
|
24
|
+
is the re-pinning analysis 02 risk-5 calls for.
|
|
25
|
+
|
|
26
|
+
Ink-specific quirks the framework dissolves (deliberately NOT ported):
|
|
27
|
+
|
|
28
|
+
- the macOS Backspace-as-``key.delete`` fold and the raw DEL/BS control
|
|
29
|
+
bytes — ``indusagi.tui.keys`` already maps ``\\x7f``/``\\x08`` to
|
|
30
|
+
``backspace``, so ``edit:erasePrev`` needs no quirk table;
|
|
31
|
+
- the printable-vs-control-byte filter (``isPrintable``) — Textual's
|
|
32
|
+
``events.Key.is_printable`` plays that role inside ``PromptEditor``;
|
|
33
|
+
- bracketed-paste delimiter stripping and the paste burst — Textual delivers
|
|
34
|
+
one assembled ``events.Paste`` (see ``tests/console/test_input.py`` §3).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from dataclasses import dataclass
|
|
40
|
+
from types import MappingProxyType
|
|
41
|
+
from typing import Literal, Mapping, TypeAlias
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"CONSOLE_VERBS",
|
|
45
|
+
"ConsoleIntent",
|
|
46
|
+
"ConsoleVerb",
|
|
47
|
+
"EDITOR_DELEGATED",
|
|
48
|
+
"INTENT_TABLE",
|
|
49
|
+
"NO_INTENT",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Console verb vocabulary (TS ``ConsoleVerb`` — verbatim)
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
#: The console's own keystroke-intent verbs. Deliberately *not* a mirror of
|
|
58
|
+
#: any upstream keybinding enum: each verb names what the console does,
|
|
59
|
+
#: grouped by concern (editing / caret / session control / model / queue /
|
|
60
|
+
#: input sources / overlays / view toggles / inert). Strings are the TS
|
|
61
|
+
#: union verbatim.
|
|
62
|
+
ConsoleVerb: TypeAlias = Literal[
|
|
63
|
+
# Editing
|
|
64
|
+
"text:type",
|
|
65
|
+
"text:newline",
|
|
66
|
+
"edit:erasePrev",
|
|
67
|
+
"edit:eraseNext",
|
|
68
|
+
"edit:clearLine",
|
|
69
|
+
# Caret / navigation
|
|
70
|
+
"nav:left",
|
|
71
|
+
"nav:right",
|
|
72
|
+
"nav:home",
|
|
73
|
+
"nav:end",
|
|
74
|
+
"nav:up",
|
|
75
|
+
"nav:down",
|
|
76
|
+
# Session control
|
|
77
|
+
"flow:submit",
|
|
78
|
+
"flow:dismiss",
|
|
79
|
+
"flow:accept",
|
|
80
|
+
"flow:interrupt",
|
|
81
|
+
"flow:suspend",
|
|
82
|
+
"flow:cycleModel",
|
|
83
|
+
# Model / reasoning
|
|
84
|
+
"model:cycleThinking",
|
|
85
|
+
# Queue
|
|
86
|
+
"queue:dequeue",
|
|
87
|
+
# Input sources
|
|
88
|
+
"input:pasteImage",
|
|
89
|
+
"input:externalEditor",
|
|
90
|
+
# Overlays
|
|
91
|
+
"overlay:open",
|
|
92
|
+
# View toggles
|
|
93
|
+
"view:expandTools",
|
|
94
|
+
"view:toggleReasoning",
|
|
95
|
+
# Inert
|
|
96
|
+
"none",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
#: Runtime mirror of the :data:`ConsoleVerb` union, in declaration order —
|
|
100
|
+
#: the coverage anchor for the union test (exhaustiveness via tests, not
|
|
101
|
+
#: types; cross-cutting rule 1).
|
|
102
|
+
CONSOLE_VERBS: tuple[str, ...] = (
|
|
103
|
+
"text:type",
|
|
104
|
+
"text:newline",
|
|
105
|
+
"edit:erasePrev",
|
|
106
|
+
"edit:eraseNext",
|
|
107
|
+
"edit:clearLine",
|
|
108
|
+
"nav:left",
|
|
109
|
+
"nav:right",
|
|
110
|
+
"nav:home",
|
|
111
|
+
"nav:end",
|
|
112
|
+
"nav:up",
|
|
113
|
+
"nav:down",
|
|
114
|
+
"flow:submit",
|
|
115
|
+
"flow:dismiss",
|
|
116
|
+
"flow:accept",
|
|
117
|
+
"flow:interrupt",
|
|
118
|
+
"flow:suspend",
|
|
119
|
+
"flow:cycleModel",
|
|
120
|
+
"model:cycleThinking",
|
|
121
|
+
"queue:dequeue",
|
|
122
|
+
"input:pasteImage",
|
|
123
|
+
"input:externalEditor",
|
|
124
|
+
"overlay:open",
|
|
125
|
+
"view:expandTools",
|
|
126
|
+
"view:toggleReasoning",
|
|
127
|
+
"none",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True, slots=True)
|
|
132
|
+
class ConsoleIntent:
|
|
133
|
+
"""A classified keystroke: a :data:`ConsoleVerb` plus the small payload a
|
|
134
|
+
verb may carry — the literal text for a ``text:type`` insert, or the
|
|
135
|
+
target overlay for an ``overlay:open``. Every other verb is
|
|
136
|
+
self-contained, so both payload fields default to ``None`` and are
|
|
137
|
+
present only on their owning verb.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# What the keystroke means to the console.
|
|
141
|
+
verb: ConsoleVerb
|
|
142
|
+
# The literal text to splice, present only for ``text:type``.
|
|
143
|
+
text: str | None = None
|
|
144
|
+
# The overlay to raise (a console ``ModalKind`` literal; the full union
|
|
145
|
+
# lands with the wave-2 console contract), present only for
|
|
146
|
+
# ``overlay:open``.
|
|
147
|
+
overlay: str | None = None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
#: The inert intent — a keystroke the console ignores.
|
|
151
|
+
NO_INTENT = ConsoleIntent(verb="none")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
# The intent table (app-level chords → intents, as data)
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
#: Key chord → :class:`ConsoleIntent`, in the framework ``KeyId`` spelling.
|
|
159
|
+
#:
|
|
160
|
+
#: This is the surviving app-level slice of the TS ``readKey`` matrix; the
|
|
161
|
+
#: wave-3 ``ConsoleApp`` derives its Textual ``BINDINGS`` from these rows.
|
|
162
|
+
#: Editor-level chords are absent by design — they are resolved inside the
|
|
163
|
+
#: framework ``PromptEditor`` (see :data:`EDITOR_DELEGATED`) and never reach
|
|
164
|
+
#: the app while the composer has focus.
|
|
165
|
+
#:
|
|
166
|
+
#: Two rows are deliberately dual-role:
|
|
167
|
+
#:
|
|
168
|
+
#: - ``escape`` → ``flow:dismiss``: while the autocomplete popup is open the
|
|
169
|
+
#: editor consumes Escape as ``selectCancel``; otherwise it bubbles to the
|
|
170
|
+
#: app, which dismisses an overlay / aborts a busy turn / feeds the
|
|
171
|
+
#: double-Esc chord latch (:func:`induscode.console.input.chord.advance_chord`).
|
|
172
|
+
#: - ``ctrl+u`` → ``edit:clearLine``: the *editing* effect is the framework's
|
|
173
|
+
#: ``deleteToLineStart`` (readline unix-line-discard, editor-consumed);
|
|
174
|
+
#: the app additionally observes the chord on an already-empty buffer to
|
|
175
|
+
#: arm the ``clear×2`` exit latch, exactly like the TS console.
|
|
176
|
+
INTENT_TABLE: Mapping[str, ConsoleIntent] = MappingProxyType(
|
|
177
|
+
{
|
|
178
|
+
# Session control.
|
|
179
|
+
"ctrl+c": ConsoleIntent(verb="flow:interrupt"),
|
|
180
|
+
"ctrl+z": ConsoleIntent(verb="flow:suspend"),
|
|
181
|
+
# Rotate the active model forward in scope (two chords, one verb —
|
|
182
|
+
# the TS table maps both ctrl+n and ctrl+p to the same rotation).
|
|
183
|
+
"ctrl+n": ConsoleIntent(verb="flow:cycleModel"),
|
|
184
|
+
"ctrl+p": ConsoleIntent(verb="flow:cycleModel"),
|
|
185
|
+
# Reasoning ladder + view toggles.
|
|
186
|
+
"shift+tab": ConsoleIntent(verb="model:cycleThinking"),
|
|
187
|
+
"ctrl+t": ConsoleIntent(verb="view:toggleReasoning"),
|
|
188
|
+
# The per-scope model picker is reached deliberately via
|
|
189
|
+
# /scoped-models, so this chord is free to toggle full tool-output
|
|
190
|
+
# rendering instead.
|
|
191
|
+
"ctrl+o": ConsoleIntent(verb="view:expandTools"),
|
|
192
|
+
# Input sources.
|
|
193
|
+
"ctrl+v": ConsoleIntent(verb="input:pasteImage"),
|
|
194
|
+
# The settings overlay is reached via /settings; this chord hands the
|
|
195
|
+
# composer buffer off to the user's external editor instead.
|
|
196
|
+
"ctrl+g": ConsoleIntent(verb="input:externalEditor"),
|
|
197
|
+
# Overlays raised directly from a control chord, without typing a
|
|
198
|
+
# slash command.
|
|
199
|
+
"ctrl+r": ConsoleIntent(verb="overlay:open", overlay="sessions"),
|
|
200
|
+
"ctrl+l": ConsoleIntent(verb="overlay:open", overlay="models"),
|
|
201
|
+
# Queue: pull the newest queued input back into the composer.
|
|
202
|
+
"alt+up": ConsoleIntent(verb="queue:dequeue"),
|
|
203
|
+
# Dual-role rows (see the table doc above).
|
|
204
|
+
"escape": ConsoleIntent(verb="flow:dismiss"),
|
|
205
|
+
"ctrl+u": ConsoleIntent(verb="edit:clearLine"),
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
# Editor delegation (which TS verbs collapsed into the framework)
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
#: TS ``readKey`` verb → the framework ``EditorAction`` name(s) that absorb
|
|
215
|
+
#: it (resolved by ``EditorKeybindingsManager``; dispatched by
|
|
216
|
+
#: ``PromptEditor`` / ``EditorCore``). Expressed as data so the coverage
|
|
217
|
+
#: test pins that every verb in :data:`CONSOLE_VERBS` is either an app
|
|
218
|
+
#: intent, editor-delegated, or the inert ``none``.
|
|
219
|
+
#:
|
|
220
|
+
#: Notes on the non-obvious rows:
|
|
221
|
+
#:
|
|
222
|
+
#: - ``text:type`` has no named action — it is ``PromptEditor``'s printable-
|
|
223
|
+
#: character path (``EditorCore.insert_character``), which also absorbs the
|
|
224
|
+
#: TS ``isPrintable`` control-byte filter.
|
|
225
|
+
#: - ``text:newline``: the bound chord is ``shift+enter``; the widget also
|
|
226
|
+
#: keeps the TS fallbacks (raw ``ctrl+j`` / ``alt+enter`` / a trailing
|
|
227
|
+
#: ``\\`` before Enter) for terminals without an extended protocol.
|
|
228
|
+
#: - ``edit:erasePrev``: the framework key decoder folds ``\\x7f``/``\\x08``
|
|
229
|
+
#: into ``backspace``, so the TS macOS quirk table dies here.
|
|
230
|
+
#: - ``edit:eraseNext`` had no chord in the TS matrix (the verb existed in
|
|
231
|
+
#: the vocabulary only); the framework gives it ``delete`` natively.
|
|
232
|
+
#: - ``nav:home``/``nav:end``: the TS console collapsed PageUp/PageDown onto
|
|
233
|
+
#: buffer start/end; the framework editor gives PageUp/PageDown true
|
|
234
|
+
#: visual-page motion (``pageUp``/``pageDown`` actions) — a deliberate,
|
|
235
|
+
#: documented divergence (the richer behavior supersedes the collapse).
|
|
236
|
+
#: - ``nav:up``/``nav:down`` are history-aware in ``EditorCore`` and double
|
|
237
|
+
#: as ``selectUp``/``selectDown`` while the completion popup is open.
|
|
238
|
+
#: - ``flow:submit`` doubles as ``selectConfirm`` in the popup; ``flow:accept``
|
|
239
|
+
#: is the popup/Tab completion accept (``tab``); ``flow:dismiss`` is
|
|
240
|
+
#: editor-consumed only while the popup is open (``selectCancel``).
|
|
241
|
+
EDITOR_DELEGATED: Mapping[str, tuple[str, ...]] = MappingProxyType(
|
|
242
|
+
{
|
|
243
|
+
"text:type": (),
|
|
244
|
+
"text:newline": ("newLine",),
|
|
245
|
+
"edit:erasePrev": ("deleteCharBackward",),
|
|
246
|
+
"edit:eraseNext": ("deleteCharForward",),
|
|
247
|
+
"edit:clearLine": ("deleteToLineStart",),
|
|
248
|
+
"nav:left": ("cursorLeft",),
|
|
249
|
+
"nav:right": ("cursorRight",),
|
|
250
|
+
"nav:home": ("cursorLineStart", "pageUp"),
|
|
251
|
+
"nav:end": ("cursorLineEnd", "pageDown"),
|
|
252
|
+
"nav:up": ("cursorUp", "selectUp"),
|
|
253
|
+
"nav:down": ("cursorDown", "selectDown"),
|
|
254
|
+
"flow:submit": ("submit", "selectConfirm"),
|
|
255
|
+
"flow:accept": ("tab",),
|
|
256
|
+
"flow:dismiss": ("selectCancel",),
|
|
257
|
+
}
|
|
258
|
+
)
|