@pixerate/editor 0.22.0 → 0.24.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/README.md +175 -2
- package/THIRD_PARTY_NOTICES.md +139 -0
- package/dist/agent/index.cjs +87 -111
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +70 -211
- package/dist/agent/index.d.ts +70 -211
- package/dist/agent/index.js +78 -102
- package/dist/agent/index.js.map +1 -1
- package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
- package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
- package/dist/canvas/index.cjs +2075 -86
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +608 -186
- package/dist/canvas/index.d.ts +608 -186
- package/dist/canvas/index.js +2060 -71
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-26BVFKHU.cjs +453 -0
- package/dist/chunk-26BVFKHU.cjs.map +1 -0
- package/dist/chunk-2H7ZHPEJ.js +762 -0
- package/dist/chunk-2H7ZHPEJ.js.map +1 -0
- package/dist/{chunk-DENSCKFC.js → chunk-3CN2WD3X.js} +1730 -189
- package/dist/chunk-3CN2WD3X.js.map +1 -0
- package/dist/{chunk-JJDLPEWL.js → chunk-3LV6RRPH.js} +2 -2
- package/dist/{chunk-MJZBNWG7.js → chunk-5S3Z4PPF.js} +78 -557
- package/dist/chunk-5S3Z4PPF.js.map +1 -0
- package/dist/chunk-664LQYFJ.js +115 -0
- package/dist/chunk-664LQYFJ.js.map +1 -0
- package/dist/chunk-7DWMJCAT.js +453 -0
- package/dist/chunk-7DWMJCAT.js.map +1 -0
- package/dist/chunk-ARRTP33I.js +101 -0
- package/dist/chunk-ARRTP33I.js.map +1 -0
- package/dist/chunk-CPOKFNBG.cjs +101 -0
- package/dist/chunk-CPOKFNBG.cjs.map +1 -0
- package/dist/{chunk-AA4ZEVCC.cjs → chunk-D27YFC46.cjs} +101 -580
- package/dist/chunk-D27YFC46.cjs.map +1 -0
- package/dist/chunk-D6FH2YAX.cjs +131 -0
- package/dist/chunk-D6FH2YAX.cjs.map +1 -0
- package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
- package/dist/chunk-FD4LPFAE.cjs.map +1 -0
- package/dist/chunk-FODNLX5N.js +749 -0
- package/dist/chunk-FODNLX5N.js.map +1 -0
- package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
- package/dist/chunk-FPENXAEJ.js.map +1 -0
- package/dist/chunk-G2I4QJOT.cjs +762 -0
- package/dist/chunk-G2I4QJOT.cjs.map +1 -0
- package/dist/chunk-HLIMTC6N.js +1 -0
- package/dist/chunk-HLIMTC6N.js.map +1 -0
- package/dist/{chunk-QGHGJ56O.js → chunk-ITBSKI26.js} +1483 -19
- package/dist/chunk-ITBSKI26.js.map +1 -0
- package/dist/{chunk-6M3BCTBD.cjs → chunk-MA2F5KHF.cjs} +470 -242
- package/dist/chunk-MA2F5KHF.cjs.map +1 -0
- package/dist/{chunk-SAZRENEZ.cjs → chunk-MCSJFWAO.cjs} +1733 -192
- package/dist/chunk-MCSJFWAO.cjs.map +1 -0
- package/dist/chunk-MKA3AHLV.cjs +256 -0
- package/dist/chunk-MKA3AHLV.cjs.map +1 -0
- package/dist/chunk-OLZJXL4R.cjs +749 -0
- package/dist/chunk-OLZJXL4R.cjs.map +1 -0
- package/dist/chunk-OXSV64JX.js +689 -0
- package/dist/chunk-OXSV64JX.js.map +1 -0
- package/dist/chunk-P3RQ5UJE.cjs +689 -0
- package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
- package/dist/chunk-P6ODPZ4W.js +256 -0
- package/dist/chunk-P6ODPZ4W.js.map +1 -0
- package/dist/chunk-PGNZKFQS.js +685 -0
- package/dist/chunk-PGNZKFQS.js.map +1 -0
- package/dist/chunk-PL4JB3BF.cjs +1 -0
- package/dist/chunk-PL4JB3BF.cjs.map +1 -0
- package/dist/{chunk-GVSMRPHB.cjs → chunk-SBCZ6RN5.cjs} +1568 -104
- package/dist/chunk-SBCZ6RN5.cjs.map +1 -0
- package/dist/{chunk-CL37JGFW.cjs → chunk-SML64VJG.cjs} +4 -4
- package/dist/{chunk-CL37JGFW.cjs.map → chunk-SML64VJG.cjs.map} +1 -1
- package/dist/chunk-TNNJXF46.cjs +115 -0
- package/dist/chunk-TNNJXF46.cjs.map +1 -0
- package/dist/chunk-U5HZSFCS.js +131 -0
- package/dist/chunk-U5HZSFCS.js.map +1 -0
- package/dist/{chunk-RU52I7QO.js → chunk-U5KWZU4X.js} +388 -160
- package/dist/chunk-U5KWZU4X.js.map +1 -0
- package/dist/chunk-XNORY7M6.cjs +685 -0
- package/dist/chunk-XNORY7M6.cjs.map +1 -0
- package/dist/commands/index.cjs +860 -0
- package/dist/commands/index.cjs.map +1 -0
- package/dist/commands/index.d.cts +244 -0
- package/dist/commands/index.d.ts +244 -0
- package/dist/commands/index.js +860 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/comment-store-BEMgVlAA.d.cts +135 -0
- package/dist/comment-store-BEMgVlAA.d.ts +135 -0
- package/dist/compare/index.cjs +15 -0
- package/dist/compare/index.cjs.map +1 -0
- package/dist/compare/index.d.cts +58 -0
- package/dist/compare/index.d.ts +58 -0
- package/dist/compare/index.js +15 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/{controller-B7GIybc4.d.cts → controller-BhLLAoi8.d.cts} +119 -5
- package/dist/{controller-B7GIybc4.d.ts → controller-BhLLAoi8.d.ts} +119 -5
- package/dist/documents-BQjzJNe2.d.ts +76 -0
- package/dist/documents-KRTPhvst.d.cts +76 -0
- package/dist/extensions/index.cjs +53 -5
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +7 -3
- package/dist/extensions/index.d.ts +7 -3
- package/dist/extensions/index.js +52 -4
- package/dist/generation/index.cjs +1373 -0
- package/dist/generation/index.cjs.map +1 -0
- package/dist/generation/index.d.cts +554 -0
- package/dist/generation/index.d.ts +554 -0
- package/dist/generation/index.js +1373 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/grammar/index.cjs +48 -3
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.d.cts +322 -100
- package/dist/grammar/index.d.ts +322 -100
- package/dist/grammar/index.js +54 -9
- package/dist/history/index.cjs +12 -2
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +211 -7
- package/dist/history/index.d.ts +211 -7
- package/dist/history/index.js +13 -3
- package/dist/image-editor/index.cjs +2244 -38
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +742 -4
- package/dist/image-editor/index.d.ts +742 -4
- package/dist/image-editor/index.js +2222 -16
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{index-CCr_z9kF.d.cts → index-BhJBSm2d.d.cts} +370 -4
- package/dist/{index-Ck6ajwXh.d.ts → index-fJrlkJCd.d.ts} +370 -4
- package/dist/index.cjs +193 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -48
- package/dist/index.d.ts +17 -48
- package/dist/index.js +198 -103
- package/dist/index.js.map +1 -1
- package/dist/migrate-iTlm3a8h.d.cts +112 -0
- package/dist/migrate-iTlm3a8h.d.ts +112 -0
- package/dist/persistence/index.cjs +676 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +347 -0
- package/dist/persistence/index.d.ts +347 -0
- package/dist/persistence/index.js +676 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/registry-BDe-vlv3.d.ts +171 -0
- package/dist/registry-D32LpIc4.d.cts +354 -0
- package/dist/registry-DnzpgY27.d.cts +171 -0
- package/dist/registry-evKTP4ls.d.ts +354 -0
- package/dist/schema-DYDGdYL9.d.cts +41 -0
- package/dist/schema-DYDGdYL9.d.ts +41 -0
- package/dist/serializers/index.cjs +4 -3
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +2 -1
- package/dist/serializers/index.d.ts +2 -1
- package/dist/serializers/index.js +3 -2
- package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
- package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
- package/dist/spreadsheet/index.cjs +53 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +247 -18
- package/dist/spreadsheet/index.d.ts +247 -18
- package/dist/spreadsheet/index.js +58 -8
- package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
- package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
- package/dist/{track-changes-BSF58RK0.d.cts → track-changes-BK5sVf4_.d.cts} +18 -136
- package/dist/{track-changes-BSF58RK0.d.ts → track-changes-CDjCrwKe.d.ts} +18 -136
- package/dist/{types-CJ4U-HHI.d.ts → types-DX4Mh4SL.d.cts} +1 -1
- package/dist/{types-CJ4U-HHI.d.cts → types-DX4Mh4SL.d.ts} +1 -1
- package/dist/types-GT5a5YAX.d.cts +86 -0
- package/dist/types-GT5a5YAX.d.ts +86 -0
- package/package.json +23 -2
- package/dist/chunk-2TWGLC5K.cjs +0 -262
- package/dist/chunk-2TWGLC5K.cjs.map +0 -1
- package/dist/chunk-4SUNJZWB.cjs.map +0 -1
- package/dist/chunk-6M3BCTBD.cjs.map +0 -1
- package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
- package/dist/chunk-AXV2ZW34.cjs +0 -118
- package/dist/chunk-AXV2ZW34.cjs.map +0 -1
- package/dist/chunk-B2ZRLN4S.js +0 -118
- package/dist/chunk-B2ZRLN4S.js.map +0 -1
- package/dist/chunk-DENSCKFC.js.map +0 -1
- package/dist/chunk-GVSMRPHB.cjs.map +0 -1
- package/dist/chunk-KAADNAPF.js +0 -262
- package/dist/chunk-KAADNAPF.js.map +0 -1
- package/dist/chunk-MJZBNWG7.js.map +0 -1
- package/dist/chunk-OYXSKQFR.js.map +0 -1
- package/dist/chunk-QGHGJ56O.js.map +0 -1
- package/dist/chunk-RU52I7QO.js.map +0 -1
- package/dist/chunk-SAZRENEZ.cjs.map +0 -1
- package/dist/types-BZgcDsC5.d.cts +0 -42
- package/dist/types-BZgcDsC5.d.ts +0 -42
- /package/dist/{chunk-JJDLPEWL.js.map → chunk-3LV6RRPH.js.map} +0 -0
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ARRANGE_SHORTCUTS,
|
|
3
|
+
PLATFORMS,
|
|
4
|
+
ShortcutMap,
|
|
5
|
+
chordToString,
|
|
6
|
+
codeFromKey,
|
|
7
|
+
codeToKeyText,
|
|
8
|
+
detectPlatform,
|
|
9
|
+
effectiveChord,
|
|
10
|
+
effectiveChordKey,
|
|
11
|
+
eventCode,
|
|
12
|
+
formatShortcut,
|
|
13
|
+
isEditableEventTarget,
|
|
14
|
+
keyToCode,
|
|
15
|
+
normalizeChord,
|
|
16
|
+
parseChord,
|
|
17
|
+
shortcutCode,
|
|
18
|
+
toShortcutBindings
|
|
19
|
+
} from "../chunk-7DWMJCAT.js";
|
|
20
|
+
import {
|
|
21
|
+
validateJsonSchema
|
|
22
|
+
} from "../chunk-ARRTP33I.js";
|
|
23
|
+
|
|
24
|
+
// src/commands/registry.ts
|
|
25
|
+
var CommandRegistryError = class extends Error {
|
|
26
|
+
code;
|
|
27
|
+
constructor(code, message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "CommandRegistryError";
|
|
30
|
+
this.code = code;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var COMMAND_ID_REGEX = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
34
|
+
function enablement(result) {
|
|
35
|
+
if (result === void 0 || result === true) return { enabled: true };
|
|
36
|
+
if (result === false) return { enabled: false, reason: "This command is not available right now." };
|
|
37
|
+
return { enabled: false, reason: String(result) };
|
|
38
|
+
}
|
|
39
|
+
function unknownParams(spec, params) {
|
|
40
|
+
const keys = Object.keys(params).filter((k) => params[k] !== void 0);
|
|
41
|
+
if (keys.length === 0) return null;
|
|
42
|
+
const schema = spec.params;
|
|
43
|
+
if (schema && schema.additionalProperties !== void 0 && schema.additionalProperties !== false) return null;
|
|
44
|
+
const accepted = Object.keys(schema?.properties ?? {});
|
|
45
|
+
const bad = keys.find((k) => !accepted.includes(k));
|
|
46
|
+
if (!bad) return null;
|
|
47
|
+
return `Unknown parameter "${bad}" for command "${spec.id}"; expected ${accepted.length ? accepted.map((k) => `"${k}"`).join(", ") : "no parameters"}.`;
|
|
48
|
+
}
|
|
49
|
+
var isThenable = (value) => !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
50
|
+
var CommandRegistry = class {
|
|
51
|
+
/** Shortcut bindings of the registered commands (plus any user overrides). */
|
|
52
|
+
shortcuts;
|
|
53
|
+
commands = /* @__PURE__ */ new Map();
|
|
54
|
+
listeners = /* @__PURE__ */ new Set();
|
|
55
|
+
options;
|
|
56
|
+
version = 0;
|
|
57
|
+
// Set while register/unregister change the shortcut map, so they notify once.
|
|
58
|
+
muted = false;
|
|
59
|
+
constructor(options = {}) {
|
|
60
|
+
this.options = options;
|
|
61
|
+
this.shortcuts = new ShortcutMap([], options.shortcuts);
|
|
62
|
+
this.shortcuts.subscribe(() => {
|
|
63
|
+
if (!this.muted) this.notify();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Monotonic counter bumped on every change (useful as a snapshot key). */
|
|
67
|
+
getVersion() {
|
|
68
|
+
return this.version;
|
|
69
|
+
}
|
|
70
|
+
/** The default context. */
|
|
71
|
+
get context() {
|
|
72
|
+
const ctx = this.options.context;
|
|
73
|
+
return typeof ctx === "function" ? ctx() : ctx;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Registers a command (or several) and returns a function that unregisters it.
|
|
77
|
+
* Throws a `CommandRegistryError` for a duplicate or invalid id, or invalid chord text.
|
|
78
|
+
*/
|
|
79
|
+
register(spec) {
|
|
80
|
+
const specs = Array.isArray(spec) ? spec : [spec];
|
|
81
|
+
const seen = /* @__PURE__ */ new Set();
|
|
82
|
+
for (const s of specs) {
|
|
83
|
+
if (!s || typeof s.id !== "string" || !COMMAND_ID_REGEX.test(s.id)) {
|
|
84
|
+
throw new CommandRegistryError("invalid_command", `Invalid command id "${s?.id}".`);
|
|
85
|
+
}
|
|
86
|
+
if (typeof s.run !== "function") {
|
|
87
|
+
throw new CommandRegistryError("invalid_command", `Command "${s.id}" has no run function.`);
|
|
88
|
+
}
|
|
89
|
+
if (this.commands.has(s.id) || seen.has(s.id)) {
|
|
90
|
+
throw new CommandRegistryError("duplicate_command", `Command "${s.id}" is already registered.`);
|
|
91
|
+
}
|
|
92
|
+
seen.add(s.id);
|
|
93
|
+
}
|
|
94
|
+
const bindings = specs.map((s) => toShortcutBindings(s.id, s.shortcut, s.scope));
|
|
95
|
+
const unbinds = [];
|
|
96
|
+
this.muted = true;
|
|
97
|
+
try {
|
|
98
|
+
for (const b of bindings) unbinds.push(this.shortcuts.bind(b));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
unbinds.forEach((u) => u());
|
|
101
|
+
throw new CommandRegistryError("invalid_command", error instanceof Error ? error.message : String(error));
|
|
102
|
+
} finally {
|
|
103
|
+
this.muted = false;
|
|
104
|
+
}
|
|
105
|
+
specs.forEach((s, i) => this.commands.set(s.id, { spec: s, unbind: unbinds[i] }));
|
|
106
|
+
this.notify();
|
|
107
|
+
let registered = true;
|
|
108
|
+
return () => {
|
|
109
|
+
if (!registered) return;
|
|
110
|
+
registered = false;
|
|
111
|
+
let changed = false;
|
|
112
|
+
for (const s of specs) {
|
|
113
|
+
const entry = this.commands.get(s.id);
|
|
114
|
+
if (entry && entry.spec === s) {
|
|
115
|
+
this.commands.delete(s.id);
|
|
116
|
+
this.unbindQuietly(entry);
|
|
117
|
+
changed = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (changed) this.notify();
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Removes a command by id. Returns whether it was registered. */
|
|
124
|
+
unregister(id) {
|
|
125
|
+
const entry = this.commands.get(id);
|
|
126
|
+
if (!entry) return false;
|
|
127
|
+
this.commands.delete(id);
|
|
128
|
+
this.unbindQuietly(entry);
|
|
129
|
+
this.notify();
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
unbindQuietly(entry) {
|
|
133
|
+
this.muted = true;
|
|
134
|
+
try {
|
|
135
|
+
entry.unbind();
|
|
136
|
+
} finally {
|
|
137
|
+
this.muted = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
has(id) {
|
|
141
|
+
return this.commands.has(id);
|
|
142
|
+
}
|
|
143
|
+
get(id) {
|
|
144
|
+
return this.commands.get(id)?.spec;
|
|
145
|
+
}
|
|
146
|
+
/** Registered ids in registration order. */
|
|
147
|
+
ids() {
|
|
148
|
+
return [...this.commands.keys()];
|
|
149
|
+
}
|
|
150
|
+
/** Whether `id` can run now (`{ enabled, reason }`); `params` are the targets a caller named. */
|
|
151
|
+
isEnabled(id, params, context = this.context) {
|
|
152
|
+
const spec = this.get(id);
|
|
153
|
+
if (!spec) return { enabled: false, reason: `Unknown command "${id}".` };
|
|
154
|
+
if (!spec.enabled) return { enabled: true };
|
|
155
|
+
try {
|
|
156
|
+
return enablement(spec.enabled(context, params));
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return { enabled: false, reason: error instanceof Error ? error.message : String(error) };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** Commands for menus, palettes and agents, with their current enablement. */
|
|
162
|
+
list(options = {}) {
|
|
163
|
+
const scopes = options.scope === void 0 ? null : typeof options.scope === "string" ? [options.scope] : options.scope;
|
|
164
|
+
const context = "context" in options ? options.context : this.context;
|
|
165
|
+
const platform = this.shortcuts.platform;
|
|
166
|
+
const out = [];
|
|
167
|
+
for (const { spec } of this.commands.values()) {
|
|
168
|
+
if (spec.hidden && !options.includeHidden) continue;
|
|
169
|
+
if (scopes && spec.scope !== void 0 && !scopes.includes(spec.scope)) continue;
|
|
170
|
+
const { enabled, reason } = this.isEnabled(spec.id, void 0, context);
|
|
171
|
+
if (options.enabledOnly && !enabled) continue;
|
|
172
|
+
out.push({
|
|
173
|
+
id: spec.id,
|
|
174
|
+
label: spec.label,
|
|
175
|
+
...spec.group !== void 0 ? { group: spec.group } : {},
|
|
176
|
+
...spec.keywords ? { keywords: spec.keywords } : {},
|
|
177
|
+
...spec.description !== void 0 ? { description: spec.description } : {},
|
|
178
|
+
...spec.params ? { params: spec.params } : {},
|
|
179
|
+
...spec.scope !== void 0 ? { scope: spec.scope } : {},
|
|
180
|
+
shortcuts: this.shortcuts.forCommand(spec.id, platform),
|
|
181
|
+
enabled,
|
|
182
|
+
...enabled ? {} : { disabledReason: reason },
|
|
183
|
+
undoable: !!spec.undoable,
|
|
184
|
+
readOnly: !!spec.readOnly
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
check(id, params, context) {
|
|
190
|
+
const spec = this.get(id);
|
|
191
|
+
if (!spec) return { ok: false, id, code: "unknown_command", message: `Unknown command "${id}".` };
|
|
192
|
+
const input = params ?? {};
|
|
193
|
+
if (typeof input !== "object" || Array.isArray(input)) {
|
|
194
|
+
return { ok: false, id, code: "invalid_params", message: `Parameters for command "${id}" must be an object.` };
|
|
195
|
+
}
|
|
196
|
+
const unknown = unknownParams(spec, input);
|
|
197
|
+
if (unknown) return { ok: false, id, code: "invalid_params", message: unknown };
|
|
198
|
+
if (spec.params) {
|
|
199
|
+
const details = validateJsonSchema(input, spec.params);
|
|
200
|
+
if (details.length > 0) {
|
|
201
|
+
return {
|
|
202
|
+
ok: false,
|
|
203
|
+
id,
|
|
204
|
+
code: "invalid_params",
|
|
205
|
+
message: `Invalid parameters for command "${id}": ${details.map((d) => `${d.path} ${d.message}`).join("; ")}`,
|
|
206
|
+
details
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const { enabled, reason } = this.isEnabled(id, params === void 0 ? void 0 : input, context);
|
|
211
|
+
if (!enabled) return { ok: false, id, code: "disabled", message: reason ?? "Disabled." };
|
|
212
|
+
return { spec, params: input };
|
|
213
|
+
}
|
|
214
|
+
invoke(spec, params, context) {
|
|
215
|
+
const history = this.options.history;
|
|
216
|
+
if (!spec.undoable || !history) return spec.run(context, params);
|
|
217
|
+
let value;
|
|
218
|
+
history.batch(spec.label, () => {
|
|
219
|
+
value = spec.run(context, params);
|
|
220
|
+
});
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Runs a command synchronously and returns a structured result; never throws.
|
|
225
|
+
* A command whose `run` returns a promise yields `{ ok: true, result: promise }`
|
|
226
|
+
* (use `executeAsync` to await it and catch its rejection).
|
|
227
|
+
*/
|
|
228
|
+
execute(id, params, context = this.context) {
|
|
229
|
+
const checked = this.check(id, params, context);
|
|
230
|
+
if ("ok" in checked) return checked;
|
|
231
|
+
try {
|
|
232
|
+
const result = this.invoke(checked.spec, checked.params, context);
|
|
233
|
+
this.notify();
|
|
234
|
+
return { ok: true, id, result };
|
|
235
|
+
} catch (error) {
|
|
236
|
+
this.notify();
|
|
237
|
+
return { ok: false, id, code: "failed", message: error instanceof Error ? error.message : String(error) };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/** Like `execute`, but awaits async commands (rejections become `failed` results). */
|
|
241
|
+
async executeAsync(id, params, context = this.context) {
|
|
242
|
+
const outcome = this.execute(id, params, context);
|
|
243
|
+
if (!outcome.ok || !isThenable(outcome.result)) return outcome;
|
|
244
|
+
try {
|
|
245
|
+
const result = await outcome.result;
|
|
246
|
+
this.notify();
|
|
247
|
+
return { ok: true, id, result };
|
|
248
|
+
} catch (error) {
|
|
249
|
+
this.notify();
|
|
250
|
+
return { ok: false, id, code: "failed", message: error instanceof Error ? error.message : String(error) };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Resolves a keydown event to a command (see `ShortcutMap.resolve`) and runs it when
|
|
255
|
+
* enabled, calling `preventDefault()`. Disabled commands leave the event alone, so the
|
|
256
|
+
* browser or a focused editor can still handle it. Returns the result, or `null`.
|
|
257
|
+
*/
|
|
258
|
+
handleKeyDown(event, options = {}) {
|
|
259
|
+
if (event.defaultPrevented) return null;
|
|
260
|
+
const match = this.shortcuts.resolve(event, options);
|
|
261
|
+
if (!match) return null;
|
|
262
|
+
const context = "context" in options ? options.context : this.context;
|
|
263
|
+
if (!this.isEnabled(match.command, void 0, context).enabled) return null;
|
|
264
|
+
event.preventDefault?.();
|
|
265
|
+
return this.execute(match.command, void 0, context);
|
|
266
|
+
}
|
|
267
|
+
/** Display-ready primary shortcut of a command (`⇧⌘Z` / `Ctrl+Shift+Z`), or `undefined`. */
|
|
268
|
+
shortcutFor(id, platform = this.shortcuts.platform) {
|
|
269
|
+
return this.shortcuts.forCommand(id, platform)[0];
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Subscribes to changes: commands registered or removed, shortcut overrides, executed
|
|
273
|
+
* commands and `invalidate()` calls (e.g. when enablement inputs change).
|
|
274
|
+
*/
|
|
275
|
+
subscribe(listener) {
|
|
276
|
+
this.listeners.add(listener);
|
|
277
|
+
return () => {
|
|
278
|
+
this.listeners.delete(listener);
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/** Tells subscribers that enablement may have changed (e.g. the selection changed). */
|
|
282
|
+
invalidate() {
|
|
283
|
+
this.notify();
|
|
284
|
+
}
|
|
285
|
+
notify() {
|
|
286
|
+
this.version++;
|
|
287
|
+
for (const listener of Array.from(this.listeners)) {
|
|
288
|
+
try {
|
|
289
|
+
listener();
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error("[CommandRegistry] Error in listener:", error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
function createCommandRegistry(options) {
|
|
297
|
+
return new CommandRegistry(options);
|
|
298
|
+
}
|
|
299
|
+
function defineCommand(spec) {
|
|
300
|
+
return spec;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/commands/palette.ts
|
|
304
|
+
var WORD_SEPARATORS = /* @__PURE__ */ new Set([" ", "-", "_", ".", "/", ":", "\u203A", "("]);
|
|
305
|
+
function fuzzyMatch(query, text) {
|
|
306
|
+
const q = query.trim().toLowerCase();
|
|
307
|
+
if (!q) return { score: 0, indices: [] };
|
|
308
|
+
const t = text.toLowerCase();
|
|
309
|
+
let score = 0;
|
|
310
|
+
let ti = 0;
|
|
311
|
+
let last = -2;
|
|
312
|
+
const indices = [];
|
|
313
|
+
for (const qc of q) {
|
|
314
|
+
if (qc === " ") continue;
|
|
315
|
+
let found = false;
|
|
316
|
+
while (ti < t.length) {
|
|
317
|
+
if (t[ti] === qc) {
|
|
318
|
+
score += last === ti - 1 ? 8 : 1;
|
|
319
|
+
if (ti === 0 || WORD_SEPARATORS.has(t[ti - 1])) score += 5;
|
|
320
|
+
last = ti;
|
|
321
|
+
indices.push(ti);
|
|
322
|
+
ti++;
|
|
323
|
+
found = true;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
ti++;
|
|
327
|
+
}
|
|
328
|
+
if (!found) return null;
|
|
329
|
+
}
|
|
330
|
+
return { score: score - Math.floor(t.length / 8), indices };
|
|
331
|
+
}
|
|
332
|
+
function fuzzyScore(query, text) {
|
|
333
|
+
return fuzzyMatch(query, text)?.score ?? null;
|
|
334
|
+
}
|
|
335
|
+
function exactBonus(query, names) {
|
|
336
|
+
const q = query.trim().toLowerCase();
|
|
337
|
+
if (!q) return 0;
|
|
338
|
+
return names.some((n) => n.replace(/(…|\.\.\.)$/, "").trim().toLowerCase() === q) ? 1e3 : 0;
|
|
339
|
+
}
|
|
340
|
+
var DEFAULT_RECENT_LIMIT = 8;
|
|
341
|
+
function pushRecent(list, id, cap = DEFAULT_RECENT_LIMIT) {
|
|
342
|
+
return [id, ...list.filter((x) => x !== id)].slice(0, Math.max(0, cap));
|
|
343
|
+
}
|
|
344
|
+
function rankCommands(query, entries, options = {}) {
|
|
345
|
+
const recents = options.recents ?? [];
|
|
346
|
+
const disabledLast = options.disabledLast ?? true;
|
|
347
|
+
const q = query.trim();
|
|
348
|
+
let ranked;
|
|
349
|
+
if (!q) {
|
|
350
|
+
const byId = new Map(entries.map((e) => [e.id, e]));
|
|
351
|
+
const recentIds = [...new Set(recents)].filter((id) => byId.has(id)).slice(0, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
352
|
+
const recentSet = new Set(recentIds);
|
|
353
|
+
ranked = recentIds.map((id, i) => ({ entry: byId.get(id), score: 1e3 - i, labelIndices: [], recent: true, order: i }));
|
|
354
|
+
if ((options.emptyQuery ?? "all") === "all") {
|
|
355
|
+
entries.forEach((entry, i) => {
|
|
356
|
+
if (!recentSet.has(entry.id)) ranked.push({ entry, score: 0, labelIndices: [], recent: false, order: recentIds.length + i });
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
ranked = [];
|
|
361
|
+
entries.forEach((entry, order) => {
|
|
362
|
+
const label = fuzzyMatch(q, entry.label);
|
|
363
|
+
const candidates = [];
|
|
364
|
+
if (label) candidates.push(label.score);
|
|
365
|
+
if (entry.group) {
|
|
366
|
+
const grouped = fuzzyScore(q, `${entry.group} ${entry.label}`);
|
|
367
|
+
if (grouped !== null) candidates.push(grouped - 1);
|
|
368
|
+
}
|
|
369
|
+
for (const keyword of entry.keywords ?? []) {
|
|
370
|
+
const s = fuzzyScore(q, keyword);
|
|
371
|
+
if (s !== null) candidates.push(s - 2);
|
|
372
|
+
}
|
|
373
|
+
const byId = fuzzyScore(q, entry.id);
|
|
374
|
+
if (byId !== null) candidates.push(byId - 3);
|
|
375
|
+
if (candidates.length === 0) return;
|
|
376
|
+
const recentIndex = recents.indexOf(entry.id);
|
|
377
|
+
const score = Math.max(...candidates) + exactBonus(q, [entry.label, ...entry.keywords ?? []]) + (recentIndex >= 0 ? Math.max(1, 4 - recentIndex) : 0);
|
|
378
|
+
ranked.push({ entry, score, labelIndices: label?.indices ?? [], recent: false, order });
|
|
379
|
+
});
|
|
380
|
+
ranked.sort((a, b) => b.score - a.score || a.entry.label.length - b.entry.label.length || a.order - b.order);
|
|
381
|
+
}
|
|
382
|
+
if (disabledLast) {
|
|
383
|
+
ranked = [...ranked.filter((r) => r.entry.enabled !== false), ...ranked.filter((r) => r.entry.enabled === false)];
|
|
384
|
+
}
|
|
385
|
+
const out = ranked.map(({ order: _order, ...rest }) => rest);
|
|
386
|
+
return options.limit !== void 0 ? out.slice(0, options.limit) : out;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/commands/paletteController.ts
|
|
390
|
+
function createCommandPaletteController(registry, options = {}) {
|
|
391
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
392
|
+
let open = false;
|
|
393
|
+
let query = "";
|
|
394
|
+
let activeIndex = 0;
|
|
395
|
+
let recents = [...options.recents ?? []];
|
|
396
|
+
let lastResult = null;
|
|
397
|
+
let state;
|
|
398
|
+
const context = () => {
|
|
399
|
+
const c = options.context;
|
|
400
|
+
if (c === void 0) return registry.context;
|
|
401
|
+
return typeof c === "function" ? c() : c;
|
|
402
|
+
};
|
|
403
|
+
const scope = () => typeof options.scope === "function" ? options.scope() : options.scope;
|
|
404
|
+
function computeItems() {
|
|
405
|
+
if (!open) return [];
|
|
406
|
+
const entries = registry.list({ scope: scope(), context: context() });
|
|
407
|
+
const platform = options.platform ?? registry.shortcuts.platform;
|
|
408
|
+
return rankCommands(query, entries, {
|
|
409
|
+
recents,
|
|
410
|
+
recentLimit: options.recentLimit,
|
|
411
|
+
limit: options.limit ?? 50
|
|
412
|
+
}).map((ranked) => ({
|
|
413
|
+
...ranked,
|
|
414
|
+
id: ranked.entry.id,
|
|
415
|
+
label: ranked.entry.label,
|
|
416
|
+
...ranked.entry.group !== void 0 ? { group: ranked.entry.group } : {},
|
|
417
|
+
...ranked.entry.shortcuts[0] ? { shortcut: formatShortcut(ranked.entry.shortcuts[0], platform) } : {},
|
|
418
|
+
enabled: ranked.entry.enabled,
|
|
419
|
+
...ranked.entry.disabledReason ? { disabledReason: ranked.entry.disabledReason } : {}
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
function update() {
|
|
423
|
+
const items = computeItems();
|
|
424
|
+
activeIndex = items.length === 0 ? 0 : Math.min(Math.max(activeIndex, 0), items.length - 1);
|
|
425
|
+
state = { open, query, activeIndex, items, recents, lastResult };
|
|
426
|
+
for (const listener of Array.from(listeners)) listener(state);
|
|
427
|
+
}
|
|
428
|
+
state = { open, query, activeIndex, items: [], recents, lastResult };
|
|
429
|
+
let unsubscribeRegistry = null;
|
|
430
|
+
const onRegistryChange = () => {
|
|
431
|
+
if (open) update();
|
|
432
|
+
};
|
|
433
|
+
const controller = {
|
|
434
|
+
getState: () => state,
|
|
435
|
+
subscribe(listener) {
|
|
436
|
+
listeners.add(listener);
|
|
437
|
+
unsubscribeRegistry ??= registry.subscribe(onRegistryChange);
|
|
438
|
+
if (open) update();
|
|
439
|
+
return () => {
|
|
440
|
+
listeners.delete(listener);
|
|
441
|
+
if (listeners.size === 0) {
|
|
442
|
+
unsubscribeRegistry?.();
|
|
443
|
+
unsubscribeRegistry = null;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
},
|
|
447
|
+
setOpen(next) {
|
|
448
|
+
if (next === open) return;
|
|
449
|
+
open = next;
|
|
450
|
+
query = "";
|
|
451
|
+
activeIndex = 0;
|
|
452
|
+
update();
|
|
453
|
+
},
|
|
454
|
+
toggle() {
|
|
455
|
+
controller.setOpen(!open);
|
|
456
|
+
},
|
|
457
|
+
setQuery(next) {
|
|
458
|
+
if (next === query) return;
|
|
459
|
+
query = next;
|
|
460
|
+
activeIndex = 0;
|
|
461
|
+
update();
|
|
462
|
+
},
|
|
463
|
+
setActiveIndex(index) {
|
|
464
|
+
activeIndex = index;
|
|
465
|
+
update();
|
|
466
|
+
},
|
|
467
|
+
/** Moves the active row by `delta`, wrapping around. */
|
|
468
|
+
move(delta) {
|
|
469
|
+
const n = state.items.length;
|
|
470
|
+
if (n === 0) return;
|
|
471
|
+
activeIndex = ((activeIndex + delta) % n + n) % n;
|
|
472
|
+
update();
|
|
473
|
+
},
|
|
474
|
+
/** Runs a command by id, closes the palette and records it as recent. Disabled commands do nothing. */
|
|
475
|
+
run(id) {
|
|
476
|
+
if (!registry.isEnabled(id, void 0, context()).enabled) return null;
|
|
477
|
+
open = false;
|
|
478
|
+
query = "";
|
|
479
|
+
activeIndex = 0;
|
|
480
|
+
recents = pushRecent(recents, id, options.recentLimit ?? DEFAULT_RECENT_LIMIT);
|
|
481
|
+
options.onRecentsChange?.(recents);
|
|
482
|
+
const result = registry.execute(id, void 0, context());
|
|
483
|
+
lastResult = result;
|
|
484
|
+
update();
|
|
485
|
+
options.onRun?.(result);
|
|
486
|
+
return result;
|
|
487
|
+
},
|
|
488
|
+
runActive() {
|
|
489
|
+
const item = state.items[state.activeIndex];
|
|
490
|
+
return item ? controller.run(item.id) : null;
|
|
491
|
+
},
|
|
492
|
+
/**
|
|
493
|
+
* Keyboard handling for the palette input: ArrowUp/ArrowDown (wrap), Home/End,
|
|
494
|
+
* Enter (run the active row) and Escape (close). Returns whether it handled the key.
|
|
495
|
+
*/
|
|
496
|
+
handleKeyDown(event) {
|
|
497
|
+
if (!open) return false;
|
|
498
|
+
switch (event.key) {
|
|
499
|
+
case "ArrowDown":
|
|
500
|
+
controller.move(1);
|
|
501
|
+
break;
|
|
502
|
+
case "ArrowUp":
|
|
503
|
+
controller.move(-1);
|
|
504
|
+
break;
|
|
505
|
+
case "Home":
|
|
506
|
+
controller.setActiveIndex(0);
|
|
507
|
+
break;
|
|
508
|
+
case "End":
|
|
509
|
+
controller.setActiveIndex(state.items.length - 1);
|
|
510
|
+
break;
|
|
511
|
+
case "Enter":
|
|
512
|
+
controller.runActive();
|
|
513
|
+
break;
|
|
514
|
+
case "Escape":
|
|
515
|
+
controller.setOpen(false);
|
|
516
|
+
break;
|
|
517
|
+
default:
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
event.preventDefault?.();
|
|
521
|
+
return true;
|
|
522
|
+
},
|
|
523
|
+
/** Re-reads commands and enablement. */
|
|
524
|
+
refresh() {
|
|
525
|
+
update();
|
|
526
|
+
},
|
|
527
|
+
destroy() {
|
|
528
|
+
unsubscribeRegistry?.();
|
|
529
|
+
unsubscribeRegistry = null;
|
|
530
|
+
listeners.clear();
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
return controller;
|
|
534
|
+
}
|
|
535
|
+
function matchPaletteHotkey(event, hotkey, platform) {
|
|
536
|
+
const map = new ShortcutMap([{ command: "palette", chord: hotkey, allowInEditable: true }], platform ? { platform } : {});
|
|
537
|
+
return map.resolve(event) !== null;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/commands/agentTools.ts
|
|
541
|
+
var toJson = (value) => {
|
|
542
|
+
if (value === void 0) return null;
|
|
543
|
+
try {
|
|
544
|
+
return JSON.parse(JSON.stringify(value));
|
|
545
|
+
} catch {
|
|
546
|
+
return String(value);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
function errorResult(result) {
|
|
550
|
+
return {
|
|
551
|
+
isError: true,
|
|
552
|
+
content: [{ type: "text", text: `${result.code}: ${result.message}` }],
|
|
553
|
+
structuredContent: {
|
|
554
|
+
error: { code: result.code, message: result.message, command: result.id, ...result.details ? { details: result.details } : {} }
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function createCommandToolProvider(registry, options = {}) {
|
|
559
|
+
const context = () => {
|
|
560
|
+
const c = options.context;
|
|
561
|
+
if (c === void 0) return registry.context;
|
|
562
|
+
return typeof c === "function" ? c() : c;
|
|
563
|
+
};
|
|
564
|
+
const visible = () => registry.list({ scope: options.scope, context: context() }).filter((e) => !options.exclude?.(e.id));
|
|
565
|
+
const allowed = (id) => registry.has(id) && !options.exclude?.(id) && visible().some((e) => e.id === id);
|
|
566
|
+
async function runOne(id, params) {
|
|
567
|
+
if (!allowed(id)) return { ok: false, id, code: "unknown_command", message: `Unknown command "${id}".` };
|
|
568
|
+
return registry.executeAsync(id, params ?? {}, context());
|
|
569
|
+
}
|
|
570
|
+
const listCommands = {
|
|
571
|
+
name: "list_commands",
|
|
572
|
+
title: "List commands",
|
|
573
|
+
description: "Lists the commands you can run with run_command: id, label, group, params (JSON Schema), shortcuts, and whether each is enabled now (with the reason when it is not).",
|
|
574
|
+
inputSchema: {
|
|
575
|
+
type: "object",
|
|
576
|
+
properties: {
|
|
577
|
+
query: { type: "string", description: "Fuzzy filter on label, keywords, group and id." },
|
|
578
|
+
enabledOnly: { type: "boolean", description: "Only commands that can run now (default false)." }
|
|
579
|
+
},
|
|
580
|
+
additionalProperties: false
|
|
581
|
+
},
|
|
582
|
+
annotations: { title: "List commands", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
583
|
+
handler: ({ query = "", enabledOnly = false }) => {
|
|
584
|
+
let entries = visible();
|
|
585
|
+
if (enabledOnly) entries = entries.filter((e) => e.enabled);
|
|
586
|
+
if (String(query).trim()) entries = rankCommands(String(query), entries, { disabledLast: false }).map((r) => r.entry);
|
|
587
|
+
return {
|
|
588
|
+
commands: entries.map((e) => ({
|
|
589
|
+
id: e.id,
|
|
590
|
+
label: e.label,
|
|
591
|
+
...e.group ? { group: e.group } : {},
|
|
592
|
+
...e.description ? { description: e.description } : {},
|
|
593
|
+
...e.params ? { params: e.params } : {},
|
|
594
|
+
...e.shortcuts.length ? { shortcuts: e.shortcuts } : {},
|
|
595
|
+
enabled: e.enabled,
|
|
596
|
+
...e.disabledReason ? { disabledReason: e.disabledReason } : {},
|
|
597
|
+
readOnly: e.readOnly
|
|
598
|
+
}))
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
const runCommandTool = () => {
|
|
603
|
+
const ids = visible().map((e) => e.id);
|
|
604
|
+
return {
|
|
605
|
+
name: "run_command",
|
|
606
|
+
title: "Run a command",
|
|
607
|
+
description: "Runs one command by id with optional params (see list_commands for each command's params schema). Unknown params are rejected.",
|
|
608
|
+
inputSchema: {
|
|
609
|
+
type: "object",
|
|
610
|
+
properties: {
|
|
611
|
+
id: { type: "string", description: "Command id from list_commands.", ...ids.length ? { enum: ids } : {} },
|
|
612
|
+
params: { type: "object", description: "Command parameters." }
|
|
613
|
+
},
|
|
614
|
+
required: ["id"],
|
|
615
|
+
additionalProperties: false
|
|
616
|
+
},
|
|
617
|
+
annotations: { title: "Run a command", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
618
|
+
handler: async ({ id, params }) => {
|
|
619
|
+
const result = await runOne(String(id), params);
|
|
620
|
+
return result.ok ? { id: result.id, ok: true, result: toJson(result.result) } : errorResult(result);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
};
|
|
624
|
+
const runCommands = {
|
|
625
|
+
name: "run_commands",
|
|
626
|
+
title: "Run several commands",
|
|
627
|
+
description: "Runs several commands in order: {steps: [{id, params?}], stopOnError?: true}. Each step is its own command and undo step; the batch is NOT atomic (steps before a failure stay applied). Returns {completed, failed, results: [{id, ok, result | code, message}]}.",
|
|
628
|
+
inputSchema: {
|
|
629
|
+
type: "object",
|
|
630
|
+
properties: {
|
|
631
|
+
steps: {
|
|
632
|
+
type: "array",
|
|
633
|
+
minItems: 1,
|
|
634
|
+
items: {
|
|
635
|
+
type: "object",
|
|
636
|
+
properties: {
|
|
637
|
+
id: { type: "string", description: "Command id." },
|
|
638
|
+
params: { type: "object", description: "Command parameters." }
|
|
639
|
+
},
|
|
640
|
+
required: ["id"],
|
|
641
|
+
additionalProperties: false
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
stopOnError: { type: "boolean", description: "Stop at the first failing step (default true)." }
|
|
645
|
+
},
|
|
646
|
+
required: ["steps"],
|
|
647
|
+
additionalProperties: false
|
|
648
|
+
},
|
|
649
|
+
annotations: {
|
|
650
|
+
title: "Run several commands",
|
|
651
|
+
readOnlyHint: false,
|
|
652
|
+
destructiveHint: true,
|
|
653
|
+
idempotentHint: false,
|
|
654
|
+
openWorldHint: false
|
|
655
|
+
},
|
|
656
|
+
handler: async ({ steps, stopOnError = true }) => {
|
|
657
|
+
let completed = 0;
|
|
658
|
+
let failed = 0;
|
|
659
|
+
const results = [];
|
|
660
|
+
for (const step of steps) {
|
|
661
|
+
const result = await runOne(step.id, step.params);
|
|
662
|
+
if (result.ok) {
|
|
663
|
+
completed++;
|
|
664
|
+
results.push({ id: result.id, ok: true, result: toJson(result.result) });
|
|
665
|
+
} else {
|
|
666
|
+
failed++;
|
|
667
|
+
results.push({ id: result.id, ok: false, code: result.code, message: result.message });
|
|
668
|
+
if (stopOnError) break;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const summary = { completed, failed, skipped: steps.length - completed - failed, results };
|
|
672
|
+
return failed > 0 ? { isError: true, content: [{ type: "text", text: JSON.stringify(summary) }], structuredContent: summary } : summary;
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
return {
|
|
676
|
+
id: options.id ?? "commands",
|
|
677
|
+
namespace: options.namespace,
|
|
678
|
+
tools: () => [listCommands, runCommandTool(), runCommands],
|
|
679
|
+
subscribe: (notify) => registry.subscribe(notify)
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// src/commands/builtins.ts
|
|
684
|
+
var DEFAULT_HISTORY_SHORTCUTS = [
|
|
685
|
+
{ command: "undo", chord: "Mod+Z" },
|
|
686
|
+
{ command: "redo", chord: "Mod+Shift+Z" },
|
|
687
|
+
{ command: "redo", chord: "Mod+Y", platforms: ["windows", "linux"] }
|
|
688
|
+
];
|
|
689
|
+
function createHistoryCommands(history, options = {}) {
|
|
690
|
+
const prefix = options.prefix ?? "history.";
|
|
691
|
+
const group = options.group ?? "Edit";
|
|
692
|
+
const shortcutsOf = (command) => DEFAULT_HISTORY_SHORTCUTS.filter((b) => b.command === command).map(({ command: _c, ...rest }) => rest);
|
|
693
|
+
return [
|
|
694
|
+
{
|
|
695
|
+
id: `${prefix}undo`,
|
|
696
|
+
label: "Undo",
|
|
697
|
+
group,
|
|
698
|
+
keywords: ["revert", "back"],
|
|
699
|
+
description: "Reverts the last change.",
|
|
700
|
+
shortcut: shortcutsOf("undo"),
|
|
701
|
+
...options.scope ? { scope: options.scope } : {},
|
|
702
|
+
enabled: () => history.getState().canUndo ? true : "Nothing to undo.",
|
|
703
|
+
run: () => {
|
|
704
|
+
history.undo();
|
|
705
|
+
return { undone: true, state: history.getState() };
|
|
706
|
+
}
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
id: `${prefix}redo`,
|
|
710
|
+
label: "Redo",
|
|
711
|
+
group,
|
|
712
|
+
keywords: ["repeat", "forward"],
|
|
713
|
+
description: "Re-applies the last undone change.",
|
|
714
|
+
shortcut: shortcutsOf("redo"),
|
|
715
|
+
...options.scope ? { scope: options.scope } : {},
|
|
716
|
+
enabled: () => history.getState().canRedo ? true : "Nothing to redo.",
|
|
717
|
+
run: () => {
|
|
718
|
+
history.redo();
|
|
719
|
+
return { redone: true, state: history.getState() };
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
];
|
|
723
|
+
}
|
|
724
|
+
var historyShortcutMap = null;
|
|
725
|
+
function matchHistoryShortcut(event, options = {}) {
|
|
726
|
+
historyShortcutMap ??= new ShortcutMap(DEFAULT_HISTORY_SHORTCUTS);
|
|
727
|
+
return historyShortcutMap.resolve(event, options)?.command ?? null;
|
|
728
|
+
}
|
|
729
|
+
var ARRANGE_LABELS = {
|
|
730
|
+
alignLeft: "Align Left",
|
|
731
|
+
alignCenter: "Align Horizontal Centers",
|
|
732
|
+
alignRight: "Align Right",
|
|
733
|
+
alignTop: "Align Top",
|
|
734
|
+
alignMiddle: "Align Vertical Centers",
|
|
735
|
+
alignBottom: "Align Bottom",
|
|
736
|
+
distributeHorizontal: "Distribute Horizontally",
|
|
737
|
+
distributeVertical: "Distribute Vertically",
|
|
738
|
+
lock: "Lock",
|
|
739
|
+
unlock: "Unlock",
|
|
740
|
+
bringForward: "Bring Forward",
|
|
741
|
+
sendBackward: "Send Backward",
|
|
742
|
+
bringToFront: "Bring to Front",
|
|
743
|
+
sendToBack: "Send to Back",
|
|
744
|
+
nextScene: "Next Scene",
|
|
745
|
+
previousScene: "Previous Scene"
|
|
746
|
+
};
|
|
747
|
+
var IDS_PARAMS = {
|
|
748
|
+
type: "object",
|
|
749
|
+
properties: {
|
|
750
|
+
ids: {
|
|
751
|
+
type: "array",
|
|
752
|
+
items: { type: "string" },
|
|
753
|
+
description: "Node ids to act on (default: the selection)."
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
function createArrangeCommands(controller, options = {}) {
|
|
758
|
+
const prefix = options.prefix ?? "canvas.";
|
|
759
|
+
const group = options.group ?? "Arrange";
|
|
760
|
+
const scope = options.scope === null ? void 0 : options.scope ?? "canvas";
|
|
761
|
+
const bindings = options.bindings ?? DEFAULT_ARRANGE_SHORTCUTS;
|
|
762
|
+
const count = (params) => params?.ids?.length ?? options.getSelectedIds?.().length;
|
|
763
|
+
const needs = (min, what) => (_, params) => {
|
|
764
|
+
const n = count(params);
|
|
765
|
+
if (n === void 0 || n >= min) return true;
|
|
766
|
+
return min === 1 ? `Select a ${what} first.` : `Select at least ${min} ${what}s.`;
|
|
767
|
+
};
|
|
768
|
+
const run = {
|
|
769
|
+
alignLeft: (ids) => controller.align("left", ids),
|
|
770
|
+
alignCenter: (ids) => controller.align("center", ids),
|
|
771
|
+
alignRight: (ids) => controller.align("right", ids),
|
|
772
|
+
alignTop: (ids) => controller.align("top", ids),
|
|
773
|
+
alignMiddle: (ids) => controller.align("middle", ids),
|
|
774
|
+
alignBottom: (ids) => controller.align("bottom", ids),
|
|
775
|
+
distributeHorizontal: (ids) => controller.distribute("horizontal", "gaps", ids),
|
|
776
|
+
distributeVertical: (ids) => controller.distribute("vertical", "gaps", ids),
|
|
777
|
+
lock: (ids) => controller.lock(ids),
|
|
778
|
+
unlock: (ids) => controller.unlock(ids),
|
|
779
|
+
bringForward: (ids) => controller.reorder("bringForward", ids),
|
|
780
|
+
sendBackward: (ids) => controller.reorder("sendBackward", ids),
|
|
781
|
+
bringToFront: (ids) => controller.reorder("bringToFront", ids),
|
|
782
|
+
sendToBack: (ids) => controller.reorder("sendToBack", ids),
|
|
783
|
+
nextScene: () => options.scenes?.next(),
|
|
784
|
+
previousScene: () => options.scenes?.previous()
|
|
785
|
+
};
|
|
786
|
+
const actions = Object.keys(ARRANGE_LABELS);
|
|
787
|
+
return actions.filter((action) => options.scenes || action !== "nextScene" && action !== "previousScene").map((action) => {
|
|
788
|
+
const isScene = action === "nextScene" || action === "previousScene";
|
|
789
|
+
const enabled = isScene ? void 0 : action.startsWith("align") ? needs(2, "node") : action.startsWith("distribute") ? needs(3, "node") : needs(1, "node");
|
|
790
|
+
return {
|
|
791
|
+
id: `${prefix}${action}`,
|
|
792
|
+
label: ARRANGE_LABELS[action],
|
|
793
|
+
group: isScene ? "Scenes" : group,
|
|
794
|
+
keywords: isScene ? ["scene", "slide", "page"] : action.startsWith("align") ? ["align"] : ["arrange", "layer", "order"],
|
|
795
|
+
...scope ? { scope } : {},
|
|
796
|
+
shortcut: bindings.filter((b) => b.action === action).map((b) => b.label),
|
|
797
|
+
...isScene ? {} : { params: IDS_PARAMS },
|
|
798
|
+
...enabled ? { enabled } : {},
|
|
799
|
+
run: (_ctx, params) => run[action](params?.ids)
|
|
800
|
+
};
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
function createSpreadsheetCommands(controller, options = {}) {
|
|
804
|
+
const prefix = options.prefix ?? "spreadsheet.";
|
|
805
|
+
const scope = options.scope === null ? void 0 : options.scope ?? "spreadsheet";
|
|
806
|
+
const hasRange = () => controller.selectedRange ? true : "Select a range first.";
|
|
807
|
+
const base = { group: "Table", ...scope ? { scope } : {} };
|
|
808
|
+
return [
|
|
809
|
+
{ ...base, id: `${prefix}toggleFilters`, label: "Toggle Filters", keywords: ["filter"], run: () => controller.toggleFilters() },
|
|
810
|
+
{
|
|
811
|
+
...base,
|
|
812
|
+
id: `${prefix}clearFilters`,
|
|
813
|
+
label: "Clear Filters",
|
|
814
|
+
keywords: ["filter", "reset"],
|
|
815
|
+
enabled: () => controller.getFilterState().rules.length > 0 ? true : "No filters to clear.",
|
|
816
|
+
run: () => controller.clearFilters()
|
|
817
|
+
},
|
|
818
|
+
{ ...base, id: `${prefix}unhideAll`, label: "Unhide All Rows and Columns", keywords: ["show", "hidden"], run: () => controller.unhideAll() },
|
|
819
|
+
{ ...base, id: `${prefix}mergeCells`, label: "Merge Cells", keywords: ["combine"], enabled: hasRange, run: () => controller.mergeCells() },
|
|
820
|
+
{ ...base, id: `${prefix}unmergeCells`, label: "Unmerge Cells", keywords: ["split"], enabled: hasRange, run: () => controller.unmergeCells() },
|
|
821
|
+
{ ...base, id: `${prefix}transposeRange`, label: "Transpose Range", keywords: ["rotate", "swap rows columns"], enabled: hasRange, run: () => controller.transposeRange() }
|
|
822
|
+
];
|
|
823
|
+
}
|
|
824
|
+
export {
|
|
825
|
+
CommandRegistry,
|
|
826
|
+
CommandRegistryError,
|
|
827
|
+
DEFAULT_HISTORY_SHORTCUTS,
|
|
828
|
+
DEFAULT_RECENT_LIMIT,
|
|
829
|
+
PLATFORMS,
|
|
830
|
+
ShortcutMap,
|
|
831
|
+
chordToString,
|
|
832
|
+
codeFromKey,
|
|
833
|
+
codeToKeyText,
|
|
834
|
+
createArrangeCommands,
|
|
835
|
+
createCommandPaletteController,
|
|
836
|
+
createCommandRegistry,
|
|
837
|
+
createCommandToolProvider,
|
|
838
|
+
createHistoryCommands,
|
|
839
|
+
createSpreadsheetCommands,
|
|
840
|
+
defineCommand,
|
|
841
|
+
detectPlatform,
|
|
842
|
+
effectiveChord,
|
|
843
|
+
effectiveChordKey,
|
|
844
|
+
eventCode,
|
|
845
|
+
exactBonus,
|
|
846
|
+
formatShortcut,
|
|
847
|
+
fuzzyMatch,
|
|
848
|
+
fuzzyScore,
|
|
849
|
+
isEditableEventTarget,
|
|
850
|
+
keyToCode,
|
|
851
|
+
matchHistoryShortcut,
|
|
852
|
+
matchPaletteHotkey,
|
|
853
|
+
normalizeChord,
|
|
854
|
+
parseChord,
|
|
855
|
+
pushRecent,
|
|
856
|
+
rankCommands,
|
|
857
|
+
shortcutCode,
|
|
858
|
+
toShortcutBindings
|
|
859
|
+
};
|
|
860
|
+
//# sourceMappingURL=index.js.map
|