mulmoterminal 6.3.0 → 6.4.0
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/common/keymap.ts +113 -19
- package/common/previewTheme.ts +39 -0
- package/common/themeVars.ts +7 -2
- package/dist/assets/{abnfDiagram-O67JEVCF-DxIs7k89.js → abnfDiagram-O67JEVCF-0EIO6Rib.js} +1 -1
- package/dist/assets/architecture-7GRP2DOG-BrJq_YrR.js +1 -0
- package/dist/assets/{architectureDiagram-NJMV4G6O-nEIJ1IPC.js → architectureDiagram-NJMV4G6O-Dv4vmoGJ.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-E59vkErT.js → blockDiagram-BEXU5L5S-DtAUYedX.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-JwRKcRsT.js → c4Diagram-YGBWAQC7-BUjpgg36.js} +1 -1
- package/dist/assets/channel-1p-80xtZ.js +1 -0
- package/dist/assets/{chunk-3FUC2YCW-CCLEOkDF.js → chunk-3FUC2YCW-BRaUCmDn.js} +1 -1
- package/dist/assets/{chunk-5DYCD2WN-1AgUN0r-.js → chunk-5DYCD2WN-BcCVJxNU.js} +1 -1
- package/dist/assets/{chunk-742MDFTN-DSUTqyro.js → chunk-742MDFTN-r7J0AnEC.js} +1 -1
- package/dist/assets/{chunk-7INBJB4K-4ocpGodg.js → chunk-7INBJB4K-BgjDPpSK.js} +1 -1
- package/dist/assets/{chunk-7M6MHVWA-DhIWQ16X.js → chunk-7M6MHVWA-BkohiMAa.js} +1 -1
- package/dist/assets/{chunk-7PRAP22T-CMK5Hvie.js → chunk-7PRAP22T-BgtGs2QT.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Dww8IZlE.js → chunk-GTNCS2PH-CabGHxDI.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BOY8sjUW.js → chunk-GWA4HPMP-ByBWBjP8.js} +1 -1
- package/dist/assets/{chunk-MBY4JIJT-CyPFVaTq.js → chunk-MBY4JIJT-CoqxPGVJ.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-CYeZvn4f.js → chunk-NETBCI7D-C3TzGKdM.js} +1 -1
- package/dist/assets/{chunk-O7XYJQB3-LuTg7vRC.js → chunk-O7XYJQB3-C_ibJVHM.js} +1 -1
- package/dist/assets/{chunk-UA2S7LBM-BeT6kQnw.js → chunk-UA2S7LBM-cdcwqQWa.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-DPztbhwz.js → chunk-WEXAMYUT-DpgQkB4Y.js} +1 -1
- package/dist/assets/{chunk-Z7XXMR3K-D02R_oPs.js → chunk-Z7XXMR3K-43mpKAmk.js} +1 -1
- package/dist/assets/{chunk-ZIGJFQKS-CAGBrgq6.js → chunk-ZIGJFQKS-BqjohiQA.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-cNPpvvCl.js → classDiagram-v2-NBCMYWYE--RyRI2eN.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-4vqsfXcN.js → cynefin-OW5HDTMX-Dr5PAqCO.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-BsaAwSEj.js → cynefinDiagram-VND7K2PF-CsJyNlep.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-B5er0lNr.js → dagre-6A5THRUB-C5ja50BF.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BP4RiqIl.js → diagram-22UHCM2B-C4cl0V63.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-DDKPFcUe.js → diagram-3UASUU5V-DV_eMiwc.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BPuCp6dP.js → diagram-ATOU4E4O-jzKf3iFX.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-TIsGX923.js → diagram-CDSNMT55-BwFqusNo.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-DIIMtWIN.js → diagram-MLGK6HIB-CoMwibZN.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-BlSps7Ge.js → diagram-MPIPVDR6-CR1GehW4.js} +1 -1
- package/dist/assets/{dist-1Wq3lKB9.js → dist-4y2pKI__.js} +1 -1
- package/dist/assets/{dist-mXycgwze.js → dist-BtevH0MI.js} +1 -1
- package/dist/assets/{dist-g1xDz2eh.js → dist-Cl-PfToj.js} +1 -1
- package/dist/assets/{dist-VlPZWpqj.js → dist-DzuN4wOr.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-CoQ9a6l2.js → ebnfDiagram-ZINNZB2B-DofcoTlD.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-CQpQ3e0G.js → elk-276RUBZZ-BPEoPGYi.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-OnRfD1Na.js → erDiagram-OPXOYQCR-BW9UZdWF.js} +1 -1
- package/dist/assets/eventmodeling-NTZA5JFV-B13TyR96.js +1 -0
- package/dist/assets/flowDiagram-KWPJA3E3-CU0tsDCp.js +1 -0
- package/dist/assets/{ganttDiagram-FUAMR5RP-DTtKLRRT.js → ganttDiagram-FUAMR5RP-QI2xjALT.js} +1 -1
- package/dist/assets/{gitGraph-4MIJSDKK-qTac1fxV.js → gitGraph-4MIJSDKK-DwWl22rs.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-DPIUDRK2.js → gitGraphDiagram-X574FWY7-OdBtq_J3.js} +1 -1
- package/dist/assets/index-BMqji566.css +1 -0
- package/dist/assets/{index-B5tJhuzV.js → index-DZ3P7G29.js} +342 -342
- package/dist/assets/{info-A6RAGUB7-oMNiXi0M.js → info-A6RAGUB7-D4dgNXV5.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-B0NktbNw.js → infoDiagram-VRGFBTTK-BLafPiGw.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-b4emOwCC.js → ishikawaDiagram-OU5B5YK6-hNUEv0D2.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CJS8YQyv.js → journeyDiagram-ZHPQQLJL-COYfA96w.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-ByUpoGJS.js → kanban-definition-PNTS6WVX-C5jDklyw.js} +1 -1
- package/dist/assets/{line-8OW2LsyA.js → line-D8KBsMtt.js} +1 -1
- package/dist/assets/{mermaid-parser.core-B5FlVjCr.js → mermaid-parser.core-Dlf5XX-m.js} +3 -3
- package/dist/assets/{mermaid.core--DeGxg9T.js → mermaid.core-77q0N4Kj.js} +4 -4
- package/dist/assets/{mindmap-definition-NLK3R4M7-BboAGlJq.js → mindmap-definition-NLK3R4M7-Bh3ST-uc.js} +1 -1
- package/dist/assets/{packet-AYTQ26CC-Dx1cTPUp.js → packet-AYTQ26CC-dzdfTl_4.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BVLZicUF.js → pegDiagram-GJSIUBJH-HUFfoKUw.js} +1 -1
- package/dist/assets/{pie-WAS4IAKB-BSxTLBfV.js → pie-WAS4IAKB-0S5KvqDy.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-BfSPFVSo.js → pieDiagram-5QR66LMP-BAUKrsuB.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-BcEr3u-n.js → quadrantDiagram-O4NWA36T-WKXQPbHy.js} +1 -1
- package/dist/assets/{radar-RG4KPBEZ-6ha11zcy.js → radar-RG4KPBEZ-CmvfbUxs.js} +1 -1
- package/dist/assets/{railroad-74A4TZTK-CAfLdF7d.js → railroad-74A4TZTK-B1eU1nZI.js} +1 -1
- package/dist/assets/railroad-abnf-HS5TGJTU-Buwlc453.js +1 -0
- package/dist/assets/railroad-ebnf-LZEXJU2U-CTzd-w0L.js +1 -0
- package/dist/assets/railroad-peg-WCYAUIDC-BzSzMdhx.js +1 -0
- package/dist/assets/{railroadDiagram-XR7U4H2S-CqOU7QD4.js → railroadDiagram-XR7U4H2S-DI3y2Box.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-cd-45UL-.js → requirementDiagram-PLB6GJNP-BHVnyaGP.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-CRSXwjHr.js → sankeyDiagram-IPEJSGJF-DPhG8F4e.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-Bd9pHNff.js → sequenceDiagram-PO4LG4MO-BNnXeMyc.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-BHX-09AB.js → stateDiagram-v2-GCMORJYK-Dg80rzkM.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-Wa3AByx7.js → swimlanes-2SLR337P-BjIVtOvB.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-_i8YJGRx.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CrTeIW7f.js → timeline-definition-EJHVYXUP-CWVsm4DO.js} +1 -1
- package/dist/assets/{treeView-Q6P3EWNA-s8edkU8_.js → treeView-Q6P3EWNA-VIzH5Ryp.js} +1 -1
- package/dist/assets/{treemap-WGGIJYW6-CAOvhdFB.js → treemap-WGGIJYW6-DIurGTrO.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-D58JgkZs.js → usecaseDiagram-POWQR4AR-CGjjyVXQ.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-Cx5jv6Dl.js → vennDiagram-UO4OBE2U-D25_R90R.js} +1 -1
- package/dist/assets/{wardley-WFR3VGLG-DRCvCrcV.js → wardley-WFR3VGLG-AZXL1jj9.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-qidEa5K9.js → wardleyDiagram-VNRHLVJA-D3g7xxxA.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DbAy_Psm.js → xychartDiagram-PMCCYNJV-C5FxR_Fd.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/config/keymap-check.ts +1 -1
- package/server/files/files-browse.ts +8 -5
- package/server/files/renderedDoc.ts +16 -1
- package/server/skills/mulmoterminal-keys/SKILL.md +28 -4
- package/dist/assets/architecture-7GRP2DOG-pJx6zX85.js +0 -1
- package/dist/assets/channel-C3MrC-mz.js +0 -1
- package/dist/assets/eventmodeling-NTZA5JFV-rG0naOXp.js +0 -1
- package/dist/assets/flowDiagram-KWPJA3E3-CRzZTuXz.js +0 -1
- package/dist/assets/index-C34PsKMj.css +0 -1
- package/dist/assets/railroad-abnf-HS5TGJTU-BuNXWChV.js +0 -1
- package/dist/assets/railroad-ebnf-LZEXJU2U-ILhcFgfq.js +0 -1
- package/dist/assets/railroad-peg-WCYAUIDC-i86jT9nY.js +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BNVwSwZd.js +0 -8
package/common/keymap.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// The server sanitizes and persists it; the browser matches keydowns against it. One
|
|
8
8
|
// definition here so the accepted syntax can't drift between the two.
|
|
9
9
|
//
|
|
10
|
-
// "keymap": { "zoom-next": "PageDown", "zoom-prev": "Shift+PageUp" }
|
|
10
|
+
// "keymap": { "zoom-next": "PageDown", "zoom-prev": "Shift+PageUp", "files-find": "Cmd+K p" }
|
|
11
11
|
|
|
12
12
|
// Actions a key can be bound to. Adding one here is all it takes for the config to accept it.
|
|
13
13
|
import { isRecord } from "./isRecord.js";
|
|
@@ -26,6 +26,7 @@ export const KEYMAP_ACTIONS = [
|
|
|
26
26
|
"terminal-restart",
|
|
27
27
|
"files-find",
|
|
28
28
|
"files-search",
|
|
29
|
+
"command-palette",
|
|
29
30
|
"copy",
|
|
30
31
|
"paste",
|
|
31
32
|
] as const;
|
|
@@ -128,6 +129,9 @@ export function parseKeyBinding(input: string): KeyBinding | null {
|
|
|
128
129
|
if (parts.length === 0 || parts.some((part) => part === "")) return null;
|
|
129
130
|
const key = parts[parts.length - 1];
|
|
130
131
|
if (key === undefined) return null; // unreachable: parts.length was checked above
|
|
132
|
+
// No browser reports a key with whitespace in it, so `"Cmd+K p"` read as ONE keystroke named
|
|
133
|
+
// "K p" was accepted and never fired. A sequence is spelled with a space; see parseKeySequence.
|
|
134
|
+
if (/\s/.test(key)) return null;
|
|
131
135
|
const binding: KeyBinding = { key, shift: false, alt: false, ctrl: false, meta: false };
|
|
132
136
|
for (const part of parts.slice(0, -1)) {
|
|
133
137
|
const flag = MODIFIERS[part.toLowerCase()];
|
|
@@ -138,6 +142,47 @@ export function parseKeyBinding(input: string): KeyBinding | null {
|
|
|
138
142
|
return MODIFIERS[key.toLowerCase()] ? null : binding;
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
// The most keystrokes one binding can be. Two is a prefix and a key after it, as in tmux or Emacs's
|
|
146
|
+
// `C-x b` — enough to put many actions behind one key the browser lets through (#2265).
|
|
147
|
+
export const MAX_SEQUENCE_STROKES = 2;
|
|
148
|
+
|
|
149
|
+
// Parse `"Cmd+K p"` into its keystrokes, or null when it is malformed. Strokes are separated by
|
|
150
|
+
// whitespace, which no single keystroke can contain. A single keystroke is a sequence of one.
|
|
151
|
+
export function parseKeySequence(input: string): KeyBinding[] | null {
|
|
152
|
+
const strokes = input.trim().split(/\s+/);
|
|
153
|
+
if (strokes.length > MAX_SEQUENCE_STROKES) return null;
|
|
154
|
+
const parsed = strokes.map(parseKeyBinding);
|
|
155
|
+
return parsed.every((stroke): stroke is KeyBinding => stroke !== null) ? parsed : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Esc with no modifier always ends a sequence's wait, so it can never be a sequence's second key.
|
|
159
|
+
export const isBareEscape = (e: KeymapKeyEvent): boolean => e.key === "Escape" && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey;
|
|
160
|
+
|
|
161
|
+
// Actions that can be bound to a sequence. Not `copy` / `paste`: they are decided inside the
|
|
162
|
+
// terminal, one keystroke at a time (TERMINAL_SCOPED_ACTIONS), and nothing there can wait.
|
|
163
|
+
export const takesSequence = (action: KeymapAction): boolean => !TERMINAL_SCOPED_ACTIONS.includes(action);
|
|
164
|
+
|
|
165
|
+
/** One action bound to two keystrokes. */
|
|
166
|
+
export interface SequenceBinding {
|
|
167
|
+
action: KeymapAction;
|
|
168
|
+
first: KeyBinding;
|
|
169
|
+
second: KeyBinding;
|
|
170
|
+
/** How the user wrote each keystroke, for the hint shown while waiting for the second. */
|
|
171
|
+
firstLabel: string;
|
|
172
|
+
secondLabel: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Every two-keystroke binding in a keymap, in action order.
|
|
176
|
+
export function sequenceBindings(keymap: Keymap): SequenceBinding[] {
|
|
177
|
+
return KEYMAP_ACTIONS.flatMap((action): SequenceBinding[] => {
|
|
178
|
+
const raw = keymap[action];
|
|
179
|
+
const strokes = raw === undefined || !takesSequence(action) ? null : parseKeySequence(raw);
|
|
180
|
+
const [first, second] = strokes ?? [];
|
|
181
|
+
const [firstLabel, secondLabel] = raw?.trim().split(/\s+/) ?? [];
|
|
182
|
+
return first && second && firstLabel && secondLabel ? [{ action, first, second, firstLabel, secondLabel }] : [];
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
141
186
|
// The structural shape of a keydown a binding is matched against. A real KeyboardEvent
|
|
142
187
|
// satisfies it, and so does a plain test object — no DOM dependency.
|
|
143
188
|
export interface KeymapKeyEvent {
|
|
@@ -159,6 +204,7 @@ export function actionForKey(keymap: Keymap, e: KeymapKeyEvent): KeymapAction |
|
|
|
159
204
|
for (const action of KEYMAP_ACTIONS) {
|
|
160
205
|
const raw = keymap[action];
|
|
161
206
|
if (raw === undefined) continue;
|
|
207
|
+
// A sequence is resolved by prefixStep (src/composables/prefixKeys.ts), never here.
|
|
162
208
|
const binding = parseKeyBinding(raw);
|
|
163
209
|
if (binding && matchesBinding(binding, e)) return action;
|
|
164
210
|
}
|
|
@@ -215,26 +261,62 @@ export function validateKeymap(input: unknown): KeymapProblem[] {
|
|
|
215
261
|
// deliberately, so Ctrl+C stays an interrupt — and the same-key send fires then. Naming `copy`
|
|
216
262
|
// as the winner there tells the user the opposite of what they will see half the time (#1901).
|
|
217
263
|
const bound = new Map<string, Claim[]>();
|
|
218
|
-
const claim = (
|
|
219
|
-
const key =
|
|
264
|
+
const claim = (strokes: KeyBinding[], entry: Claim): void => {
|
|
265
|
+
const key = canonicalSequence(strokes); // as PARSED: "Shift+PageUp" and "shift+pageup" are one keystroke
|
|
220
266
|
bound.set(key, [...(bound.get(key) ?? []), entry]);
|
|
221
267
|
};
|
|
222
|
-
const problems = entries.flatMap(([action, binding]): KeymapProblem[] =>
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
268
|
+
const problems = entries.flatMap(([action, binding]): KeymapProblem[] =>
|
|
269
|
+
action === "send" ? sendProblems(binding, claim) : actionProblems(action, binding, claim),
|
|
270
|
+
);
|
|
271
|
+
return [...problems, ...duplicateWarnings(bound), ...prefixWarnings(bound)];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Everything wrong with one action's binding, claiming its keystrokes when it is well-formed.
|
|
275
|
+
function actionProblems(action: string, binding: unknown, claim: (strokes: KeyBinding[], entry: Claim) => void): KeymapProblem[] {
|
|
276
|
+
if (!isKeymapAction(action)) {
|
|
277
|
+
return [{ action, binding, reason: `unknown action (known: ${KEYMAP_ACTIONS.join(", ")}, send)`, fatal: false }];
|
|
278
|
+
}
|
|
279
|
+
if (typeof binding !== "string") return [{ action, binding, reason: "binding must be a string", fatal: true }];
|
|
280
|
+
const strokes = parseKeySequence(binding);
|
|
281
|
+
if (strokes === null) {
|
|
282
|
+
return [{ action, binding, reason: 'unparseable key binding — expected e.g. "PageDown", "Shift+PageUp" or two keys "Cmd+K p"', fatal: true }];
|
|
283
|
+
}
|
|
284
|
+
if (strokes.length > 1 && !takesSequence(action)) {
|
|
285
|
+
return [{ action, binding, reason: "takes a single keystroke — it is decided inside the terminal, which cannot wait for a second key", fatal: true }];
|
|
286
|
+
}
|
|
287
|
+
claim(strokes, { label: action, binding, rank: KEYMAP_ACTIONS.indexOf(action), kind: "action" });
|
|
288
|
+
return [...strokes.flatMap((stroke) => unshiftedUnderCmdWarnings(action, binding, stroke)), ...escapeSecondWarnings(action, binding, strokes)];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const escapeSecondWarnings = (action: string, binding: string, [, second]: KeyBinding[]): KeymapProblem[] =>
|
|
292
|
+
second && isBareEscape({ key: second.key, shiftKey: second.shift, altKey: second.alt, ctrlKey: second.ctrl, metaKey: second.meta })
|
|
293
|
+
? [{ action, binding, reason: "never fires — a bare Escape always cancels a sequence; add a modifier or pick another second key", fatal: false }]
|
|
294
|
+
: [];
|
|
295
|
+
|
|
296
|
+
// A sequence whose FIRST key is also a keystroke of its own. The single keystroke is claimed before
|
|
297
|
+
// any sequence can start (see GridView's handler), so the sequence never gets its first key.
|
|
298
|
+
function prefixWarnings(bound: Map<string, Claim[]>): KeymapProblem[] {
|
|
299
|
+
return [...bound.entries()].flatMap(([key, claims]) => {
|
|
300
|
+
const [first, second] = key.split(SEQUENCE_SEPARATOR);
|
|
301
|
+
const singles = second === undefined || first === undefined ? [] : (bound.get(first) ?? []);
|
|
302
|
+
if (singles.length === 0) return [];
|
|
303
|
+
const reason = prefixCollision(singles);
|
|
304
|
+
return claims.map((claim) => ({ action: claim.label, binding: claim.binding, reason, fatal: false }));
|
|
234
305
|
});
|
|
235
|
-
return [...problems, ...duplicateWarnings(bound)];
|
|
236
306
|
}
|
|
237
307
|
|
|
308
|
+
// Deliberately NOT a prediction of the states the sequence still starts in. Which single binding
|
|
309
|
+
// acts depends on the zoom state, on a selection for `copy`, and on which of several claims dispatch
|
|
310
|
+
// reaches — twice a narrower sentence was wrong for a combination it did not list (codex on #2283).
|
|
311
|
+
// Every claim is named, in dispatch order, and the advice is the one that is always right.
|
|
312
|
+
const prefixCollision = (singles: Claim[]): string => {
|
|
313
|
+
const names = [...singles]
|
|
314
|
+
.sort((x, y) => x.rank - y.rank)
|
|
315
|
+
.map((claim) => `\`${claim.label}\``)
|
|
316
|
+
.join(", ");
|
|
317
|
+
return `its first key is also bound on its own, to ${names} — those take the key whenever they act, so this sequence may never start; give it a first key nothing else uses`;
|
|
318
|
+
};
|
|
319
|
+
|
|
238
320
|
// A binding that says one keystroke and waits for another. While Cmd is held, a macOS browser puts
|
|
239
321
|
// the UNSHIFTED character in `KeyboardEvent.key` — Cmd+Shift+P arrives as `"p"` — so a binding
|
|
240
322
|
// written `"Cmd+Shift+P"` waits for a `"P"` that never comes, and nothing downstream can see the
|
|
@@ -273,7 +355,7 @@ interface Claim {
|
|
|
273
355
|
// Fatal throughout, for the reason the module header gives: a send binding is invisible until
|
|
274
356
|
// the key is pressed, so a dropped one is indistinguishable from a shortcut that "doesn't work".
|
|
275
357
|
// Empty `bytes` is fatal too — it would take the key away from the terminal and put nothing back.
|
|
276
|
-
function sendProblems(input: unknown, claim: (
|
|
358
|
+
function sendProblems(input: unknown, claim: (strokes: KeyBinding[], entry: Claim) => void): KeymapProblem[] {
|
|
277
359
|
if (!Array.isArray(input)) {
|
|
278
360
|
return [{ action: "send", binding: input, reason: "`send` must be an array of { key, bytes }", fatal: true }];
|
|
279
361
|
}
|
|
@@ -288,7 +370,7 @@ function sendProblems(input: unknown, claim: (parsed: KeyBinding, entry: Claim)
|
|
|
288
370
|
return [{ action: label, binding: entry.key, reason: "`bytes` is empty — the key would be taken from the terminal and nothing sent", fatal: true }];
|
|
289
371
|
}
|
|
290
372
|
// Ranked after every action, matching who actually wins (see the `bound` comment above).
|
|
291
|
-
claim(parsed, { label, binding: entry.key, rank: KEYMAP_ACTIONS.length + i, kind: "send" });
|
|
373
|
+
claim([parsed], { label, binding: entry.key, rank: KEYMAP_ACTIONS.length + i, kind: "send" });
|
|
292
374
|
return unshiftedUnderCmdWarnings(label, entry.key, parsed);
|
|
293
375
|
});
|
|
294
376
|
}
|
|
@@ -358,13 +440,25 @@ const collisionReason = (winner: Claim, runnerUp: Claim | null, loser: Claim): s
|
|
|
358
440
|
// A binding's identity as a keystroke, for spotting two actions that claim the same one.
|
|
359
441
|
const canonicalBinding = (b: KeyBinding): string => `${b.shift ? "S" : ""}${b.alt ? "A" : ""}${b.ctrl ? "C" : ""}${b.meta ? "M" : ""}|${b.key}`;
|
|
360
442
|
|
|
443
|
+
// The same for a sequence. A keystroke's identity cannot contain the separator, because no key
|
|
444
|
+
// does (parseKeyBinding refuses whitespace), so a sequence's first key splits back out exactly.
|
|
445
|
+
const SEQUENCE_SEPARATOR = " ";
|
|
446
|
+
const canonicalSequence = (strokes: KeyBinding[]): string => strokes.map(canonicalBinding).join(SEQUENCE_SEPARATOR);
|
|
447
|
+
|
|
448
|
+
// A binding the dispatch can act on: it parses, and it is a single keystroke unless the action can
|
|
449
|
+
// wait for a second one.
|
|
450
|
+
const isUsableBinding = (action: KeymapAction, binding: string): boolean => {
|
|
451
|
+
const strokes = parseKeySequence(binding);
|
|
452
|
+
return strokes !== null && (strokes.length === 1 || takesSequence(action));
|
|
453
|
+
};
|
|
454
|
+
|
|
361
455
|
// Keep only known actions bound to a parseable, non-empty string. Unknown keys and
|
|
362
456
|
// malformed bindings are dropped rather than rejecting the whole map, matching how the
|
|
363
457
|
// rest of the config treats one bad entry.
|
|
364
458
|
export function sanitizeKeymap(input: unknown): Keymap {
|
|
365
459
|
if (!isRecord(input)) return {};
|
|
366
460
|
const entries = Object.entries(input).filter(
|
|
367
|
-
(entry): entry is [KeymapAction, string] => isKeymapAction(entry[0]) && typeof entry[1] === "string" &&
|
|
461
|
+
(entry): entry is [KeymapAction, string] => isKeymapAction(entry[0]) && typeof entry[1] === "string" && isUsableBinding(entry[0], entry[1]),
|
|
368
462
|
);
|
|
369
463
|
const send = sanitizeSendBindings(input.send);
|
|
370
464
|
return { ...Object.fromEntries(entries), ...(send.length ? { send } : {}) };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// The app's theme, carried to the Markdown preview (#2263). The preview is a sandboxed document the
|
|
2
|
+
// app cannot style from outside, so the host hands it the few colours it paints with on the URL and
|
|
3
|
+
// the server writes them into the document's own <style>.
|
|
4
|
+
//
|
|
5
|
+
// In `common/` because both ends decide from it: the pane builds the query, the server reads it.
|
|
6
|
+
// Every value lands in CSS, so a colour is accepted only in the one shape the app's theme variables
|
|
7
|
+
// use — `#` and hex digits — and any other value drops the WHOLE theme, leaving the document on the
|
|
8
|
+
// reader's system colours as before. A partial theme could put dark text on a dark background.
|
|
9
|
+
import { isPaletteColor } from "./themeColors.js";
|
|
10
|
+
import type { ThemeVarKey, ThemeVars } from "./themeVars.js";
|
|
11
|
+
|
|
12
|
+
/** Query parameter → the app theme variable it carries. */
|
|
13
|
+
export const PREVIEW_THEME_PARAMS = {
|
|
14
|
+
bg: "--bg-base",
|
|
15
|
+
fg: "--text",
|
|
16
|
+
muted: "--text-secondary",
|
|
17
|
+
subtle: "--bg-subtle",
|
|
18
|
+
border: "--border",
|
|
19
|
+
link: "--accent",
|
|
20
|
+
} as const satisfies Record<string, ThemeVarKey>;
|
|
21
|
+
|
|
22
|
+
export type PreviewThemeParam = keyof typeof PREVIEW_THEME_PARAMS;
|
|
23
|
+
export type PreviewTheme = Record<PreviewThemeParam, string>;
|
|
24
|
+
|
|
25
|
+
/** Build a theme from named values, or null unless every one is a hex colour. */
|
|
26
|
+
function themeOf(valueFor: (param: PreviewThemeParam) => unknown): PreviewTheme | null {
|
|
27
|
+
const colour = (param: PreviewThemeParam): string | null => {
|
|
28
|
+
const value = valueFor(param);
|
|
29
|
+
return isPaletteColor(value) ? value : null;
|
|
30
|
+
};
|
|
31
|
+
const [bg, fg, muted, subtle, border, link] = [colour("bg"), colour("fg"), colour("muted"), colour("subtle"), colour("border"), colour("link")];
|
|
32
|
+
return bg && fg && muted && subtle && border && link ? { bg, fg, muted, subtle, border, link } : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The preview's colours from the app's theme variables. */
|
|
36
|
+
export const previewThemeFromVars = (vars: ThemeVars): PreviewTheme | null => themeOf((param) => vars[PREVIEW_THEME_PARAMS[param]]);
|
|
37
|
+
|
|
38
|
+
/** The preview's colours from a request's query, or null when any is missing or not a hex colour. */
|
|
39
|
+
export const previewThemeFromQuery = (query: Record<string, unknown>): PreviewTheme | null => themeOf((param) => query[param]);
|
package/common/themeVars.ts
CHANGED
|
@@ -129,11 +129,16 @@ export function relativeLuminance(color: string): number | null {
|
|
|
129
129
|
// thinks about it still gets legible pills.
|
|
130
130
|
const LIGHT_LUMINANCE_THRESHOLD = 0.4;
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
/** Whether a background colour reads as light — the same line `isLightTheme` draws for the chrome. */
|
|
133
|
+
export function isLightColor(color: string): boolean {
|
|
134
|
+
const luminance = relativeLuminance(color);
|
|
134
135
|
return luminance !== null && luminance > LIGHT_LUMINANCE_THRESHOLD;
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
export function isLightTheme(vars: ThemeVars): boolean {
|
|
139
|
+
return isLightColor(vars["--bg-base"]);
|
|
140
|
+
}
|
|
141
|
+
|
|
137
142
|
/** The xterm palette a theme's variables imply. xterm draws on a canvas and cannot read CSS
|
|
138
143
|
* variables, so the same colours have to reach it as values — derived here rather than asked
|
|
139
144
|
* for twice, since two copies of one colour is two chances to change only one. The 16 ANSI
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as e,t}from"./mermaid-parser.core-
|
|
1
|
+
import{m as e,t}from"./mermaid-parser.core-Dlf5XX-m.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-CllyRBe0.js";import"./chunk-O7XYJQB3-C_ibJVHM.js";import{n as i,r as a,t as o}from"./chunk-GTNCS2PH-CabGHxDI.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-77q0N4Kj.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-FOHPRMQF-DzxwRta7.js";import{M as e}from"./mermaid-parser.core-Dlf5XX-m.js";export{e as createArchitectureServices};
|