@pooder/core 2.0.0 → 2.2.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/CHANGELOG.md +12 -0
- package/dist/index.d.mts +196 -21
- package/dist/index.d.ts +196 -21
- package/dist/index.js +718 -157
- package/dist/index.mjs +707 -156
- package/package.json +1 -1
- package/src/command.ts +10 -10
- package/src/context.ts +22 -17
- package/src/contribution/index.ts +12 -12
- package/src/contribution/points.ts +27 -3
- package/src/contribution/registry.ts +118 -118
- package/src/disposable.ts +3 -3
- package/src/extension.ts +177 -164
- package/src/index.ts +338 -145
- package/src/run-test-full.ts +98 -98
- package/src/service.ts +191 -11
- package/src/services/CommandService.ts +79 -79
- package/src/services/ConfigurationService.ts +107 -107
- package/src/services/ToolRegistryService.ts +41 -0
- package/src/services/ToolSessionService.ts +213 -0
- package/src/services/WorkbenchService.ts +187 -8
- package/src/services/index.ts +23 -1
- package/src/services/tokens.ts +27 -0
- package/src/test-extension-full.ts +79 -79
package/src/run-test-full.ts
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import { Pooder } from "./index";
|
|
2
|
-
import { fullExtension } from "./test-extension-full";
|
|
3
|
-
import { ContributionPointIds } from "./contribution";
|
|
4
|
-
import CommandService from "./services/CommandService";
|
|
5
|
-
|
|
6
|
-
async function runTest() {
|
|
7
|
-
console.log("Starting Test...");
|
|
8
|
-
const app = new Pooder();
|
|
9
|
-
|
|
10
|
-
// Register the full extension
|
|
11
|
-
console.log("Registering extension...");
|
|
12
|
-
app.extensionManager.register(fullExtension);
|
|
13
|
-
const commandService = app.getService<CommandService>("CommandService")!;
|
|
14
|
-
|
|
15
|
-
// 1. Verify Command Contributions
|
|
16
|
-
console.log("\n--- Verifying Commands ---");
|
|
17
|
-
|
|
18
|
-
// 1.1 Imperative Command
|
|
19
|
-
try {
|
|
20
|
-
const res = await commandService.executeCommand(
|
|
21
|
-
"test.imperative.hello",
|
|
22
|
-
"User",
|
|
23
|
-
);
|
|
24
|
-
console.log(
|
|
25
|
-
`[Imperative] "test.imperative.hello" result: "${res}"` ===
|
|
26
|
-
`[Imperative] "test.imperative.hello" result: "Hello Imperative User"`
|
|
27
|
-
? "✅ PASS"
|
|
28
|
-
: "❌ FAIL",
|
|
29
|
-
);
|
|
30
|
-
} catch (e) {
|
|
31
|
-
console.error("❌ FAIL: Imperative command failed", e);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// 1.2 Declarative Auto-Registered Command
|
|
35
|
-
try {
|
|
36
|
-
const res = await commandService.executeCommand("test.declarative.auto");
|
|
37
|
-
console.log(
|
|
38
|
-
`[Declarative] "test.declarative.auto" result: "${res}"` ===
|
|
39
|
-
`[Declarative] "test.declarative.auto" result: "Auto Registered Result"`
|
|
40
|
-
? "✅ PASS"
|
|
41
|
-
: "❌ FAIL",
|
|
42
|
-
);
|
|
43
|
-
} catch (e) {
|
|
44
|
-
console.error("❌ FAIL: Declarative auto-registered command failed", e);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// 2. Verify Registry Entries
|
|
48
|
-
console.log("\n--- Verifying Contribution Registry ---");
|
|
49
|
-
|
|
50
|
-
const commands = app.getContributions(ContributionPointIds.COMMANDS);
|
|
51
|
-
console.log(
|
|
52
|
-
`Commands registered: ${commands.length}` === "Commands registered: 2"
|
|
53
|
-
? "✅ PASS (2 commands found)"
|
|
54
|
-
: `❌ FAIL (${commands.length} commands found)`,
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const tools = app.getContributions(ContributionPointIds.TOOLS);
|
|
58
|
-
console.log(
|
|
59
|
-
`Tools registered: ${tools.length}` === "Tools registered: 1"
|
|
60
|
-
? "✅ PASS (1 tool found)"
|
|
61
|
-
: `❌ FAIL (${tools.length} tools found)`,
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const views = app.getContributions(ContributionPointIds.VIEWS);
|
|
65
|
-
console.log(
|
|
66
|
-
`Views registered: ${views.length}` === "Views registered: 1"
|
|
67
|
-
? "✅ PASS (1 view found)"
|
|
68
|
-
: `❌ FAIL (${views.length} views found)`,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// 3. Unregister and Verify Cleanup
|
|
72
|
-
console.log("\n--- Verifying Unregistration/Cleanup ---");
|
|
73
|
-
// The ID is now explicit: "full-feature-test-extension"
|
|
74
|
-
const extensionId = "full-feature-test-extension";
|
|
75
|
-
app.extensionManager.unregister(extensionId);
|
|
76
|
-
|
|
77
|
-
const commandsAfter = app.getContributions(ContributionPointIds.COMMANDS);
|
|
78
|
-
console.log(
|
|
79
|
-
`Commands after unregister: ${commandsAfter.length}` ===
|
|
80
|
-
"Commands after unregister: 0"
|
|
81
|
-
? "✅ PASS"
|
|
82
|
-
: `❌ FAIL (${commandsAfter.length} left)`,
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
// Verify command service cleanup
|
|
86
|
-
const cmdMap = commandService.getCommands();
|
|
87
|
-
const hasCmd = cmdMap.has("test.declarative.auto");
|
|
88
|
-
console.log(
|
|
89
|
-
`Command service cleaned up: ${!hasCmd}` ===
|
|
90
|
-
"Command service cleaned up: true"
|
|
91
|
-
? "✅ PASS"
|
|
92
|
-
: "❌ FAIL (Command still exists)",
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
console.log("\nTest Completed.");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
runTest().catch(console.error);
|
|
1
|
+
import { Pooder } from "./index";
|
|
2
|
+
import { fullExtension } from "./test-extension-full";
|
|
3
|
+
import { ContributionPointIds } from "./contribution";
|
|
4
|
+
import CommandService from "./services/CommandService";
|
|
5
|
+
|
|
6
|
+
async function runTest() {
|
|
7
|
+
console.log("Starting Test...");
|
|
8
|
+
const app = new Pooder();
|
|
9
|
+
|
|
10
|
+
// Register the full extension
|
|
11
|
+
console.log("Registering extension...");
|
|
12
|
+
app.extensionManager.register(fullExtension);
|
|
13
|
+
const commandService = app.getService<CommandService>("CommandService")!;
|
|
14
|
+
|
|
15
|
+
// 1. Verify Command Contributions
|
|
16
|
+
console.log("\n--- Verifying Commands ---");
|
|
17
|
+
|
|
18
|
+
// 1.1 Imperative Command
|
|
19
|
+
try {
|
|
20
|
+
const res = await commandService.executeCommand(
|
|
21
|
+
"test.imperative.hello",
|
|
22
|
+
"User",
|
|
23
|
+
);
|
|
24
|
+
console.log(
|
|
25
|
+
`[Imperative] "test.imperative.hello" result: "${res}"` ===
|
|
26
|
+
`[Imperative] "test.imperative.hello" result: "Hello Imperative User"`
|
|
27
|
+
? "✅ PASS"
|
|
28
|
+
: "❌ FAIL",
|
|
29
|
+
);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.error("❌ FAIL: Imperative command failed", e);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 1.2 Declarative Auto-Registered Command
|
|
35
|
+
try {
|
|
36
|
+
const res = await commandService.executeCommand("test.declarative.auto");
|
|
37
|
+
console.log(
|
|
38
|
+
`[Declarative] "test.declarative.auto" result: "${res}"` ===
|
|
39
|
+
`[Declarative] "test.declarative.auto" result: "Auto Registered Result"`
|
|
40
|
+
? "✅ PASS"
|
|
41
|
+
: "❌ FAIL",
|
|
42
|
+
);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.error("❌ FAIL: Declarative auto-registered command failed", e);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 2. Verify Registry Entries
|
|
48
|
+
console.log("\n--- Verifying Contribution Registry ---");
|
|
49
|
+
|
|
50
|
+
const commands = app.getContributions(ContributionPointIds.COMMANDS);
|
|
51
|
+
console.log(
|
|
52
|
+
`Commands registered: ${commands.length}` === "Commands registered: 2"
|
|
53
|
+
? "✅ PASS (2 commands found)"
|
|
54
|
+
: `❌ FAIL (${commands.length} commands found)`,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const tools = app.getContributions(ContributionPointIds.TOOLS);
|
|
58
|
+
console.log(
|
|
59
|
+
`Tools registered: ${tools.length}` === "Tools registered: 1"
|
|
60
|
+
? "✅ PASS (1 tool found)"
|
|
61
|
+
: `❌ FAIL (${tools.length} tools found)`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const views = app.getContributions(ContributionPointIds.VIEWS);
|
|
65
|
+
console.log(
|
|
66
|
+
`Views registered: ${views.length}` === "Views registered: 1"
|
|
67
|
+
? "✅ PASS (1 view found)"
|
|
68
|
+
: `❌ FAIL (${views.length} views found)`,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// 3. Unregister and Verify Cleanup
|
|
72
|
+
console.log("\n--- Verifying Unregistration/Cleanup ---");
|
|
73
|
+
// The ID is now explicit: "full-feature-test-extension"
|
|
74
|
+
const extensionId = "full-feature-test-extension";
|
|
75
|
+
app.extensionManager.unregister(extensionId);
|
|
76
|
+
|
|
77
|
+
const commandsAfter = app.getContributions(ContributionPointIds.COMMANDS);
|
|
78
|
+
console.log(
|
|
79
|
+
`Commands after unregister: ${commandsAfter.length}` ===
|
|
80
|
+
"Commands after unregister: 0"
|
|
81
|
+
? "✅ PASS"
|
|
82
|
+
: `❌ FAIL (${commandsAfter.length} left)`,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Verify command service cleanup
|
|
86
|
+
const cmdMap = commandService.getCommands();
|
|
87
|
+
const hasCmd = cmdMap.has("test.declarative.auto");
|
|
88
|
+
console.log(
|
|
89
|
+
`Command service cleaned up: ${!hasCmd}` ===
|
|
90
|
+
"Command service cleaned up: true"
|
|
91
|
+
? "✅ PASS"
|
|
92
|
+
: "❌ FAIL (Command still exists)",
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
console.log("\nTest Completed.");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
runTest().catch(console.error);
|
package/src/service.ts
CHANGED
|
@@ -1,25 +1,205 @@
|
|
|
1
|
+
import type EventBus from "./event";
|
|
2
|
+
|
|
1
3
|
export interface Service {
|
|
2
|
-
init?(): void
|
|
3
|
-
dispose?(): void
|
|
4
|
+
init?(context: ServiceContext): void | Promise<void>;
|
|
5
|
+
dispose?(context: ServiceContext): void | Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ServiceToken<T extends Service = Service> {
|
|
9
|
+
readonly kind: "service-token";
|
|
10
|
+
readonly key: symbol;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ServiceIdentifier<T extends Service = Service> =
|
|
15
|
+
| string
|
|
16
|
+
| ServiceToken<T>;
|
|
17
|
+
|
|
18
|
+
export interface ServiceContext {
|
|
19
|
+
readonly eventBus: EventBus;
|
|
20
|
+
get<T extends Service>(identifier: ServiceIdentifier<T>): T | undefined;
|
|
21
|
+
getOrThrow<T extends Service>(
|
|
22
|
+
identifier: ServiceIdentifier<T>,
|
|
23
|
+
errorMessage?: string,
|
|
24
|
+
): T;
|
|
25
|
+
has(identifier: ServiceIdentifier<Service>): boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RegisterServiceOptions {
|
|
29
|
+
allowOverride?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RegisteredService<T extends Service = Service> {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly token?: ServiceToken<T>;
|
|
35
|
+
readonly service: T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ServiceEntry<T extends Service = Service> {
|
|
39
|
+
token?: ServiceToken<T>;
|
|
40
|
+
name: string;
|
|
41
|
+
service: T;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface NormalizedIdentifier<T extends Service = Service> {
|
|
45
|
+
token?: ServiceToken<T>;
|
|
46
|
+
name: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createServiceToken<T extends Service = Service>(
|
|
50
|
+
name: string,
|
|
51
|
+
): ServiceToken<T> {
|
|
52
|
+
if (!name) {
|
|
53
|
+
throw new Error("Service token name is required.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
kind: "service-token" as const,
|
|
58
|
+
key: Symbol(name),
|
|
59
|
+
name,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isServiceToken<T extends Service = Service>(
|
|
64
|
+
identifier: unknown,
|
|
65
|
+
): identifier is ServiceToken<T> {
|
|
66
|
+
return (
|
|
67
|
+
typeof identifier === "object" &&
|
|
68
|
+
identifier !== null &&
|
|
69
|
+
"kind" in identifier &&
|
|
70
|
+
(identifier as { kind?: unknown }).kind === "service-token"
|
|
71
|
+
);
|
|
4
72
|
}
|
|
5
73
|
|
|
6
74
|
export class ServiceRegistry {
|
|
7
|
-
private
|
|
75
|
+
private readonly servicesByName: Map<string, ServiceEntry> = new Map();
|
|
76
|
+
private readonly servicesByToken: Map<symbol, ServiceEntry> = new Map();
|
|
77
|
+
private readonly registrationOrder: ServiceEntry[] = [];
|
|
78
|
+
|
|
79
|
+
register<T extends Service>(
|
|
80
|
+
identifier: ServiceIdentifier<T>,
|
|
81
|
+
service: T,
|
|
82
|
+
options: RegisterServiceOptions = {},
|
|
83
|
+
): T {
|
|
84
|
+
const normalized = this.normalizeIdentifier(identifier);
|
|
85
|
+
const existing = this.findEntry(normalized);
|
|
86
|
+
|
|
87
|
+
if (existing && !options.allowOverride) {
|
|
88
|
+
throw new Error(`Service "${normalized.name}" is already registered.`);
|
|
89
|
+
}
|
|
90
|
+
if (existing) {
|
|
91
|
+
this.removeEntry(existing);
|
|
92
|
+
}
|
|
8
93
|
|
|
9
|
-
|
|
10
|
-
|
|
94
|
+
const entry: ServiceEntry<T> = {
|
|
95
|
+
token: normalized.token,
|
|
96
|
+
name: normalized.name,
|
|
97
|
+
service,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
this.servicesByName.set(entry.name, entry);
|
|
101
|
+
if (entry.token) {
|
|
102
|
+
this.servicesByToken.set(entry.token.key, entry);
|
|
103
|
+
}
|
|
104
|
+
this.registrationOrder.push(entry as ServiceEntry);
|
|
11
105
|
return service;
|
|
12
106
|
}
|
|
13
107
|
|
|
14
|
-
get<T extends Service>(
|
|
15
|
-
|
|
108
|
+
get<T extends Service>(identifier: ServiceIdentifier<T>): T | undefined;
|
|
109
|
+
get<T extends Service>(identifier: ServiceToken<T>): T | undefined;
|
|
110
|
+
get<T extends Service>(identifier: string): T | undefined;
|
|
111
|
+
get<T extends Service>(identifier: ServiceIdentifier<T>): T | undefined {
|
|
112
|
+
const normalized = this.normalizeIdentifier(identifier);
|
|
113
|
+
const entry = this.findEntry(normalized);
|
|
114
|
+
return entry?.service as T | undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getOrThrow<T extends Service>(
|
|
118
|
+
identifier: ServiceIdentifier<T>,
|
|
119
|
+
errorMessage?: string,
|
|
120
|
+
): T;
|
|
121
|
+
getOrThrow<T extends Service>(
|
|
122
|
+
identifier: ServiceToken<T>,
|
|
123
|
+
errorMessage?: string,
|
|
124
|
+
): T;
|
|
125
|
+
getOrThrow<T extends Service>(identifier: string, errorMessage?: string): T;
|
|
126
|
+
getOrThrow<T extends Service>(
|
|
127
|
+
identifier: ServiceIdentifier<T>,
|
|
128
|
+
errorMessage?: string,
|
|
129
|
+
): T {
|
|
130
|
+
const service = this.get(identifier);
|
|
131
|
+
if (service) {
|
|
132
|
+
return service;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const normalized = this.normalizeIdentifier(identifier);
|
|
136
|
+
throw new Error(errorMessage ?? `Service "${normalized.name}" not found.`);
|
|
16
137
|
}
|
|
17
138
|
|
|
18
|
-
has(
|
|
19
|
-
|
|
139
|
+
has(identifier: ServiceIdentifier<Service>): boolean {
|
|
140
|
+
const normalized = this.normalizeIdentifier(identifier);
|
|
141
|
+
return Boolean(this.findEntry(normalized));
|
|
20
142
|
}
|
|
21
143
|
|
|
22
|
-
delete(
|
|
23
|
-
this.
|
|
144
|
+
delete(identifier: ServiceIdentifier<Service>): boolean {
|
|
145
|
+
const normalized = this.normalizeIdentifier(identifier);
|
|
146
|
+
const entry = this.findEntry(normalized);
|
|
147
|
+
if (!entry) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
this.removeEntry(entry);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
list(): RegisteredService[] {
|
|
155
|
+
return this.registrationOrder.map((entry) => ({
|
|
156
|
+
id: entry.name,
|
|
157
|
+
token: entry.token,
|
|
158
|
+
service: entry.service,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
clear() {
|
|
163
|
+
this.servicesByName.clear();
|
|
164
|
+
this.servicesByToken.clear();
|
|
165
|
+
this.registrationOrder.length = 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private findEntry(
|
|
169
|
+
identifier: NormalizedIdentifier,
|
|
170
|
+
): ServiceEntry | undefined {
|
|
171
|
+
if (identifier.token) {
|
|
172
|
+
return (
|
|
173
|
+
this.servicesByToken.get(identifier.token.key) ??
|
|
174
|
+
this.servicesByName.get(identifier.name)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return this.servicesByName.get(identifier.name);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private normalizeIdentifier<T extends Service>(
|
|
181
|
+
identifier: ServiceIdentifier<T>,
|
|
182
|
+
): NormalizedIdentifier<T> {
|
|
183
|
+
if (isServiceToken(identifier)) {
|
|
184
|
+
return { token: identifier, name: identifier.name };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const name = identifier.trim();
|
|
188
|
+
if (!name) {
|
|
189
|
+
throw new Error("Service identifier must be a non-empty string.");
|
|
190
|
+
}
|
|
191
|
+
return { name };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private removeEntry(entry: ServiceEntry) {
|
|
195
|
+
this.servicesByName.delete(entry.name);
|
|
196
|
+
if (entry.token) {
|
|
197
|
+
this.servicesByToken.delete(entry.token.key);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const index = this.registrationOrder.lastIndexOf(entry);
|
|
201
|
+
if (index >= 0) {
|
|
202
|
+
this.registrationOrder.splice(index, 1);
|
|
203
|
+
}
|
|
24
204
|
}
|
|
25
205
|
}
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
import { Command } from "../command";
|
|
2
|
-
import Disposable from "../disposable";
|
|
3
|
-
import { Service } from "../service";
|
|
4
|
-
|
|
5
|
-
export default class CommandService implements Service {
|
|
6
|
-
private commands: Map<string, Command> = new Map();
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Register a command
|
|
10
|
-
* @param commandId Command Name (ID)
|
|
11
|
-
* @param handler Command handler function
|
|
12
|
-
* @param thisArg The `this` context for the handler
|
|
13
|
-
* @returns Disposable to unregister the command
|
|
14
|
-
*/
|
|
15
|
-
registerCommand(
|
|
16
|
-
commandId: string,
|
|
17
|
-
handler: (...args: any[]) => any,
|
|
18
|
-
thisArg?: any,
|
|
19
|
-
): Disposable {
|
|
20
|
-
if (this.commands.has(commandId)) {
|
|
21
|
-
console.warn(
|
|
22
|
-
`Command "${commandId}" is already registered. Overwriting.`,
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const command: Command = {
|
|
27
|
-
id: commandId,
|
|
28
|
-
handler: thisArg ? handler.bind(thisArg) : handler,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
this.commands.set(commandId, command);
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
dispose: () => {
|
|
35
|
-
if (this.commands.get(commandId) === command) {
|
|
36
|
-
this.commands.delete(commandId);
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Execute a command
|
|
44
|
-
* @param commandId Command Name (ID)
|
|
45
|
-
* @param args Arguments to pass to the handler
|
|
46
|
-
* @returns The result of the command handler
|
|
47
|
-
*/
|
|
48
|
-
async executeCommand<T = any>(commandId: string, ...args: any[]): Promise<T> {
|
|
49
|
-
const command = this.commands.get(commandId);
|
|
50
|
-
if (!command) {
|
|
51
|
-
throw new Error(`Command "${commandId}" not found.`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
return await command.handler(...args);
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.error(`Error executing command "${commandId}":`, error);
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Get all registered commands
|
|
64
|
-
*/
|
|
65
|
-
getCommands(): Map<string, Command> {
|
|
66
|
-
return this.commands;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Get a specific command
|
|
71
|
-
*/
|
|
72
|
-
getCommand(commandId: string): Command | undefined {
|
|
73
|
-
return this.commands.get(commandId);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
dispose() {
|
|
77
|
-
this.commands.clear();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
1
|
+
import { Command } from "../command";
|
|
2
|
+
import Disposable from "../disposable";
|
|
3
|
+
import { Service } from "../service";
|
|
4
|
+
|
|
5
|
+
export default class CommandService implements Service {
|
|
6
|
+
private commands: Map<string, Command> = new Map();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Register a command
|
|
10
|
+
* @param commandId Command Name (ID)
|
|
11
|
+
* @param handler Command handler function
|
|
12
|
+
* @param thisArg The `this` context for the handler
|
|
13
|
+
* @returns Disposable to unregister the command
|
|
14
|
+
*/
|
|
15
|
+
registerCommand(
|
|
16
|
+
commandId: string,
|
|
17
|
+
handler: (...args: any[]) => any,
|
|
18
|
+
thisArg?: any,
|
|
19
|
+
): Disposable {
|
|
20
|
+
if (this.commands.has(commandId)) {
|
|
21
|
+
console.warn(
|
|
22
|
+
`Command "${commandId}" is already registered. Overwriting.`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const command: Command = {
|
|
27
|
+
id: commandId,
|
|
28
|
+
handler: thisArg ? handler.bind(thisArg) : handler,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
this.commands.set(commandId, command);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
dispose: () => {
|
|
35
|
+
if (this.commands.get(commandId) === command) {
|
|
36
|
+
this.commands.delete(commandId);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Execute a command
|
|
44
|
+
* @param commandId Command Name (ID)
|
|
45
|
+
* @param args Arguments to pass to the handler
|
|
46
|
+
* @returns The result of the command handler
|
|
47
|
+
*/
|
|
48
|
+
async executeCommand<T = any>(commandId: string, ...args: any[]): Promise<T> {
|
|
49
|
+
const command = this.commands.get(commandId);
|
|
50
|
+
if (!command) {
|
|
51
|
+
throw new Error(`Command "${commandId}" not found.`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
return await command.handler(...args);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Error executing command "${commandId}":`, error);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all registered commands
|
|
64
|
+
*/
|
|
65
|
+
getCommands(): Map<string, Command> {
|
|
66
|
+
return this.commands;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get a specific command
|
|
71
|
+
*/
|
|
72
|
+
getCommand(commandId: string): Command | undefined {
|
|
73
|
+
return this.commands.get(commandId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
dispose() {
|
|
77
|
+
this.commands.clear();
|
|
78
|
+
}
|
|
79
|
+
}
|