@theia/plugin-ext 1.71.0-next.41 → 1.71.0-next.43
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/lib/plugin/plugin-context.js +1 -1
- package/lib/plugin/plugin-context.js.map +1 -1
- package/lib/plugin/terminal-ext.d.ts +13 -3
- package/lib/plugin/terminal-ext.d.ts.map +1 -1
- package/lib/plugin/terminal-ext.js +51 -10
- package/lib/plugin/terminal-ext.js.map +1 -1
- package/lib/plugin/terminal-ext.spec.d.ts +2 -0
- package/lib/plugin/terminal-ext.spec.d.ts.map +1 -0
- package/lib/plugin/terminal-ext.spec.js +285 -0
- package/lib/plugin/terminal-ext.spec.js.map +1 -0
- package/package.json +29 -29
- package/src/plugin/plugin-context.ts +4 -4
- package/src/plugin/terminal-ext.spec.ts +350 -0
- package/src/plugin/terminal-ext.ts +58 -12
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import * as chai from 'chai';
|
|
18
|
+
import * as theia from '@theia/plugin';
|
|
19
|
+
import { TerminalServiceMain, Plugin, TerminalOptions } from '../common/plugin-api-rpc';
|
|
20
|
+
import { RPCProtocol, ProxyIdentifier } from '../common/rpc-protocol';
|
|
21
|
+
import { TerminalServiceExtImpl, TerminalExtImpl } from './terminal-ext';
|
|
22
|
+
import { TerminalExitReason } from './types-impl';
|
|
23
|
+
|
|
24
|
+
const expect = chai.expect;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a mock RPCProtocol that returns the given proxy for TERMINAL_MAIN.
|
|
28
|
+
*/
|
|
29
|
+
function createMockRpc(proxy: Partial<TerminalServiceMain>): RPCProtocol {
|
|
30
|
+
return {
|
|
31
|
+
getProxy<T>(_proxyId: ProxyIdentifier<T>): T {
|
|
32
|
+
return proxy as unknown as T;
|
|
33
|
+
},
|
|
34
|
+
set<T, R extends T>(_identifier: ProxyIdentifier<T>, instance: R): R {
|
|
35
|
+
return instance;
|
|
36
|
+
},
|
|
37
|
+
dispose(): void { }
|
|
38
|
+
} as RPCProtocol;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a minimal mock Plugin object.
|
|
43
|
+
*/
|
|
44
|
+
function createMockPlugin(): Plugin {
|
|
45
|
+
return {
|
|
46
|
+
pluginPath: '/test',
|
|
47
|
+
pluginFolder: '/test',
|
|
48
|
+
pluginUri: 'file:///test',
|
|
49
|
+
model: { id: 'test.plugin' } as Plugin['model'],
|
|
50
|
+
rawModel: {} as Plugin['rawModel'],
|
|
51
|
+
lifecycle: {} as Plugin['lifecycle'],
|
|
52
|
+
isUnderDevelopment: false
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates a stub TerminalServiceMain that records calls.
|
|
58
|
+
*/
|
|
59
|
+
function createMockProxy(): TerminalServiceMain & { createdTerminals: { id: string; options: TerminalOptions }[] } {
|
|
60
|
+
const createdTerminals: { id: string; options: TerminalOptions }[] = [];
|
|
61
|
+
return {
|
|
62
|
+
createdTerminals,
|
|
63
|
+
$createTerminal(id: string, options: TerminalOptions): Promise<string> {
|
|
64
|
+
createdTerminals.push({ id, options });
|
|
65
|
+
return Promise.resolve(id);
|
|
66
|
+
},
|
|
67
|
+
$sendText(): void { },
|
|
68
|
+
$write(): void { },
|
|
69
|
+
$resize(): void { },
|
|
70
|
+
$show(): void { },
|
|
71
|
+
$hide(): void { },
|
|
72
|
+
$dispose(): void { },
|
|
73
|
+
$setName(): void { },
|
|
74
|
+
$writeByTerminalId(): void { },
|
|
75
|
+
$resizeByTerminalId(): void { },
|
|
76
|
+
$disposeByTerminalId(): void { },
|
|
77
|
+
$setNameByTerminalId(): void { },
|
|
78
|
+
$setEnvironmentVariableCollection(): void { },
|
|
79
|
+
$registerTerminalLinkProvider(): void { },
|
|
80
|
+
$unregisterTerminalLinkProvider(): void { },
|
|
81
|
+
$registerTerminalObserver(): void { },
|
|
82
|
+
$unregisterTerminalObserver(): void { },
|
|
83
|
+
} as unknown as TerminalServiceMain & { createdTerminals: { id: string; options: TerminalOptions }[] };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe('TerminalServiceExtImpl', () => {
|
|
87
|
+
let proxy: ReturnType<typeof createMockProxy>;
|
|
88
|
+
let service: TerminalServiceExtImpl;
|
|
89
|
+
let plugin: Plugin;
|
|
90
|
+
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
proxy = createMockProxy();
|
|
93
|
+
const rpc = createMockRpc(proxy);
|
|
94
|
+
service = new TerminalServiceExtImpl(rpc);
|
|
95
|
+
plugin = createMockPlugin();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('terminals list', () => {
|
|
99
|
+
it('returns empty array initially', () => {
|
|
100
|
+
expect(service.terminals).to.deep.equal([]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('includes terminals after creation via $terminalCreated', () => {
|
|
104
|
+
service.$terminalCreated('t1', 'Terminal 1');
|
|
105
|
+
expect(service.terminals).to.have.length(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('removes terminals after $terminalClosed', () => {
|
|
109
|
+
service.$terminalCreated('t1', 'Terminal 1');
|
|
110
|
+
service.$terminalClosed('t1', { code: 0, reason: TerminalExitReason.Process });
|
|
111
|
+
expect(service.terminals).to.have.length(0);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('API object identity', () => {
|
|
116
|
+
it('returns the raw TerminalExtImpl when no wrapper is provided', () => {
|
|
117
|
+
const terminal = service.createTerminal(plugin, 'Test Terminal');
|
|
118
|
+
expect(terminal).to.be.instanceOf(TerminalExtImpl);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('returns the wrapped API object when a wrapper is provided', () => {
|
|
122
|
+
const wrapper = (t: TerminalExtImpl): theia.Terminal => ({ ...t, name: 'wrapped' } as unknown as theia.Terminal);
|
|
123
|
+
service.createTerminal(plugin, 'Test Terminal', undefined, undefined, wrapper);
|
|
124
|
+
const id = proxy.createdTerminals[0].id;
|
|
125
|
+
service.$terminalCreated(id, 'Test Terminal');
|
|
126
|
+
const terminal = service.terminals[0];
|
|
127
|
+
expect(terminal.name).to.equal('wrapped');
|
|
128
|
+
expect(terminal).to.not.be.instanceOf(TerminalExtImpl);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('fires onDidOpenTerminal with the API object, not the raw terminal', () => {
|
|
132
|
+
const apiObject = { marker: 'api-object' } as unknown as theia.Terminal;
|
|
133
|
+
const wrapper = (_t: TerminalExtImpl): theia.Terminal => apiObject;
|
|
134
|
+
service.createTerminal(plugin, 'Test Terminal', undefined, undefined, wrapper);
|
|
135
|
+
|
|
136
|
+
const opened: theia.Terminal[] = [];
|
|
137
|
+
service.onDidOpenTerminal(t => opened.push(t));
|
|
138
|
+
|
|
139
|
+
// Get the ID from the proxy call
|
|
140
|
+
const id = proxy.createdTerminals[0].id;
|
|
141
|
+
service.$terminalCreated(id, 'Test Terminal');
|
|
142
|
+
|
|
143
|
+
expect(opened).to.have.length(1);
|
|
144
|
+
expect(opened[0]).to.equal(apiObject);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('fires onDidCloseTerminal with the API object', () => {
|
|
148
|
+
const apiObject = { marker: 'api-object' } as unknown as theia.Terminal;
|
|
149
|
+
const wrapper = (_t: TerminalExtImpl): theia.Terminal => apiObject;
|
|
150
|
+
service.createTerminal(plugin, 'Test Terminal', undefined, undefined, wrapper);
|
|
151
|
+
|
|
152
|
+
const closed: theia.Terminal[] = [];
|
|
153
|
+
service.onDidCloseTerminal(t => closed.push(t));
|
|
154
|
+
|
|
155
|
+
const id = proxy.createdTerminals[0].id;
|
|
156
|
+
service.$terminalCreated(id, 'Test Terminal');
|
|
157
|
+
service.$terminalClosed(id, { code: 0, reason: TerminalExitReason.Process });
|
|
158
|
+
|
|
159
|
+
expect(closed).to.have.length(1);
|
|
160
|
+
expect(closed[0]).to.equal(apiObject);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('fires onDidChangeTerminalState with the API object on interaction', () => {
|
|
164
|
+
const apiObject = { marker: 'api-object' } as unknown as theia.Terminal;
|
|
165
|
+
const wrapper = (_t: TerminalExtImpl): theia.Terminal => apiObject;
|
|
166
|
+
service.createTerminal(plugin, 'Test Terminal', undefined, undefined, wrapper);
|
|
167
|
+
|
|
168
|
+
const stateChanged: theia.Terminal[] = [];
|
|
169
|
+
service.onDidChangeTerminalState(t => stateChanged.push(t));
|
|
170
|
+
|
|
171
|
+
const id = proxy.createdTerminals[0].id;
|
|
172
|
+
service.$terminalCreated(id, 'Test Terminal');
|
|
173
|
+
service.$terminalOnInteraction(id);
|
|
174
|
+
|
|
175
|
+
expect(stateChanged).to.have.length(1);
|
|
176
|
+
expect(stateChanged[0]).to.equal(apiObject);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('fires onDidChangeTerminalState with the API object on shell type change', () => {
|
|
180
|
+
const apiObject = { marker: 'api-object' } as unknown as theia.Terminal;
|
|
181
|
+
const wrapper = (_t: TerminalExtImpl): theia.Terminal => apiObject;
|
|
182
|
+
service.createTerminal(plugin, 'Test Terminal', undefined, undefined, wrapper);
|
|
183
|
+
|
|
184
|
+
const stateChanged: theia.Terminal[] = [];
|
|
185
|
+
service.onDidChangeTerminalState(t => stateChanged.push(t));
|
|
186
|
+
|
|
187
|
+
const id = proxy.createdTerminals[0].id;
|
|
188
|
+
service.$terminalCreated(id, 'Test Terminal');
|
|
189
|
+
service.$terminalShellTypeChanged(id, '/bin/zsh');
|
|
190
|
+
|
|
191
|
+
expect(stateChanged).to.have.length(1);
|
|
192
|
+
expect(stateChanged[0]).to.equal(apiObject);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('returns the API object from the terminals list', () => {
|
|
196
|
+
const apiObject = { marker: 'api-object' } as unknown as theia.Terminal;
|
|
197
|
+
const wrapper = (_t: TerminalExtImpl): theia.Terminal => apiObject;
|
|
198
|
+
service.createTerminal(plugin, 'Test Terminal', undefined, undefined, wrapper);
|
|
199
|
+
|
|
200
|
+
const id = proxy.createdTerminals[0].id;
|
|
201
|
+
service.$terminalCreated(id, 'Test Terminal');
|
|
202
|
+
|
|
203
|
+
const terminals = service.terminals;
|
|
204
|
+
expect(terminals).to.have.length(1);
|
|
205
|
+
expect(terminals[0]).to.equal(apiObject);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('returns the API object as activeTerminal', () => {
|
|
209
|
+
const apiObject = { marker: 'api-object' } as unknown as theia.Terminal;
|
|
210
|
+
const wrapper = (_t: TerminalExtImpl): theia.Terminal => apiObject;
|
|
211
|
+
service.createTerminal(plugin, 'Test Terminal', undefined, undefined, wrapper);
|
|
212
|
+
|
|
213
|
+
const id = proxy.createdTerminals[0].id;
|
|
214
|
+
service.$terminalCreated(id, 'Test Terminal');
|
|
215
|
+
service.$currentTerminalChanged(id);
|
|
216
|
+
|
|
217
|
+
expect(service.activeTerminal).to.equal(apiObject);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('cleans up API object on terminal close', () => {
|
|
221
|
+
const apiObject = { marker: 'api-object' } as unknown as theia.Terminal;
|
|
222
|
+
const wrapper = (_t: TerminalExtImpl): theia.Terminal => apiObject;
|
|
223
|
+
service.createTerminal(plugin, 'Test Terminal', undefined, undefined, wrapper);
|
|
224
|
+
|
|
225
|
+
const id = proxy.createdTerminals[0].id;
|
|
226
|
+
service.$terminalCreated(id, 'Test Terminal');
|
|
227
|
+
service.$terminalClosed(id, { code: 0, reason: TerminalExitReason.Process });
|
|
228
|
+
|
|
229
|
+
expect(service.terminals).to.have.length(0);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('parentTerminal resolution', () => {
|
|
234
|
+
it('resolves parentTerminal from API proxy objects', () => {
|
|
235
|
+
const apiObject = { marker: 'parent-api' } as unknown as theia.Terminal;
|
|
236
|
+
const wrapper = (_t: TerminalExtImpl): theia.Terminal => apiObject;
|
|
237
|
+
service.createTerminal(plugin, 'Parent', undefined, undefined, wrapper);
|
|
238
|
+
|
|
239
|
+
const parentId = proxy.createdTerminals[0].id;
|
|
240
|
+
service.$terminalCreated(parentId, 'Parent');
|
|
241
|
+
|
|
242
|
+
// Create a child with parentTerminal set to the API object
|
|
243
|
+
service.createTerminal(plugin, {
|
|
244
|
+
name: 'Child',
|
|
245
|
+
location: { parentTerminal: apiObject }
|
|
246
|
+
} as theia.TerminalOptions);
|
|
247
|
+
|
|
248
|
+
expect(proxy.createdTerminals).to.have.length(2);
|
|
249
|
+
// The second createTerminal call should have passed the parent ID
|
|
250
|
+
// We verify it was called on the proxy (the parentId arg is the 3rd parameter)
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('resolves parentTerminal from raw terminal objects', () => {
|
|
254
|
+
const rawTerminal = service.createTerminal(plugin, 'Parent');
|
|
255
|
+
|
|
256
|
+
const parentId = proxy.createdTerminals[0].id;
|
|
257
|
+
service.$terminalCreated(parentId, 'Parent');
|
|
258
|
+
|
|
259
|
+
// Create a child with parentTerminal set to the raw terminal
|
|
260
|
+
service.createTerminal(plugin, {
|
|
261
|
+
name: 'Child',
|
|
262
|
+
location: { parentTerminal: rawTerminal }
|
|
263
|
+
} as theia.TerminalOptions);
|
|
264
|
+
|
|
265
|
+
expect(proxy.createdTerminals).to.have.length(2);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('events for terminals without wrapper', () => {
|
|
270
|
+
it('fires onDidOpenTerminal with the raw terminal when no wrapper is used', () => {
|
|
271
|
+
const opened: theia.Terminal[] = [];
|
|
272
|
+
service.onDidOpenTerminal(t => opened.push(t));
|
|
273
|
+
|
|
274
|
+
service.$terminalCreated('ext-t1', 'External Terminal');
|
|
275
|
+
|
|
276
|
+
expect(opened).to.have.length(1);
|
|
277
|
+
expect(opened[0]).to.be.instanceOf(TerminalExtImpl);
|
|
278
|
+
expect(opened[0].name).to.equal('External Terminal');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('fires onDidCloseTerminal with the raw terminal when no wrapper is used', () => {
|
|
282
|
+
const closed: theia.Terminal[] = [];
|
|
283
|
+
service.onDidCloseTerminal(t => closed.push(t));
|
|
284
|
+
|
|
285
|
+
service.$terminalCreated('ext-t1', 'External Terminal');
|
|
286
|
+
service.$terminalClosed('ext-t1', { code: 0, reason: TerminalExitReason.Process });
|
|
287
|
+
|
|
288
|
+
expect(closed).to.have.length(1);
|
|
289
|
+
expect(closed[0]).to.be.instanceOf(TerminalExtImpl);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('shell change', () => {
|
|
294
|
+
it('fires onDidChangeShell when shell changes', async () => {
|
|
295
|
+
const shells: string[] = [];
|
|
296
|
+
service.onDidChangeShell(s => shells.push(s));
|
|
297
|
+
|
|
298
|
+
await service.$setShell('/bin/zsh');
|
|
299
|
+
|
|
300
|
+
expect(shells).to.deep.equal(['/bin/zsh']);
|
|
301
|
+
expect(service.defaultShell).to.equal('/bin/zsh');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('does not fire onDidChangeShell when shell is the same', async () => {
|
|
305
|
+
const shells: string[] = [];
|
|
306
|
+
await service.$setShell('/bin/zsh');
|
|
307
|
+
|
|
308
|
+
service.onDidChangeShell(s => shells.push(s));
|
|
309
|
+
await service.$setShell('/bin/zsh');
|
|
310
|
+
|
|
311
|
+
expect(shells).to.deep.equal([]);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('$terminalNameChanged', () => {
|
|
316
|
+
it('updates the terminal name', () => {
|
|
317
|
+
service.$terminalCreated('t1', 'Old Name');
|
|
318
|
+
service.$terminalNameChanged('t1', 'New Name');
|
|
319
|
+
|
|
320
|
+
expect(service.terminals[0].name).to.equal('New Name');
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('activeTerminal', () => {
|
|
325
|
+
it('is undefined initially', () => {
|
|
326
|
+
expect(service.activeTerminal).to.equal(undefined);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('reflects the current active terminal', () => {
|
|
330
|
+
service.$terminalCreated('t1', 'Terminal 1');
|
|
331
|
+
service.$currentTerminalChanged('t1');
|
|
332
|
+
|
|
333
|
+
expect(service.activeTerminal).to.not.equal(undefined);
|
|
334
|
+
expect(service.activeTerminal!.name).to.equal('Terminal 1');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('fires onDidChangeActiveTerminal', () => {
|
|
338
|
+
const changes: (theia.Terminal | undefined)[] = [];
|
|
339
|
+
service.onDidChangeActiveTerminal(t => changes.push(t));
|
|
340
|
+
|
|
341
|
+
service.$terminalCreated('t1', 'Terminal 1');
|
|
342
|
+
service.$currentTerminalChanged('t1');
|
|
343
|
+
service.$currentTerminalChanged(undefined);
|
|
344
|
+
|
|
345
|
+
expect(changes).to.have.length(2);
|
|
346
|
+
expect(changes[0]!.name).to.equal('Terminal 1');
|
|
347
|
+
expect(changes[1]).to.equal(undefined);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
});
|
|
@@ -38,6 +38,13 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
38
38
|
|
|
39
39
|
private readonly _terminals = new Map<string, TerminalExtImpl>();
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Stores the API objects (Proxies) returned to plugins for each terminal.
|
|
43
|
+
* This ensures that events fire with the same object instance that plugins received,
|
|
44
|
+
* allowing strict equality checks (===) to work correctly.
|
|
45
|
+
*/
|
|
46
|
+
private readonly _terminalApiObjects = new Map<string, theia.Terminal>();
|
|
47
|
+
|
|
41
48
|
private readonly _pseudoTerminals = new Map<string, PseudoTerminal>();
|
|
42
49
|
|
|
43
50
|
private static nextProviderId = 0;
|
|
@@ -66,8 +73,8 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
66
73
|
this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.TERMINAL_MAIN);
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
get terminals():
|
|
70
|
-
return [...this._terminals.
|
|
76
|
+
get terminals(): theia.Terminal[] {
|
|
77
|
+
return [...this._terminals.keys()].map(id => this.getApiObject(id)).filter((t): t is theia.Terminal => t !== undefined);
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
get defaultShell(): string {
|
|
@@ -84,7 +91,9 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
84
91
|
createTerminal(
|
|
85
92
|
plugin: Plugin,
|
|
86
93
|
nameOrOptions: theia.TerminalOptions | theia.PseudoTerminalOptions | theia.ExtensionTerminalOptions | string | undefined,
|
|
87
|
-
shellPath?: string,
|
|
94
|
+
shellPath?: string,
|
|
95
|
+
shellArgs?: string[] | string,
|
|
96
|
+
apiObjectWrapper?: (terminal: TerminalExtImpl) => theia.Terminal
|
|
88
97
|
): theia.Terminal {
|
|
89
98
|
const id = `plugin-terminal-${UUID.uuid4()}`;
|
|
90
99
|
let options: TerminalOptions;
|
|
@@ -110,7 +119,15 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
110
119
|
let parentId;
|
|
111
120
|
if (options.location && typeof options.location === 'object' && 'parentTerminal' in options.location) {
|
|
112
121
|
const parentTerminal = options.location.parentTerminal;
|
|
113
|
-
|
|
122
|
+
// Check both the API proxy objects and the raw terminals, since
|
|
123
|
+
// plugins receive the proxy but _terminals stores the raw object.
|
|
124
|
+
for (const [k, v] of this._terminalApiObjects) {
|
|
125
|
+
if (v === parentTerminal) {
|
|
126
|
+
parentId = k;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!parentId) {
|
|
114
131
|
for (const [k, v] of this._terminals) {
|
|
115
132
|
if (v === parentTerminal) {
|
|
116
133
|
parentId = k;
|
|
@@ -136,7 +153,16 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
136
153
|
if (typeof nameOrOptions === 'object' && 'pty' in nameOrOptions) {
|
|
137
154
|
creationOptions = nameOrOptions;
|
|
138
155
|
}
|
|
139
|
-
|
|
156
|
+
const terminal = this.obtainTerminal(id, options.name || 'Terminal', creationOptions);
|
|
157
|
+
|
|
158
|
+
// If a wrapper function is provided, wrap the terminal and register the API object
|
|
159
|
+
// This ensures events fire with the same object instance that plugins received
|
|
160
|
+
if (apiObjectWrapper) {
|
|
161
|
+
const apiObject = apiObjectWrapper(terminal);
|
|
162
|
+
this._terminalApiObjects.set(id, apiObject);
|
|
163
|
+
return apiObject;
|
|
164
|
+
}
|
|
165
|
+
return terminal;
|
|
140
166
|
}
|
|
141
167
|
|
|
142
168
|
attachPtyToTerminal(terminalId: number, pty: theia.Pseudoterminal): void {
|
|
@@ -153,6 +179,13 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
153
179
|
return terminal;
|
|
154
180
|
}
|
|
155
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Gets the API object for a terminal by ID, falling back to the raw terminal if not set.
|
|
184
|
+
*/
|
|
185
|
+
protected getApiObject(id: string): theia.Terminal | undefined {
|
|
186
|
+
return this._terminalApiObjects.get(id) ?? this._terminals.get(id);
|
|
187
|
+
}
|
|
188
|
+
|
|
156
189
|
$terminalOnInput(id: string, data: string): void {
|
|
157
190
|
const terminal = this._pseudoTerminals.get(id);
|
|
158
191
|
if (!terminal) {
|
|
@@ -168,7 +201,10 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
168
201
|
}
|
|
169
202
|
if (!terminal.state.isInteractedWith) {
|
|
170
203
|
terminal.state = { ...terminal.state, isInteractedWith: true };
|
|
171
|
-
this.
|
|
204
|
+
const apiObject = this.getApiObject(id);
|
|
205
|
+
if (apiObject) {
|
|
206
|
+
this.onDidChangeTerminalStateEmitter.fire(apiObject);
|
|
207
|
+
}
|
|
172
208
|
}
|
|
173
209
|
}
|
|
174
210
|
|
|
@@ -179,7 +215,10 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
179
215
|
}
|
|
180
216
|
if (terminal.state.shell !== shellType) {
|
|
181
217
|
terminal.state = { ...terminal.state, shell: shellType };
|
|
182
|
-
this.
|
|
218
|
+
const apiObject = this.getApiObject(id);
|
|
219
|
+
if (apiObject) {
|
|
220
|
+
this.onDidChangeTerminalStateEmitter.fire(apiObject);
|
|
221
|
+
}
|
|
183
222
|
}
|
|
184
223
|
}
|
|
185
224
|
|
|
@@ -194,7 +233,10 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
194
233
|
$terminalCreated(id: string, name: string): void {
|
|
195
234
|
const terminal = this.obtainTerminal(id, name);
|
|
196
235
|
terminal.id.resolve(id);
|
|
197
|
-
this.
|
|
236
|
+
const apiObject = this.getApiObject(id);
|
|
237
|
+
if (apiObject) {
|
|
238
|
+
this.onDidOpenTerminalEmitter.fire(apiObject);
|
|
239
|
+
}
|
|
198
240
|
}
|
|
199
241
|
|
|
200
242
|
$terminalNameChanged(id: string, name: string): void {
|
|
@@ -234,8 +276,12 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
234
276
|
const terminal = this._terminals.get(id);
|
|
235
277
|
if (terminal) {
|
|
236
278
|
terminal.exitStatus = exitStatus ?? { code: undefined, reason: TerminalExitReason.Unknown };
|
|
237
|
-
this.
|
|
279
|
+
const apiObject = this.getApiObject(id);
|
|
280
|
+
if (apiObject) {
|
|
281
|
+
this.onDidCloseTerminalEmitter.fire(apiObject);
|
|
282
|
+
}
|
|
238
283
|
this._terminals.delete(id);
|
|
284
|
+
this._terminalApiObjects.delete(id);
|
|
239
285
|
}
|
|
240
286
|
const pseudoTerminal = this._pseudoTerminals.get(id);
|
|
241
287
|
if (pseudoTerminal) {
|
|
@@ -245,8 +291,8 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
245
291
|
}
|
|
246
292
|
|
|
247
293
|
private activeTerminalId: string | undefined;
|
|
248
|
-
get activeTerminal():
|
|
249
|
-
return this.activeTerminalId && this.
|
|
294
|
+
get activeTerminal(): theia.Terminal | undefined {
|
|
295
|
+
return this.activeTerminalId && this.getApiObject(this.activeTerminalId) || undefined;
|
|
250
296
|
}
|
|
251
297
|
$currentTerminalChanged(id: string | undefined): void {
|
|
252
298
|
this.activeTerminalId = id;
|
|
@@ -319,7 +365,7 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
|
|
|
319
365
|
|
|
320
366
|
async $provideTerminalLinks(line: string, terminalId: string, token: theia.CancellationToken): Promise<ProvidedTerminalLink[]> {
|
|
321
367
|
const links: ProvidedTerminalLink[] = [];
|
|
322
|
-
const terminal = this.
|
|
368
|
+
const terminal = this.getApiObject(terminalId);
|
|
323
369
|
if (terminal) {
|
|
324
370
|
for (const [providerId, provider] of this.terminalLinkProviders) {
|
|
325
371
|
const providedLinks = await provider.provideTerminalLinks({ line, terminal }, token);
|