@oh-my-pi/pi-tui 18.3.2 → 18.3.3
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.
- package/CHANGELOG.md +22 -0
- package/dist/types/app-keybindings.d.ts +2 -17
- package/dist/types/autocomplete.d.ts +18 -3
- package/dist/types/chrome/keybinding-hints.d.ts +16 -10
- package/dist/types/components/editor.d.ts +11 -0
- package/dist/types/key-hint-format.d.ts +29 -0
- package/dist/types/keys.d.ts +2 -1
- package/dist/types/overlays/agent-hub.d.ts +1 -1
- package/dist/types/overlays/agent-transcript-viewer.d.ts +1 -1
- package/dist/types/overlays/model-picker.d.ts +1 -3
- package/dist/types/prompt/composer-hints.d.ts +42 -0
- package/dist/types/prompt/composer.d.ts +2 -1
- package/dist/types/prompt/custom-editor.d.ts +8 -2
- package/dist/types/prompt/macos-spelling.d.ts +7 -16
- package/dist/types/prompt/prompt-action-autocomplete.d.ts +4 -1
- package/dist/types/prompt/prose-gate.d.ts +24 -0
- package/dist/types/prompt/word-completion.d.ts +46 -0
- package/dist/types/render/render-utils.d.ts +1 -1
- package/dist/types/theme/active-symbols.d.ts +12 -0
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +0 -1
- package/dist/types/tools/eval.d.ts +1 -1
- package/dist/types/tools/mcp.d.ts +3 -1
- package/dist/types/tools/task-yield-assembly.d.ts +10 -2
- package/dist/types/tools/task.d.ts +4 -0
- package/package.json +11 -10
- package/src/app-keybindings.ts +11 -104
- package/src/apps/autoresearch-dashboard.ts +10 -3
- package/src/apps/debug/log-viewer.ts +3 -2
- package/src/apps/debug/raw-sse.ts +2 -1
- package/src/apps/git/git-tui.ts +9 -5
- package/src/apps/live-visualizer.ts +2 -1
- package/src/apps/ps-top.ts +13 -4
- package/src/autocomplete.ts +42 -7
- package/src/chat/compaction-summary-message.ts +2 -1
- package/src/chat/execution-shared.ts +4 -2
- package/src/chat/tool-execution.ts +3 -1
- package/src/chat/ttsr-notification.ts +4 -3
- package/src/chat/user-message.ts +2 -2
- package/src/chrome/keybinding-hints.ts +38 -13
- package/src/components/editor.ts +180 -58
- package/src/components/settings-list.ts +10 -3
- package/src/components/tab-bar.ts +2 -1
- package/src/debug-server.ts +4 -1
- package/src/hotkeys-markdown.ts +14 -17
- package/src/key-hint-format.ts +122 -0
- package/src/keys.ts +2 -1
- package/src/overlays/advisor-config.ts +28 -6
- package/src/overlays/agent-hub.ts +8 -5
- package/src/overlays/agent-transcript-viewer.ts +6 -4
- package/src/overlays/agents-hub.ts +22 -8
- package/src/overlays/annotation-overlay.ts +31 -17
- package/src/overlays/ask-dialog.ts +14 -19
- package/src/overlays/bordered-loader.ts +2 -1
- package/src/overlays/btw-history-panel.ts +15 -12
- package/src/overlays/btw-panel.ts +12 -7
- package/src/overlays/cleanse-panel.ts +9 -6
- package/src/overlays/codex-reset-fireworks.ts +5 -3
- package/src/overlays/copy-selector.ts +13 -5
- package/src/overlays/extensions/extension-dashboard.ts +6 -1
- package/src/overlays/history-search.ts +3 -2
- package/src/overlays/hook-editor.ts +7 -2
- package/src/overlays/hook-input.ts +2 -1
- package/src/overlays/hook-selector.ts +5 -1
- package/src/overlays/login-dialog.ts +17 -4
- package/src/overlays/logout-account-selector.ts +10 -1
- package/src/overlays/mcp-add-wizard.ts +32 -20
- package/src/overlays/model-hub.ts +55 -28
- package/src/overlays/model-picker.ts +27 -12
- package/src/overlays/move-overlay.ts +5 -1
- package/src/overlays/omfg-panel.ts +8 -5
- package/src/overlays/pause-screen.ts +7 -3
- package/src/overlays/plan-review-overlay.ts +40 -14
- package/src/overlays/plan-save-overlay.ts +3 -1
- package/src/overlays/plugin-settings.ts +15 -8
- package/src/overlays/reset-usage-selector.ts +10 -3
- package/src/overlays/rewind-selector.ts +11 -5
- package/src/overlays/session-info-overlay.ts +4 -3
- package/src/overlays/session-selector.ts +12 -2
- package/src/overlays/settings-selector.ts +18 -11
- package/src/overlays/tree-selector.ts +27 -4
- package/src/overlays/usage-dashboard.ts +8 -2
- package/src/prompt/composer-cache.ts +2 -1
- package/src/prompt/composer-hints.ts +75 -0
- package/src/prompt/composer.ts +3 -2
- package/src/prompt/custom-editor.ts +22 -4
- package/src/prompt/macos-spelling.ts +17 -158
- package/src/prompt/prompt-action-autocomplete.ts +9 -2
- package/src/prompt/prose-gate.ts +87 -0
- package/src/prompt/tips.txt +6 -6
- package/src/prompt/welcome.ts +35 -2
- package/src/prompt/word-completion.ts +205 -0
- package/src/render/render-utils.ts +4 -4
- package/src/setup/scenes/composer.ts +5 -1
- package/src/setup/scenes/model.ts +3 -1
- package/src/setup/scenes/providers.ts +4 -1
- package/src/setup/scenes/sign-in.ts +4 -2
- package/src/setup/scenes/splash.ts +9 -4
- package/src/setup/scenes/theme.ts +18 -5
- package/src/setup/wizard-overlay.ts +5 -4
- package/src/theme/active-symbols.ts +20 -0
- package/src/theme/glyph-bundle.json +28 -1
- package/src/theme/symbols.ts +88 -6
- package/src/theme/theme-class.ts +0 -1
- package/src/theme/theme.ts +2 -0
- package/src/tools/bash-interactive.ts +2 -1
- package/src/tools/eval.ts +3 -3
- package/src/tools/mcp.ts +3 -1
- package/src/tools/task-yield-assembly.ts +34 -8
- package/src/tools/task.ts +14 -5
- package/dist/types/overlays/tiny-title-download-progress.d.ts +0 -21
- package/src/overlays/tiny-title-download-progress.ts +0 -102
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import type { EditorTextAssistProvider } from "../components/editor";
|
|
3
|
+
import { maskNonProse } from "./markdown-prose";
|
|
4
|
+
import { isProseWord, lineContext, ProseSource } from "./prose-gate";
|
|
5
|
+
|
|
6
|
+
/** Values of the `spelling.autocomplete` setting. */
|
|
7
|
+
export const WORD_COMPLETION_METHODS = ["off", "auto", "ngram", "smollm", "apple"] as const;
|
|
8
|
+
|
|
9
|
+
/** Configured word-completion engine; `off` disables ghost text. */
|
|
10
|
+
export type WordCompletionMethod = (typeof WORD_COMPLETION_METHODS)[number];
|
|
11
|
+
|
|
12
|
+
/** Whether `value` is a {@link WordCompletionMethod}. */
|
|
13
|
+
export function isWordCompletionMethod(value: unknown): value is WordCompletionMethod {
|
|
14
|
+
return WORD_COMPLETION_METHODS.some(method => method === value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** An enabled word-completion engine. */
|
|
18
|
+
export type WordCompletionEngine = Exclude<WordCompletionMethod, "off">;
|
|
19
|
+
|
|
20
|
+
/** Asynchronous engine behind ghost-text word completion (coding-agent: the text-prediction daemon). */
|
|
21
|
+
export interface WordPredictionBackend {
|
|
22
|
+
/** Characters to paint after `prefix` typed after `before`, or `null` to show nothing. */
|
|
23
|
+
complete(before: string, prefix: string): Promise<string | null>;
|
|
24
|
+
/** Report a shown suggestion the user accepted (Tab/→) or typed past. Fire-and-forget. */
|
|
25
|
+
feedback(before: string, prefix: string, suggestion: string, accepted: boolean): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Resolves the backend for an engine; `undefined` means none is available (no ghost text). */
|
|
29
|
+
export type WordPredictionBackendResolver = (method: WordCompletionEngine) => WordPredictionBackend | undefined;
|
|
30
|
+
|
|
31
|
+
let hostResolver: WordPredictionBackendResolver | undefined;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Install the host's backend resolver for every editor, including ones built
|
|
35
|
+
* before the host finished starting. Pass `undefined` to detach.
|
|
36
|
+
*/
|
|
37
|
+
export function setWordPredictionHost(resolver: WordPredictionBackendResolver | undefined): void {
|
|
38
|
+
hostResolver = resolver;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const WORD_SUFFIX = /[\p{L}\p{M}']+$/u;
|
|
42
|
+
const WORD_CONTINUES = /^[\p{L}\p{M}']/u;
|
|
43
|
+
const CACHE_LIMIT = 256;
|
|
44
|
+
/** Context sent with each query: the editor text before the word, capped to a recent tail. */
|
|
45
|
+
const BEFORE_LIMIT = 2_000;
|
|
46
|
+
|
|
47
|
+
interface WordQuery {
|
|
48
|
+
key: string;
|
|
49
|
+
before: string;
|
|
50
|
+
prefix: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Editor text before the word starting at `start`, truncated to its last {@link BEFORE_LIMIT} code units. */
|
|
54
|
+
function textBefore(lines: readonly string[], cursorLine: number, start: number): string {
|
|
55
|
+
let text = (lines[cursorLine] ?? "").slice(0, start);
|
|
56
|
+
for (let line = cursorLine - 1; line >= 0 && text.length < BEFORE_LIMIT; line--) {
|
|
57
|
+
text = `${lines[line] ?? ""}\n${text}`;
|
|
58
|
+
}
|
|
59
|
+
if (text.length <= BEFORE_LIMIT) return text;
|
|
60
|
+
let cut = text.length - BEFORE_LIMIT;
|
|
61
|
+
// Never start on the low half of a surrogate pair.
|
|
62
|
+
const unit = text.charCodeAt(cut);
|
|
63
|
+
if (unit >= 0xdc00 && unit <= 0xdfff) cut++;
|
|
64
|
+
return text.slice(cut);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Ghost-text word completion for the prose word ending at the cursor, served
|
|
69
|
+
* by an injected asynchronous {@link WordPredictionBackend}.
|
|
70
|
+
*
|
|
71
|
+
* Rendering is synchronous, so a cache miss schedules a single-flight backend
|
|
72
|
+
* request (one active, the newest queued) and repaints through `onUpdate` when
|
|
73
|
+
* the answer changes what is shown. The previous suggestion is projected: if
|
|
74
|
+
* its full word still extends what was typed, the remainder shows immediately
|
|
75
|
+
* and stays until the engine offers a different word.
|
|
76
|
+
*/
|
|
77
|
+
export class WordCompletionProvider implements EditorTextAssistProvider {
|
|
78
|
+
#method: WordCompletionMethod = "off";
|
|
79
|
+
#generation = 0;
|
|
80
|
+
#prose = new ProseSource();
|
|
81
|
+
#cache = new Map<string, string | null>();
|
|
82
|
+
#activeKey: string | undefined;
|
|
83
|
+
#queued: WordQuery | undefined;
|
|
84
|
+
/** Last non-empty suggestion shown, for projection while typing through it. */
|
|
85
|
+
#lastShown: { before: string; word: string } | undefined;
|
|
86
|
+
/** What the last `getWordCompletion` returned, so a fetch repaints only when it changes that. */
|
|
87
|
+
#displayed: { key: string; suffix: string | null } | undefined;
|
|
88
|
+
|
|
89
|
+
/** Invoked when an asynchronous answer changes the rendered ghost text. */
|
|
90
|
+
onUpdate: (() => void) | undefined;
|
|
91
|
+
|
|
92
|
+
readonly #resolveBackend: WordPredictionBackendResolver;
|
|
93
|
+
|
|
94
|
+
/** `resolveBackend` defaults to the host installed with {@link setWordPredictionHost}. */
|
|
95
|
+
constructor(resolveBackend: WordPredictionBackendResolver = method => hostResolver?.(method)) {
|
|
96
|
+
this.#resolveBackend = resolveBackend;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Select the engine (`off` disables completion) and drop answers from the previous one. */
|
|
100
|
+
setMethod(method: WordCompletionMethod): void {
|
|
101
|
+
if (this.#method === method) return;
|
|
102
|
+
this.#method = method;
|
|
103
|
+
this.#generation++;
|
|
104
|
+
this.#cache.clear();
|
|
105
|
+
this.#queued = undefined;
|
|
106
|
+
this.#lastShown = undefined;
|
|
107
|
+
this.#displayed = undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Return the ghost-text suffix for the prose word ending at the cursor, or `null`. */
|
|
111
|
+
getWordCompletion(lines: string[], cursorLine: number, cursorCol: number): string | null {
|
|
112
|
+
const query = this.#query(lines, cursorLine, cursorCol);
|
|
113
|
+
const backend = query && this.#backend();
|
|
114
|
+
if (!query || !backend) {
|
|
115
|
+
this.#displayed = undefined;
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (!this.#cache.has(query.key)) this.#schedule(backend, query);
|
|
119
|
+
// Typing through a shown ghost without Tab can answer null (the word fell
|
|
120
|
+
// under the show threshold, or SmolLM excludes words typed past); keep that
|
|
121
|
+
// ghost until the engine offers something else.
|
|
122
|
+
const suffix = this.#cache.get(query.key) ?? this.#project(query);
|
|
123
|
+
this.#displayed = { key: query.key, suffix };
|
|
124
|
+
if (suffix) this.#lastShown = { before: query.before, word: query.prefix + suffix };
|
|
125
|
+
return suffix;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Forward accept (Tab/→) or typed-past feedback for the suggestion shown at the cursor. */
|
|
129
|
+
wordCompletionFeedback(
|
|
130
|
+
lines: string[],
|
|
131
|
+
cursorLine: number,
|
|
132
|
+
cursorCol: number,
|
|
133
|
+
suggestion: string,
|
|
134
|
+
accepted: boolean,
|
|
135
|
+
): void {
|
|
136
|
+
const query = this.#query(lines, cursorLine, cursorCol);
|
|
137
|
+
if (!query) return;
|
|
138
|
+
this.#backend()?.feedback(query.before, query.prefix, suggestion, accepted);
|
|
139
|
+
if (!accepted) this.#lastShown = undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#backend(): WordPredictionBackend | undefined {
|
|
143
|
+
return this.#method === "off" ? undefined : this.#resolveBackend(this.#method);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#query(lines: readonly string[], cursorLine: number, cursorCol: number): WordQuery | undefined {
|
|
147
|
+
if (this.#method === "off") return undefined;
|
|
148
|
+
const line = lines[cursorLine] ?? "";
|
|
149
|
+
if (WORD_CONTINUES.test(line.slice(cursorCol))) return undefined;
|
|
150
|
+
// Single letters are asked too (`figure o|ut`); engines own their minimum prefix.
|
|
151
|
+
const match = WORD_SUFFIX.exec(line.slice(0, cursorCol));
|
|
152
|
+
if (!match) return undefined;
|
|
153
|
+
const start = cursorCol - match[0].length;
|
|
154
|
+
if (!isProseWord(line, maskNonProse(line), start, cursorCol)) return undefined;
|
|
155
|
+
if (!this.#prose.isProse(lineContext(lines, cursorLine), start, cursorCol)) return undefined;
|
|
156
|
+
const prefix = match[0];
|
|
157
|
+
const before = textBefore(lines, cursorLine, start);
|
|
158
|
+
// The method keeps a request still in flight for the previous engine from
|
|
159
|
+
// shadowing the same word state on the new one.
|
|
160
|
+
return { key: `${this.#method}\u0000${prefix}\u0000${before}`, before, prefix };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#project(query: WordQuery): string | null {
|
|
164
|
+
const shown = this.#lastShown;
|
|
165
|
+
if (!shown || shown.before !== query.before || shown.word.length <= query.prefix.length) return null;
|
|
166
|
+
if (!shown.word.toLocaleLowerCase().startsWith(query.prefix.toLocaleLowerCase())) return null;
|
|
167
|
+
return shown.word.slice(query.prefix.length);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#schedule(backend: WordPredictionBackend, query: WordQuery): void {
|
|
171
|
+
if (this.#activeKey === query.key || this.#queued?.key === query.key) return;
|
|
172
|
+
if (this.#activeKey !== undefined) {
|
|
173
|
+
this.#queued = query;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
void this.#run(backend, query);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Answer `query`, start the queued request, then repaint if the answer changes what is shown. */
|
|
180
|
+
async #run(backend: WordPredictionBackend, query: WordQuery): Promise<void> {
|
|
181
|
+
this.#activeKey = query.key;
|
|
182
|
+
const generation = this.#generation;
|
|
183
|
+
let suffix: string | null;
|
|
184
|
+
try {
|
|
185
|
+
suffix = (await backend.complete(query.before, query.prefix)) || null;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
logger.debug("word completion request failed", { error: String(error) });
|
|
188
|
+
suffix = null;
|
|
189
|
+
}
|
|
190
|
+
if (this.#activeKey === query.key) this.#activeKey = undefined;
|
|
191
|
+
const current = generation === this.#generation;
|
|
192
|
+
if (current) {
|
|
193
|
+
if (this.#cache.size >= CACHE_LIMIT) this.#cache.clear();
|
|
194
|
+
this.#cache.set(query.key, suffix);
|
|
195
|
+
}
|
|
196
|
+
const queued = this.#queued;
|
|
197
|
+
this.#queued = undefined;
|
|
198
|
+
const next = queued && !this.#cache.has(queued.key) ? this.#backend() : undefined;
|
|
199
|
+
if (queued && next) void this.#run(next, queued);
|
|
200
|
+
const displayed = this.#displayed;
|
|
201
|
+
if (current && displayed?.key === query.key && displayed.suffix !== (suffix ?? this.#project(query))) {
|
|
202
|
+
this.onUpdate?.();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -10,7 +10,7 @@ import * as path from "node:path";
|
|
|
10
10
|
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
11
11
|
import type { Ellipsis } from "@oh-my-pi/pi-natives";
|
|
12
12
|
import { pluralize, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
13
|
-
import {
|
|
13
|
+
import { formatKeyHint, type KeyId } from "../app-keybindings";
|
|
14
14
|
import { getKeybindings } from "../keybindings";
|
|
15
15
|
import type { Theme } from "../theme/theme";
|
|
16
16
|
import type { Component } from "../tui";
|
|
@@ -201,10 +201,10 @@ const EXPAND_ACTION = "app.tools.expand";
|
|
|
201
201
|
/** Fallback key when no binding is resolvable (e.g. outside an interactive session). */
|
|
202
202
|
const DEFAULT_EXPAND_KEY: KeyId = "ctrl+o";
|
|
203
203
|
|
|
204
|
-
/** Human-readable key
|
|
204
|
+
/** Human-readable primary key bound to tool-output expansion, e.g. `Ctrl+O`. */
|
|
205
205
|
export function expandKeyHint(): string {
|
|
206
|
-
const
|
|
207
|
-
return
|
|
206
|
+
const [key = DEFAULT_EXPAND_KEY] = getKeybindings().getKeys(EXPAND_ACTION);
|
|
207
|
+
return formatKeyHint(key);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// =============================================================================
|
|
@@ -6,6 +6,7 @@ import { WizardStep } from "../../components/wizard-step";
|
|
|
6
6
|
import type { ComposerShape } from "../../overlays/composer-shape-registry";
|
|
7
7
|
import { renderComposerShapePreview } from "../../overlays/composer-shape-preview";
|
|
8
8
|
import { getComposerShapeOptions } from "../../overlays/composer-shape-registry";
|
|
9
|
+
import { editorKey } from "../../chrome/keybinding-hints";
|
|
9
10
|
import { getSelectListTheme, theme } from "../../theme/theme";
|
|
10
11
|
import type { SetupScene, SetupSceneController, SetupSceneHost } from "./types";
|
|
11
12
|
|
|
@@ -73,7 +74,10 @@ class ComposerSceneController implements SetupSceneController {
|
|
|
73
74
|
|
|
74
75
|
render(width: number, maxLines?: number): readonly string[] {
|
|
75
76
|
const intro = new Text(
|
|
76
|
-
theme.fg(
|
|
77
|
+
theme.fg(
|
|
78
|
+
"muted",
|
|
79
|
+
`Select a layout; live preview updates below. Press ${editorKey("tui.select.confirm")} to confirm.`,
|
|
80
|
+
),
|
|
77
81
|
0,
|
|
78
82
|
0,
|
|
79
83
|
);
|
|
@@ -4,6 +4,7 @@ import { Text } from "../../components/text";
|
|
|
4
4
|
import { WizardStep } from "../../components/wizard-step";
|
|
5
5
|
import { buildBrowserItems, ModelBrowser, resolveRoleAssignments, sortModelItems } from "../../overlays/model-browser";
|
|
6
6
|
import { BROWSER_FRAME_ROWS } from "../../overlays/model-picker";
|
|
7
|
+
import { formatKeyHint } from "../../app-keybindings";
|
|
7
8
|
import { theme } from "../../theme/theme";
|
|
8
9
|
import type { SetupScene, SetupSceneController, SetupSceneHost } from "./types";
|
|
9
10
|
|
|
@@ -58,7 +59,8 @@ class ModelSceneController implements SetupSceneController {
|
|
|
58
59
|
|
|
59
60
|
render(width: number, maxLines?: number): readonly string[] {
|
|
60
61
|
const intro = new Text(
|
|
61
|
-
this.#status ??
|
|
62
|
+
this.#status ??
|
|
63
|
+
theme.fg("muted", `Type to search. ${formatKeyHint("enter")} saves the highlighted model as your default.`),
|
|
62
64
|
0,
|
|
63
65
|
0,
|
|
64
66
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type SgrMouseEvent } from "../../mouse";
|
|
2
2
|
import { TabBar } from "../../components/tab-bar";
|
|
3
|
+
import { editorKey } from "../../chrome/keybinding-hints";
|
|
3
4
|
import { getTabBarTheme } from "../../chrome/shared";
|
|
4
5
|
import { SignInTab } from "./sign-in";
|
|
5
6
|
import type { SetupScene, SetupSceneController, SetupSceneHost, SetupTab } from "./types";
|
|
@@ -13,7 +14,9 @@ import { WebSearchTab } from "./web-search";
|
|
|
13
14
|
*/
|
|
14
15
|
class ProvidersSceneController implements SetupSceneController {
|
|
15
16
|
title = "Set up your providers";
|
|
16
|
-
subtitle
|
|
17
|
+
get subtitle(): string {
|
|
18
|
+
return `Sign in and pick a web search provider. Press ${editorKey("tui.select.cancel")} when you're done.`;
|
|
19
|
+
}
|
|
17
20
|
|
|
18
21
|
#tabs: SetupTab[];
|
|
19
22
|
#tabBar: TabBar;
|
|
@@ -6,6 +6,8 @@ import { Spacer } from "../../components/spacer";
|
|
|
6
6
|
import { Text } from "../../components/text";
|
|
7
7
|
import { WizardStep } from "../../components/wizard-step";
|
|
8
8
|
import { Input } from "../../components/input";
|
|
9
|
+
import { formatKeyHint } from "../../app-keybindings";
|
|
10
|
+
import { editorKey } from "../../chrome/keybinding-hints";
|
|
9
11
|
import { matchesKey } from "../../keys";
|
|
10
12
|
import { type SgrMouseEvent } from "../../mouse";
|
|
11
13
|
import { wrapTextWithAnsi } from "../../utils";
|
|
@@ -19,7 +21,7 @@ function loginUrlLink(url: string): string {
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
function loginCopyHint(): string {
|
|
22
|
-
return theme.fg("dim",
|
|
24
|
+
return theme.fg("dim", `(clipboard copy attempted; ${formatKeyHint("alt+c")} retries)`);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
class CopyablePromptInput implements Component, Focusable {
|
|
@@ -290,7 +292,7 @@ export class SignInTab implements SetupTab {
|
|
|
290
292
|
const message = error instanceof Error ? error.message : String(error);
|
|
291
293
|
this.#statusLines = [
|
|
292
294
|
theme.fg("error", `Login failed: ${message}`),
|
|
293
|
-
theme.fg("dim",
|
|
295
|
+
theme.fg("dim", `Choose another provider or press ${editorKey("tui.select.cancel")} to continue.`),
|
|
294
296
|
];
|
|
295
297
|
this.#authUrl = undefined;
|
|
296
298
|
this.#authLaunchUrl = undefined;
|
|
@@ -2,6 +2,7 @@ import { centerLine, visibleWidth } from "../../utils";
|
|
|
2
2
|
import { padToWidth } from "../../render/utils";
|
|
3
3
|
import { gradientEscape, gradientLogo, PI_LOGO, type ShineConfig } from "../../prompt/welcome";
|
|
4
4
|
import { theme } from "../../theme/theme";
|
|
5
|
+
import { formatKeyHint } from "../../app-keybindings";
|
|
5
6
|
|
|
6
7
|
export const SETUP_SPLASH_MS = 2600;
|
|
7
8
|
export const SETUP_TICK_MS = 33;
|
|
@@ -22,7 +23,10 @@ const RESET = "\x1b[0m";
|
|
|
22
23
|
const MIN_SCENE_WIDTH = 56;
|
|
23
24
|
const MIN_SCENE_HEIGHT = 22;
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
/** Skip affordance; built at render time so it follows the live symbol preset. */
|
|
27
|
+
function skipHint(): string {
|
|
28
|
+
return `press ${formatKeyHint("enter")} to skip`;
|
|
29
|
+
}
|
|
26
30
|
|
|
27
31
|
/** Density ramp for the rippling water, lightest → heaviest. */
|
|
28
32
|
const WATER_RAMP = [
|
|
@@ -166,12 +170,13 @@ export function renderSetupSplash(width: number, height: number, elapsedMs: numb
|
|
|
166
170
|
}
|
|
167
171
|
});
|
|
168
172
|
// 4. skip hint on a cleared strip at the bottom so it stays legible over the water
|
|
169
|
-
const
|
|
173
|
+
const hint = skipHint();
|
|
174
|
+
const hintWidth = visibleWidth(hint);
|
|
170
175
|
const hintStart = Math.floor((w - hintWidth) / 2);
|
|
171
176
|
const hintRow = h - 1;
|
|
172
177
|
for (let x = hintStart - 1; x <= hintStart + hintWidth; x++) put(x, hintRow, " ");
|
|
173
178
|
let col = hintStart;
|
|
174
|
-
for (const ch of
|
|
179
|
+
for (const ch of hint) put(col++, hintRow, ch === " " ? " " : theme.fg("dim", ch));
|
|
175
180
|
|
|
176
181
|
return cells.map(row => row.join(""));
|
|
177
182
|
}
|
|
@@ -187,6 +192,6 @@ function renderCompactSplash(width: number, height: number, phase: number, shine
|
|
|
187
192
|
lines.push(width > 0 ? padToWidth(item !== undefined ? centerLine(item, width) : "", width) : "");
|
|
188
193
|
}
|
|
189
194
|
if (height > 2)
|
|
190
|
-
lines[height - 2] = width > 0 ? padToWidth(centerLine(theme.fg("dim",
|
|
195
|
+
lines[height - 2] = width > 0 ? padToWidth(centerLine(theme.fg("dim", skipHint()), width), width) : "";
|
|
191
196
|
return lines;
|
|
192
197
|
}
|
|
@@ -5,6 +5,7 @@ import { type SelectItem, SelectList } from "../../components/select-list";
|
|
|
5
5
|
import { Text } from "../../components/text";
|
|
6
6
|
import { WizardStep } from "../../components/wizard-step";
|
|
7
7
|
import { Container } from "../../tui";
|
|
8
|
+
import { editorKey } from "../../chrome/keybinding-hints";
|
|
8
9
|
import {
|
|
9
10
|
enableAutoTheme,
|
|
10
11
|
getAvailableThemes,
|
|
@@ -59,7 +60,10 @@ function renderMockEditor(width: number): string[] {
|
|
|
59
60
|
const top = theme.fg("borderAccent", `${box.topLeft}${horizontal}${box.topRight}`);
|
|
60
61
|
const bottom = theme.fg("borderMuted", `${box.bottomLeft}${horizontal}${box.bottomRight}`);
|
|
61
62
|
const prompt = `${theme.fg("accent", ">")} ${theme.fg("text", "Ask anything, edit files, run tools")}${theme.inverse(" ")}`;
|
|
62
|
-
const hint = theme.fg(
|
|
63
|
+
const hint = theme.fg(
|
|
64
|
+
"dim",
|
|
65
|
+
`${editorKey("tui.input.submit")} send · ${editorKey("tui.input.newLine")} newline · / commands`,
|
|
66
|
+
);
|
|
63
67
|
return [
|
|
64
68
|
top,
|
|
65
69
|
`${theme.fg("borderAccent", box.vertical)}${innerWidth > 0 ? padToWidth(prompt, innerWidth) : ""}${theme.fg("borderAccent", box.vertical)}`,
|
|
@@ -83,7 +87,9 @@ function renderThemePreview(width: number): string[] {
|
|
|
83
87
|
|
|
84
88
|
class ThemeSceneController implements SetupSceneController {
|
|
85
89
|
title = "Pick a theme";
|
|
86
|
-
subtitle
|
|
90
|
+
get subtitle(): string {
|
|
91
|
+
return `Move through the list to preview; ${editorKey("tui.select.confirm")} saves the highlighted choice.`;
|
|
92
|
+
}
|
|
87
93
|
#mode: ThemeMode = "curated";
|
|
88
94
|
#selectList: SelectList;
|
|
89
95
|
#loadingAllThemes = false;
|
|
@@ -135,13 +141,20 @@ class ThemeSceneController implements SetupSceneController {
|
|
|
135
141
|
render(width: number, maxLines?: number): readonly string[] {
|
|
136
142
|
const intro = new Container();
|
|
137
143
|
intro.addChild(
|
|
138
|
-
new Text(
|
|
144
|
+
new Text(
|
|
145
|
+
theme.fg(
|
|
146
|
+
"muted",
|
|
147
|
+
`Theme changes preview live. Nothing is saved until you press ${editorKey("tui.select.confirm")}.`,
|
|
148
|
+
),
|
|
149
|
+
0,
|
|
150
|
+
0,
|
|
151
|
+
),
|
|
139
152
|
);
|
|
140
153
|
intro.addChild(
|
|
141
154
|
new Text(
|
|
142
155
|
this.#mode === "all"
|
|
143
|
-
? theme.fg("dim",
|
|
144
|
-
: theme.fg("dim", "
|
|
156
|
+
? theme.fg("dim", `Browsing all themes · ${editorKey("tui.select.cancel")} returns to curated choices`)
|
|
157
|
+
: theme.fg("dim", `${editorKey("tui.select.cancel")} skips this step`),
|
|
145
158
|
0,
|
|
146
159
|
0,
|
|
147
160
|
),
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Component, type OverlayFocusOwner } from "../tui";
|
|
2
|
+
import { formatKeyHint } from "../app-keybindings";
|
|
3
|
+
import { editorKey, editorKeys } from "../chrome/keybinding-hints";
|
|
2
4
|
import { matchesKey } from "../keys";
|
|
3
5
|
import { centerLine, padding } from "../utils";
|
|
4
6
|
import { padToWidth } from "../render/utils";
|
|
@@ -200,10 +202,9 @@ export class SetupWizardComponent implements Component, OverlayFocusOwner {
|
|
|
200
202
|
header.push("");
|
|
201
203
|
this.#bodyRowStart = header.length;
|
|
202
204
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
];
|
|
205
|
+
const navKeys = editorKeys("tui.select.up", "tui.select.down");
|
|
206
|
+
const footerHint = `${navKeys} select · ${editorKey("tui.select.confirm")} confirm · ${editorKey("tui.select.cancel")} skip · ${formatKeyHint("ctrl+c")} exit setup`;
|
|
207
|
+
const footer = ["", centerLine(theme.fg("dim", footerHint), width)];
|
|
207
208
|
const maxBodyLines = Math.max(0, height - header.length - footer.length);
|
|
208
209
|
const body = this.#activeScene?.render(contentWidth, maxBodyLines).slice(0, maxBodyLines) ?? [];
|
|
209
210
|
const lines = [...header, ...body.map(line => indentLine(line, width, SCENE_MARGIN_X))];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active theme's symbol lookup without importing `./theme` (which pulls the
|
|
3
|
+
* native addon through `@oh-my-pi/pi-natives`). Key-hint formatting runs on
|
|
4
|
+
* CLI paths that must stay addon-free (`omp --version`, help), so it reads the
|
|
5
|
+
* active theme through this mirror; `./theme` publishes every assignment here.
|
|
6
|
+
*/
|
|
7
|
+
import { SYMBOL_PRESETS, type SymbolKey } from "./symbols";
|
|
8
|
+
import type { Theme } from "./theme-class";
|
|
9
|
+
|
|
10
|
+
let active: Theme | undefined;
|
|
11
|
+
|
|
12
|
+
/** @internal Called by `./theme` whenever the active theme changes. */
|
|
13
|
+
export function setActiveSymbolTheme(value: Theme | undefined): void {
|
|
14
|
+
active = value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Symbol from the active theme, or the ascii preset before any theme loads. */
|
|
18
|
+
export function activeThemeSymbol(key: SymbolKey): string {
|
|
19
|
+
return active ? active.symbol(key) : SYMBOL_PRESETS.ascii[key];
|
|
20
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "
|
|
2
|
+
"$comment": "Generated by packages/tui/scripts/gen-glyphs.ts from JetBrainsMono NFM Regular (Version 2.304; ttfautohint (v1.8.4.7-5d5b);Nerd Fonts 3.5.1). Do not edit; run `bun run gen:glyphs`.",
|
|
3
3
|
"upm": 1000,
|
|
4
4
|
"aw": 600,
|
|
5
5
|
"lh": 1320,
|
|
@@ -595,6 +595,9 @@
|
|
|
595
595
|
"f0068": {
|
|
596
596
|
"glyf": "AAUAAAFoAlgDwQAJABIAHAAgADAAABMHNyciFzc0BxcFNzQHFycHNycTNAcXJwc3JzAXAzcnBzcXFhQHAQYiLwEmNDcBNjK6RigoAkhIKCgBDkgoKEhGKCiOKChIRigoRq5GPkZcQAoK/rYKGAhCCgoBTAoYA1spSEYoKAFHSPEpAUhHKChHRwFWAUdIKSlIRij+9UU9RV9BCBkJ/rQKCkEIGAoBTAo="
|
|
597
597
|
},
|
|
598
|
+
"f006e": {
|
|
599
|
+
"glyf": "AAIAAAGzAlgDdQAOABoAAAEhIg8BFxYzITI2NRE0JgMHJwcnNyc3FzcXBwIm/ogYDoiIDhgBeBQeHmAiWloiWloiWloiWAN1FsvLFh0VAV4VHf7FI1paI1paI1paI1o="
|
|
600
|
+
},
|
|
598
601
|
"f00e3": {
|
|
599
602
|
"glyf": "AAIAAAF4AlgDsAAJABkAAAEnJiIHARcBNjQBIgYVFAcGIx4BMzI2NTQmAk4qCBwI/uRYARoK/kYoOBQUFhRGJDRKNgN8LAgI/uRWARoKGv7gNigcEhAeIko0KDY="
|
|
600
603
|
},
|
|
@@ -661,6 +664,12 @@
|
|
|
661
664
|
"f030c": {
|
|
662
665
|
"glyf": "AAwAAAHCAlgDZgADAAcACwAPABMAFwAbAB8AIwAnACsAOwAAASM1MxUjNTMnIzUzFSM1MxUjNTMlIzUzFSM1OwIVIzUzFSMXMxUjNTMVIyUhIgYVERQWMyEyNjURNCYB/jw8PDxaPDw8PPDw/vI8PDw8Hjw8PDxaPDw8PAEO/iAYJCQYAeAYJCQC0DyWPB48ljy0PJY8ljw8ljwePJY8liQY/tQYJCQYASwYJA=="
|
|
663
666
|
},
|
|
667
|
+
"f0311": {
|
|
668
|
+
"glyf": "AAEAAAHWAlgDUgAKAAABFSE3JwcXNychNQIY/mByLL6+LHIB4AMyfnAuvr4ucL4="
|
|
669
|
+
},
|
|
670
|
+
"f0312": {
|
|
671
|
+
"glyf": "AAIAAAHoAlgDQAADAAwAAAEzESMHFyEVIQcXNycCIDg48mj+agGWaCqqqgHoAVgqZjhoKKys"
|
|
672
|
+
},
|
|
664
673
|
"f0331": {
|
|
665
674
|
"glyf": "AAIAAAE+AlgD6gAIABYAAAEyNjQmIgYUFhcuASMRMhYXPgEzESIGASwqOjpUOjoqPJpWVpo8PphWVpgDIjtTOjpTO3U4Pf6SPjg4PgFuPQ=="
|
|
666
675
|
},
|
|
@@ -700,6 +709,18 @@
|
|
|
700
709
|
"f0616": {
|
|
701
710
|
"glyf": "AAIAAAFoAlgDwAAIABEAABM1IzcnBzUjFQE3FTM1IxUzB+h2ljCURAGAlETodpYBaEOWL5Z36QFQlnfpQ5Y="
|
|
702
711
|
},
|
|
712
|
+
"f0633": {
|
|
713
|
+
"glyf": "AAYAAAFoAlgDwAAoADEANQA+AEcAUAAAEzIWHQEzNTQ2MhYUBisBFTMyFhQGIiY9ASMVFAYiJjQ2OwE1IyImNDYBFBYyNjQmKwEnIxUzByIGFBYyNj0BETQmIgYUFjsBITI2NCYiBh0BeDBIeEZiSEgwPDwwSEhiRnhIYkZGMjw8MkZGAV4iNCIiGjw8eHjwGiIiMiQkMiIiGjwBLBgkIjQiA8BGMT09MUZGYkd5R2JHRzE9PTFHR2JHeUdiRv4gGSMjMiS0eTskMiMjGT0BLBkjIzMjIzMjIxk9"
|
|
714
|
+
},
|
|
715
|
+
"f0634": {
|
|
716
|
+
"glyf": "AAEAAAHjAlgDRQAHAAABByInByciAQJYNQH29jUBASwCGTb09DYBLA=="
|
|
717
|
+
},
|
|
718
|
+
"f0635": {
|
|
719
|
+
"glyf": "AAIAAAGJAlgDnwAHAAsAABEzEzMVIwMjJTMVI8zsoMzqogFw6OgDn/4tQwHTQ0M="
|
|
720
|
+
},
|
|
721
|
+
"f0636": {
|
|
722
|
+
"glyf": "AAIAAAGkAlgDhAAGAA0AAAE1MycHMxUTASMVITUjAYZAmpxCWgEslv7UlgHgtJqatAGk/tS0tA=="
|
|
723
|
+
},
|
|
703
724
|
"f0638": {
|
|
704
725
|
"glyf": "AAP/+wGDAlwDpQAFABsAIQAAAScRNzYmBRMeARcyPwE+ATc0JwMuASMiDwEOASU0JisBFwI0KEYKE/28jgccEAwL1A4UAQSQBR4PCwzTFRQB2SIYKWMDaBH+/qgVLGL+qRATAQVYBxwQCwsBWBAUBFgKLFgYIu8="
|
|
705
726
|
},
|
|
@@ -748,9 +769,15 @@
|
|
|
748
769
|
"f0d57": {
|
|
749
770
|
"glyf": "AAQAAAFoAlgDwAAQACAAJAAoAAABBxc1PgI0LgEnFR4BFAYHJTQ2NzUOAhUUFhcnNy4BASMRNxE1JxUBaHh4RG4+Pm5EUGRmTv7UZFBEbj5uVhQkRFQBDjw8PAKUd3ldByo/Rz8qBzwHMj4xB1cfMQg8Byo/JDBNDxUjCi4BSP63O/62Hj1b"
|
|
750
771
|
},
|
|
772
|
+
"f0e7e": {
|
|
773
|
+
"glyf": "AAIAAAGzAlgDdQAOABoAABMhMj8BJyYjISIGFREUFhM3FzcXBxcHJwcnNzIBeBQUhoYUFP6IFB4eYCRYWiRaWiRaWCRYAbMWy8sWHRX+ohUdATsjWlojWlojWlojWg=="
|
|
774
|
+
},
|
|
751
775
|
"f0eab": {
|
|
752
776
|
"glyf": "AAUAAAFoAlgDwAALACAAJAAqAC4AAAEzFTMVIxUjNSM1MwEhMhYdASYnNSERMxYXIyImNRE0NhchFSEVIRUGByMVMxUjAbo+YGA+Xl7+hgG4HCQeIv5I4AQS9hwkJFoBPP7EATwoIvKengJkXz5fXz4BuyUb9hEF4P5HIh4lGwG5GyV+QD8jBhY/QA=="
|
|
753
777
|
},
|
|
778
|
+
"f1050": {
|
|
779
|
+
"glyf": "AAEAAAIwAlgC+AANAAARMxUhNTMVFAYjISImNUQB0EQoHP4wHCgC+IWFhRwnJxw="
|
|
780
|
+
},
|
|
754
781
|
"f12b7": {
|
|
755
782
|
"glyf": "AAMAAAILAlgDHQALACMAOwAAETMVIxUzFSMVMxUjATMVIxUzMhYdARQGKwE1MzUjIiY9ATQ2OwEyFh0BIzUjFTM1MxUUBisBIiY9ATQ2pGxsbGykARJsbDQYICAYbGw0GCAg8DYYIDg2NjggGDYWICADHTc3Njc3ARI3NyAWNxcgNzcgFjcXICAXGxukGxsXICEWpBcg"
|
|
756
783
|
},
|
package/src/theme/symbols.ts
CHANGED
|
@@ -99,7 +99,6 @@ export type SymbolKey =
|
|
|
99
99
|
| "icon.advisorClosed"
|
|
100
100
|
| "icon.time"
|
|
101
101
|
| "icon.omp"
|
|
102
|
-
| "icon.esc"
|
|
103
102
|
| "icon.ghost"
|
|
104
103
|
| "icon.agents"
|
|
105
104
|
| "icon.job"
|
|
@@ -285,7 +284,28 @@ export type SymbolKey =
|
|
|
285
284
|
| "tool.goal"
|
|
286
285
|
| "tool.irc"
|
|
287
286
|
| "tool.delete"
|
|
288
|
-
| "tool.move"
|
|
287
|
+
| "tool.move"
|
|
288
|
+
// Keyboard keys (rendered by formatKeyHint). `*Mac` variants replace the
|
|
289
|
+
// generic modifier on macOS, whose keycaps are labelled with ⌃ ⌥ ⌘.
|
|
290
|
+
| "key.ctrl"
|
|
291
|
+
| "key.ctrlMac"
|
|
292
|
+
| "key.shift"
|
|
293
|
+
| "key.alt"
|
|
294
|
+
| "key.altMac"
|
|
295
|
+
| "key.super"
|
|
296
|
+
| "key.superMac"
|
|
297
|
+
// Gap after a glyph modifier (`⌃⇧C`); nerd icons render too small to abut
|
|
298
|
+
| "key.joiner"
|
|
299
|
+
| "key.enter"
|
|
300
|
+
| "key.esc"
|
|
301
|
+
| "key.tab"
|
|
302
|
+
| "key.backspace"
|
|
303
|
+
| "key.delete"
|
|
304
|
+
| "key.space"
|
|
305
|
+
| "key.up"
|
|
306
|
+
| "key.down"
|
|
307
|
+
| "key.left"
|
|
308
|
+
| "key.right";
|
|
289
309
|
|
|
290
310
|
export type SymbolMap = Record<SymbolKey, string>;
|
|
291
311
|
/**
|
|
@@ -459,7 +479,6 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
459
479
|
"icon.advisorClosed": "🙈",
|
|
460
480
|
"icon.time": "⏱",
|
|
461
481
|
"icon.omp": "π",
|
|
462
|
-
"icon.esc": "⎋",
|
|
463
482
|
"icon.ghost": "👻",
|
|
464
483
|
"icon.agents": "👥",
|
|
465
484
|
"icon.job": "⚙",
|
|
@@ -646,6 +665,25 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
646
665
|
"tool.irc": "✉",
|
|
647
666
|
"tool.delete": "🗑",
|
|
648
667
|
"tool.move": "➜",
|
|
668
|
+
// Keys: word modifiers off macOS (⌃/⌥/⌘ name no key on a PC keyboard)
|
|
669
|
+
"key.ctrl": "Ctrl",
|
|
670
|
+
"key.ctrlMac": "⌃",
|
|
671
|
+
"key.shift": "⇧",
|
|
672
|
+
"key.alt": "Alt",
|
|
673
|
+
"key.altMac": "⌥",
|
|
674
|
+
"key.super": "Super",
|
|
675
|
+
"key.superMac": "⌘",
|
|
676
|
+
"key.joiner": "",
|
|
677
|
+
"key.enter": "⏎",
|
|
678
|
+
"key.esc": "⎋",
|
|
679
|
+
"key.tab": "⇥",
|
|
680
|
+
"key.backspace": "⌫",
|
|
681
|
+
"key.delete": "⌦",
|
|
682
|
+
"key.space": "␣",
|
|
683
|
+
"key.up": "↑",
|
|
684
|
+
"key.down": "↓",
|
|
685
|
+
"key.left": "←",
|
|
686
|
+
"key.right": "→",
|
|
649
687
|
};
|
|
650
688
|
|
|
651
689
|
const NERD_SYMBOLS: SymbolMap = {
|
|
@@ -822,8 +860,6 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
822
860
|
// U+F03FF before (2ec52b8bdd) and reverted because it renders the wrong glyph;
|
|
823
861
|
// do not swap it again.
|
|
824
862
|
"icon.omp": "\u{f0d57}",
|
|
825
|
-
// pick: (nf-md-keyboard_esc) | alt: ⎋
|
|
826
|
-
"icon.esc": "\u{f12b7}",
|
|
827
863
|
// pick: (nf-md-ghost) | alt: 👻
|
|
828
864
|
"icon.ghost": "\u{f02a0}",
|
|
829
865
|
// pick: | alt:
|
|
@@ -1098,6 +1134,35 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
1098
1134
|
"tool.irc": "\uF086",
|
|
1099
1135
|
"tool.delete": "\uf12d",
|
|
1100
1136
|
"tool.move": "\uf061",
|
|
1137
|
+
// Keys: word modifiers off macOS; arrows stay Unicode (nerd arrows add nothing)
|
|
1138
|
+
"key.ctrl": "Ctrl",
|
|
1139
|
+
// pick: (nf-md-apple_keyboard_control) | alt: ⌃
|
|
1140
|
+
"key.ctrlMac": "\u{f0634}",
|
|
1141
|
+
// pick: (nf-md-apple_keyboard_shift) | alt: ⇧
|
|
1142
|
+
"key.shift": "\u{f0636}",
|
|
1143
|
+
"key.alt": "Alt",
|
|
1144
|
+
// pick: (nf-md-apple_keyboard_option) | alt: ⌥
|
|
1145
|
+
"key.altMac": "\u{f0635}",
|
|
1146
|
+
"key.super": "Super",
|
|
1147
|
+
// pick: (nf-md-apple_keyboard_command) | alt: ⌘
|
|
1148
|
+
"key.superMac": "\u{f0633}",
|
|
1149
|
+
"key.joiner": " ",
|
|
1150
|
+
// pick: (nf-md-keyboard_return) | alt: ⏎
|
|
1151
|
+
"key.enter": "\u{f0311}",
|
|
1152
|
+
// pick: (nf-md-keyboard_esc) | alt: ⎋
|
|
1153
|
+
"key.esc": "\u{f12b7}",
|
|
1154
|
+
// pick: (nf-md-keyboard_tab) | alt: ⇥
|
|
1155
|
+
"key.tab": "\u{f0312}",
|
|
1156
|
+
// pick: (nf-md-backspace) | alt: ⌫
|
|
1157
|
+
"key.backspace": "\u{f006e}",
|
|
1158
|
+
// pick: (nf-md-backspace_reverse) | alt: ⌦
|
|
1159
|
+
"key.delete": "\u{f0e7e}",
|
|
1160
|
+
// pick: (nf-md-keyboard_space) | alt: ␣
|
|
1161
|
+
"key.space": "\u{f1050}",
|
|
1162
|
+
"key.up": "↑",
|
|
1163
|
+
"key.down": "↓",
|
|
1164
|
+
"key.left": "←",
|
|
1165
|
+
"key.right": "→",
|
|
1101
1166
|
};
|
|
1102
1167
|
|
|
1103
1168
|
const ASCII_SYMBOLS: SymbolMap = {
|
|
@@ -1192,7 +1257,6 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
1192
1257
|
"icon.advisorClosed": "(adv)",
|
|
1193
1258
|
"icon.time": "t:",
|
|
1194
1259
|
"icon.omp": "pi",
|
|
1195
|
-
"icon.esc": "esc",
|
|
1196
1260
|
"icon.ghost": "@",
|
|
1197
1261
|
"icon.agents": "AG",
|
|
1198
1262
|
"icon.job": "bg",
|
|
@@ -1374,6 +1438,24 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
1374
1438
|
"tool.irc": "#",
|
|
1375
1439
|
"tool.delete": "rm",
|
|
1376
1440
|
"tool.move": "mv",
|
|
1441
|
+
"key.ctrl": "Ctrl",
|
|
1442
|
+
"key.ctrlMac": "Ctrl",
|
|
1443
|
+
"key.shift": "Shift",
|
|
1444
|
+
"key.alt": "Alt",
|
|
1445
|
+
"key.altMac": "Option",
|
|
1446
|
+
"key.super": "Super",
|
|
1447
|
+
"key.superMac": "Cmd",
|
|
1448
|
+
"key.joiner": "+",
|
|
1449
|
+
"key.enter": "Enter",
|
|
1450
|
+
"key.esc": "Esc",
|
|
1451
|
+
"key.tab": "Tab",
|
|
1452
|
+
"key.backspace": "Backspace",
|
|
1453
|
+
"key.delete": "Delete",
|
|
1454
|
+
"key.space": "Space",
|
|
1455
|
+
"key.up": "Up",
|
|
1456
|
+
"key.down": "Down",
|
|
1457
|
+
"key.left": "Left",
|
|
1458
|
+
"key.right": "Right",
|
|
1377
1459
|
};
|
|
1378
1460
|
|
|
1379
1461
|
export const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap> = {
|