Skip to content

Commit da1049e

Browse files
authored
Merge pull request microsoft#183214 from microsoft/tyriar/embedder_api
Add embedder API for creating simple readonly terminals
2 parents fe14808 + b932def commit da1049e

File tree

6 files changed

+180
-2
lines changed

6 files changed

+180
-2
lines changed

src/vs/platform/instantiation/common/extensions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const _registry: [ServiceIdentifier<any>, SyncDescriptor<any>][] = [];
1010

1111
export const enum InstantiationType {
1212
/**
13-
* Instantiate this service as soon as a consumer depdends on it. _Note_ that this
13+
* Instantiate this service as soon as a consumer depends on it. _Note_ that this
1414
* is more costly as some upfront work is done that is likely not needed
1515
*/
1616
Eager = 0,

src/vs/workbench/browser/web.api.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { TunnelProviderFeatures } from 'vs/platform/tunnel/common/tunnel';
1717
import type { IProgress, IProgressCompositeOptions, IProgressDialogOptions, IProgressNotificationOptions, IProgressOptions, IProgressStep, IProgressWindowOptions } from 'vs/platform/progress/common/progress';
1818
import type { ITextEditorOptions } from 'vs/platform/editor/common/editor';
1919
import type { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService';
20+
import type { IEmbedderTerminalOptions } from 'vs/workbench/services/terminal/common/embedderTerminalService';
2021

2122
/**
2223
* The `IWorkbench` interface is the API facade for web embedders
@@ -90,6 +91,17 @@ export interface IWorkbench {
9091
options: IProgressOptions | IProgressDialogOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
9192
task: (progress: IProgress<IProgressStep>) => Promise<R>
9293
): Promise<R>;
94+
95+
/**
96+
* Creates a terminal with limited capabilities that is intended for
97+
* writing output from the embedder before the workbench has finished
98+
* loading. When an embedder terminal is created it will automatically
99+
* show to the user.
100+
*
101+
* @param options The definition of the terminal, this is similar to
102+
* `ExtensionTerminalOptions` in the extension API.
103+
*/
104+
createTerminal(options: IEmbedderTerminalOptions): void;
93105
};
94106

95107
workspace: {

src/vs/workbench/browser/web.main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import { UserDataSyncInitializer } from 'vs/workbench/services/userDataSync/brow
9191
import { BrowserRemoteResourceLoader } from 'vs/workbench/services/remote/browser/browserRemoteResourceHandler';
9292
import { BufferLogger } from 'vs/platform/log/common/bufferLog';
9393
import { FileLoggerService } from 'vs/platform/log/common/fileLog';
94+
import { IEmbedderTerminalService } from 'vs/workbench/services/terminal/common/embedderTerminalService';
9495

9596
export class BrowserMain extends Disposable {
9697

@@ -151,6 +152,7 @@ export class BrowserMain extends Disposable {
151152
const instantiationService = accessor.get(IInstantiationService);
152153
const remoteExplorerService = accessor.get(IRemoteExplorerService);
153154
const labelService = accessor.get(ILabelService);
155+
const embedderTerminalService = accessor.get(IEmbedderTerminalService);
154156

155157
let logger: DelayedLogChannel | undefined = undefined;
156158

@@ -181,7 +183,8 @@ export class BrowserMain extends Disposable {
181183
}
182184
},
183185
window: {
184-
withProgress: (options, task) => progressService.withProgress(options, task)
186+
withProgress: (options, task) => progressService.withProgress(options, task),
187+
createTerminal: (options) => embedderTerminalService.createTerminal(options),
185188
},
186189
workspace: {
187190
openTunnel: async tunnelOptions => {

src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ITerminalEditorService, ITerminalGroupService, ITerminalInstanceService
1212
import { parseTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri';
1313
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
1414
import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
15+
import { IEmbedderTerminalService } from 'vs/workbench/services/terminal/common/embedderTerminalService';
1516

1617
/**
1718
* The main contribution for the terminal contrib. This contains calls to other components necessary
@@ -21,6 +22,7 @@ import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/s
2122
export class TerminalMainContribution extends Disposable implements IWorkbenchContribution {
2223
constructor(
2324
@IEditorResolverService editorResolverService: IEditorResolverService,
25+
@IEmbedderTerminalService embedderTerminalService: IEmbedderTerminalService,
2426
@ILabelService labelService: ILabelService,
2527
@ITerminalService terminalService: ITerminalService,
2628
@ITerminalEditorService terminalEditorService: ITerminalEditorService,
@@ -88,5 +90,14 @@ export class TerminalMainContribution extends Disposable implements IWorkbenchCo
8890
separator: ''
8991
}
9092
});
93+
94+
embedderTerminalService.onDidCreateTerminal(async embedderTerminal => {
95+
const terminal = await terminalService.createTerminal({
96+
config: embedderTerminal,
97+
location: TerminalLocation.Panel
98+
});
99+
terminalService.setActiveInstance(terminal);
100+
await terminalService.revealActiveTerminal();
101+
});
91102
}
92103
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
7+
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
8+
import { Emitter, Event } from 'vs/base/common/event';
9+
import { IProcessDataEvent, IProcessProperty, IProcessPropertyMap, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, ProcessPropertyType } from 'vs/platform/terminal/common/terminal';
10+
import { Disposable } from 'vs/base/common/lifecycle';
11+
12+
export const IEmbedderTerminalService = createDecorator<IEmbedderTerminalService>('embedderTerminalService');
13+
14+
/**
15+
* Manages terminals that the embedder can create before the terminal contrib is available.
16+
*/
17+
export interface IEmbedderTerminalService {
18+
readonly _serviceBrand: undefined;
19+
20+
readonly onDidCreateTerminal: Event<IShellLaunchConfig>;
21+
22+
createTerminal(options: IEmbedderTerminalOptions): void;
23+
}
24+
25+
export type EmbedderTerminal = IShellLaunchConfig & Required<Pick<IShellLaunchConfig, 'customPtyImplementation'>>;
26+
27+
export interface IEmbedderTerminalOptions {
28+
name: string;
29+
pty: IEmbedderTerminalPty;
30+
31+
// Extension APIs that have not been implemented for embedders:
32+
// iconPath?: URI | { light: URI; dark: URI } | ThemeIcon;
33+
// color?: ThemeColor;
34+
// location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions;
35+
// isTransient?: boolean;
36+
}
37+
38+
/**
39+
* See Pseudoterminal on the vscode API for usage.
40+
*/
41+
export interface IEmbedderTerminalPty {
42+
onDidWrite: Event<string>;
43+
onDidClose?: Event<void | number>;
44+
onDidChangeName?: Event<string>;
45+
46+
open(): void;
47+
close(): void;
48+
49+
// Extension APIs that have not been implemented for embedders:
50+
// onDidOverrideDimensions?: Event<TerminalDimensions | undefined>;
51+
// handleInput?(data: string): void;
52+
// setDimensions?(dimensions: TerminalDimensions): void;
53+
}
54+
55+
class EmbedderTerminalService implements IEmbedderTerminalService {
56+
declare _serviceBrand: undefined;
57+
58+
private readonly _onDidCreateTerminal = new Emitter<IShellLaunchConfig>();
59+
readonly onDidCreateTerminal = Event.buffer(this._onDidCreateTerminal.event);
60+
61+
createTerminal(options: IEmbedderTerminalOptions): void {
62+
const slc: EmbedderTerminal = {
63+
name: options.name,
64+
isFeatureTerminal: true,
65+
customPtyImplementation(terminalId, cols, rows) {
66+
return new EmbedderTerminalProcess(terminalId, options.pty);
67+
},
68+
};
69+
this._onDidCreateTerminal.fire(slc);
70+
}
71+
}
72+
73+
74+
class EmbedderTerminalProcess extends Disposable implements ITerminalChildProcess {
75+
readonly #pty: IEmbedderTerminalPty;
76+
77+
readonly shouldPersist = false;
78+
79+
readonly onProcessData: Event<IProcessDataEvent | string>;
80+
readonly #onProcessReady = this._register(new Emitter<{ pid: number; cwd: string }>());
81+
readonly onProcessReady = this.#onProcessReady.event;
82+
readonly #onDidChangeProperty = this._register(new Emitter<IProcessProperty<any>>());
83+
readonly onDidChangeProperty = this.#onDidChangeProperty.event;
84+
readonly #onProcessExit = this._register(new Emitter<number | undefined>());
85+
readonly onProcessExit = this.#onProcessExit.event;
86+
87+
constructor(
88+
readonly id: number,
89+
pty: IEmbedderTerminalPty
90+
) {
91+
super();
92+
93+
this.#pty = pty;
94+
this.onProcessData = this.#pty.onDidWrite;
95+
if (this.#pty.onDidClose) {
96+
this._register(this.#pty.onDidClose(e => this.#onProcessExit.fire(e || undefined)));
97+
}
98+
if (this.#pty.onDidChangeName) {
99+
this._register(this.#pty.onDidChangeName(e => this.#onDidChangeProperty.fire({
100+
type: ProcessPropertyType.Title,
101+
value: e
102+
})));
103+
}
104+
}
105+
106+
async start(): Promise<ITerminalLaunchError | undefined> {
107+
this.#onProcessReady.fire({ pid: -1, cwd: '' });
108+
this.#pty.open();
109+
return undefined;
110+
}
111+
shutdown(): void {
112+
this.#pty.close();
113+
}
114+
115+
// TODO: A lot of these aren't useful for some implementations of ITerminalChildProcess, should
116+
// they be optional? Should there be a base class for "external" consumers to implement?
117+
118+
input(): void {
119+
// not supported
120+
}
121+
async processBinary(): Promise<void> {
122+
// not supported
123+
}
124+
resize(): void {
125+
// no-op
126+
}
127+
acknowledgeDataEvent(): void {
128+
// no-op, flow control not currently implemented
129+
}
130+
async setUnicodeVersion(): Promise<void> {
131+
// no-op
132+
}
133+
async getInitialCwd(): Promise<string> {
134+
return '';
135+
}
136+
async getCwd(): Promise<string> {
137+
return '';
138+
}
139+
async getLatency(): Promise<number> {
140+
return 0;
141+
}
142+
refreshProperty<T extends ProcessPropertyType>(property: ProcessPropertyType): Promise<IProcessPropertyMap[T]> {
143+
throw new Error(`refreshProperty is not suppported in EmbedderTerminalProcess. property: ${property}`);
144+
}
145+
146+
updateProperty(property: ProcessPropertyType, value: any): Promise<void> {
147+
throw new Error(`updateProperty is not suppported in EmbedderTerminalProcess. property: ${property}, value: ${value}`);
148+
}
149+
}
150+
151+
registerSingleton(IEmbedderTerminalService, EmbedderTerminalService, InstantiationType.Delayed);

src/vs/workbench/workbench.common.main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ import 'vs/workbench/services/userDataProfile/browser/userDataProfileManagement'
9090
import 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles';
9191
import 'vs/workbench/services/remote/common/remoteExplorerService';
9292
import 'vs/workbench/services/remote/common/remoteExtensionsScanner';
93+
import 'vs/workbench/services/terminal/common/embedderTerminalService';
9394
import 'vs/workbench/services/workingCopy/common/workingCopyService';
9495
import 'vs/workbench/services/workingCopy/common/workingCopyFileService';
9596
import 'vs/workbench/services/workingCopy/common/workingCopyEditorService';

0 commit comments

Comments
 (0)