ptyHostService.ts ×54

Frontier kind: Joint frontier

unlabeled · c_aaad0e91b25d

1 test · 15823 LOC · 75 files · introduces 1 test · 735 LOC · 3 files

Introduces — evidence that enters the hierarchy at this concept

Code
57 ranges735 lines · 3 files
Tests
1 test

Contains — complete concept membership

All code (extent)
2110 ranges15823 lines · 75 files · Browse complete extent
All tests (intent)
1 testBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

1 test introduced at this concept.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

3 files ranked by introduced lines: 735 introduced LOC across 57 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/terminal/common/terminalPlatformConfiguration.ts 479 introduced LOC · 2 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- terminalPlatformConfiguration.ts
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 { Codicon, getAllCodicons } from '../../../base/common/codicons.js';
7 > import { IJSONSchema, IJSONSchemaMap } from '../../../base/common/jsonSchema.js';
8 > import { OperatingSystem, Platform, PlatformToString } from '../../../base/common/platform.js';
9 > import { localize } from '../../../nls.js';
10 > import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js';
11 > import { Registry } from '../../registry/common/platform.js';
12 > import { IExtensionTerminalProfile, ITerminalProfile, TerminalSettingId } from './terminal.js';
13 > import { createProfileSchemaEnums } from './terminalProfiles.js';
14 >
15 > export const terminalColorSchema: IJSONSchema = {
16 > type: ['string', 'null'],
17 > enum: [
18 > 'terminal.ansiBlack',
19 > 'terminal.ansiRed',
20 > 'terminal.ansiGreen',
21 > 'terminal.ansiYellow',
22 > 'terminal.ansiBlue',
23 > 'terminal.ansiMagenta',
24 > 'terminal.ansiCyan',
25 > 'terminal.ansiWhite'
26 > ],
27 > default: null
28 > };
29 >
30 > export const terminalIconSchema: IJSONSchema = {
31 > type: 'string',
32 > enum: Array.from(getAllCodicons(), icon => icon.id),
33 > markdownEnumDescriptions: Array.from(getAllCodicons(), icon => `$(${icon.id})`),
34 > };
35 >
36 > export const terminalProfileBaseProperties: IJSONSchemaMap = {
37 > args: {
38 > description: localize('terminalProfile.args', 'An optional set of arguments to run the shell executable with.'),
39 > type: 'array',
40 > items: {
41 > type: 'string'
42 > }
43 > },
44 > icon: {
45 > description: localize('terminalProfile.icon', 'A codicon ID to associate with the terminal icon.'),
46 > ...terminalIconSchema
47 > },
48 > color: {
49 > description: localize('terminalProfile.color', 'A theme color ID to associate with the terminal icon.'),
50 > ...terminalColorSchema
51 > },
52 > env: {
53 > markdownDescription: localize('terminalProfile.env', "An object with environment variables that will be added to the terminal profile process. Set to `null` to delete environment variables from the base environment."),
54 > type: 'object',
55 > additionalProperties: {
56 > type: ['string', 'null']
57 > },
58 > default: {}
59 > }
60 > };
61 >
62 > const terminalProfileSchema: IJSONSchema = {
63 > type: 'object',
64 > required: ['path'],
65 > properties: {
66 > path: {
67 > description: localize('terminalProfile.path', 'A single path to a shell executable or an array of paths that will be used as fallbacks when one fails.'),
68 > type: ['string', 'array'],
69 > items: {
70 > type: 'string'
71 > }
72 > },
73 > overrideName: {
74 > description: localize('terminalProfile.overrideName', 'Whether or not to replace the dynamic terminal title that detects what program is running with the static profile name.'),
75 > type: 'boolean'
76 > },
77 > ...terminalProfileBaseProperties
78 > }
79 > };
80 >
81 > const terminalAutomationProfileSchema: IJSONSchema = {
82 > type: 'object',
83 > required: ['path'],
84 > properties: {
85 > path: {
86 > description: localize('terminalAutomationProfile.path', 'A path to a shell executable.'),
87 > type: ['string'],
88 > items: {
89 > type: 'string'
90 > }
91 > },
92 > ...terminalProfileBaseProperties
93 > }
94 > };
95 >
96 > function createTerminalProfileMarkdownDescription(platform: Platform.Linux | Platform.Mac | Platform.Windows): string {
97 > const key = platform === Platform.Linux ? 'linux' : platform === Platform.Mac ? 'osx' : 'windows';
98 > return localize(
99 > {
100 > key: 'terminal.integrated.profile',
101 > comment: ['{0} is the platform, {1} is a code block, {2} and {3} are a link start and end']
102 > },
103 > "A set of terminal profile customizations for {0} which allows adding, removing or changing how terminals are launched. Profiles are made up of a mandatory path, optional arguments and other presentation options.\n\nTo override an existing profile use its profile name as the key, for example:\n\n{1}\n\n{2}Read more about configuring profiles{3}.",
104 > PlatformToString(platform),
105 > '```json\n"terminal.integrated.profile.' + key + '": {\n "bash": null\n}\n```',
106 > '[',
107 > '](https://code.visualstudio.com/docs/terminal/profiles)'
108 > );
109 > }
110 >
111 > const terminalPlatformConfiguration: IConfigurationNode = {
112 > id: 'terminal',
113 > order: 100,
114 > title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
115 > type: 'object',
116 > properties: {
117 > [TerminalSettingId.AutomationProfileLinux]: {
118 > restricted: true,
119 > markdownDescription: localize('terminal.integrated.automationProfile.linux', "The terminal profile to use on Linux for automation-related terminal usage like tasks and debug."),
120 > type: ['object', 'null'],
121 > default: null,
122 > 'anyOf': [
123 > { type: 'null' },
124 > terminalAutomationProfileSchema
125 > ],
126 > defaultSnippets: [
127 > {
128 > body: {
129 > path: '${1}',
130 > icon: '${2}'
131 > }
132 > }
133 > ]
134 > },
135 > [TerminalSettingId.AutomationProfileMacOs]: {
136 > restricted: true,
137 > markdownDescription: localize('terminal.integrated.automationProfile.osx', "The terminal profile to use on macOS for automation-related terminal usage like tasks and debug."),
138 > type: ['object', 'null'],
139 > default: null,
140 > 'anyOf': [
141 > { type: 'null' },
142 > terminalAutomationProfileSchema
143 > ],
144 > defaultSnippets: [
145 > {
146 > body: {
147 > path: '${1}',
148 > icon: '${2}'
149 > }
150 > }
151 > ]
152 > },
153 > [TerminalSettingId.AutomationProfileWindows]: {
154 > restricted: true,
155 > markdownDescription: localize('terminal.integrated.automationProfile.windows', "The terminal profile to use for automation-related terminal usage like tasks and debug. This setting will currently be ignored if {0} (now deprecated) is set.", '`terminal.integrated.automationShell.windows`'),
156 > type: ['object', 'null'],
157 > default: null,
158 > 'anyOf': [
159 > { type: 'null' },
160 > terminalAutomationProfileSchema
161 > ],
162 > defaultSnippets: [
163 > {
164 > body: {
165 > path: '${1}',
166 > icon: '${2}'
167 > }
168 > }
169 > ]
170 > },
171 > [TerminalSettingId.AgentHostProfileLinux]: {
172 > restricted: true,
173 > markdownDescription: localize('terminal.integrated.agentHostProfile.linux', "The terminal profile to use on Linux for agent host terminals, including shells launched by AI agent tools. Accepts either a profile name from {0} or an inline profile object. When unset, falls back to {1}. Currently applies to the local agent host. Only the executable `path` is honored today; `args` and `env` from the profile are ignored. Remote agent hosts need remote-side shell configuration because local resolved paths may be invalid on the remote.", '`#terminal.integrated.profiles.linux#`', '`#terminal.integrated.defaultProfile.linux#`'),
174 > type: ['string', 'object', 'null'],
175 > default: null,
176 > 'anyOf': [
177 > { type: 'null' },
178 > { type: 'string' },
179 > terminalAutomationProfileSchema
180 > ],
181 > defaultSnippets: [
182 > {
183 > body: {
184 > path: '${1}',
185 > icon: '${2}'
186 > }
187 > }
188 > ]
189 > },
190 > [TerminalSettingId.AgentHostProfileMacOs]: {
191 > restricted: true,
192 > markdownDescription: localize('terminal.integrated.agentHostProfile.osx', "The terminal profile to use on macOS for agent host terminals, including shells launched by AI agent tools. Accepts either a profile name from {0} or an inline profile object. When unset, falls back to {1}. Currently applies to the local agent host. Only the executable `path` is honored today; `args` and `env` from the profile are ignored. Remote agent hosts need remote-side shell configuration because local resolved paths may be invalid on the remote.", '`#terminal.integrated.profiles.osx#`', '`#terminal.integrated.defaultProfile.osx#`'),
193 > type: ['string', 'object', 'null'],
194 > default: null,
195 > 'anyOf': [
196 > { type: 'null' },
197 > { type: 'string' },
198 > terminalAutomationProfileSchema
199 > ],
200 > defaultSnippets: [
201 > {
202 > body: {
203 > path: '${1}',
204 > icon: '${2}'
205 > }
206 > }
207 > ]
208 > },
209 > [TerminalSettingId.AgentHostProfileWindows]: {
210 > restricted: true,
211 > markdownDescription: localize('terminal.integrated.agentHostProfile.windows', "The terminal profile to use on Windows for agent host terminals, including shells launched by AI agent tools. Accepts either a profile name from {0} or an inline profile object. When unset, falls back to {1}. Currently applies to the local agent host. Only the executable `path` is honored today; `args` and `env` from the profile are ignored. Remote agent hosts need remote-side shell configuration because local resolved paths may be invalid on the remote.", '`#terminal.integrated.profiles.windows#`', '`#terminal.integrated.defaultProfile.windows#`'),
212 > type: ['string', 'object', 'null'],
213 > default: null,
214 > 'anyOf': [
215 > { type: 'null' },
216 > { type: 'string' },
217 > terminalAutomationProfileSchema
218 > ],
219 > defaultSnippets: [
220 > {
221 > body: {
222 > path: '${1}',
223 > icon: '${2}'
224 > }
225 > }
226 > ]
227 > },
228 > [TerminalSettingId.ProfilesWindows]: {
229 > restricted: true,
230 > markdownDescription: createTerminalProfileMarkdownDescription(Platform.Windows),
231 > type: 'object',
232 > default: {
233 > 'PowerShell': {
234 > source: 'PowerShell',
235 > icon: Codicon.terminalPowershell.id,
236 > },
237 > 'Command Prompt': {
238 > path: [
239 > '${env:windir}\\Sysnative\\cmd.exe',
240 > '${env:windir}\\System32\\cmd.exe'
241 > ],
242 > args: [],
243 > icon: Codicon.terminalCmd.id,
244 > },
245 > 'Git Bash': {
246 > source: 'Git Bash',
247 > icon: Codicon.terminalGitBash.id,
248 > }
249 > },
250 > additionalProperties: {
251 > 'anyOf': [
252 > {
253 > type: 'object',
254 > required: ['source'],
255 > properties: {
256 > source: {
257 > description: localize('terminalProfile.windowsSource', 'A profile source that will auto detect the paths to the shell. Note that non-standard executable locations are not supported and must be created manually in a new profile.'),
258 > enum: ['PowerShell', 'Git Bash']
259 > },
260 > ...terminalProfileBaseProperties
261 > }
262 > },
263 > {
264 > type: 'object',
265 > required: ['extensionIdentifier', 'id', 'title'],
266 > properties: {
267 > extensionIdentifier: {
268 > description: localize('terminalProfile.windowsExtensionIdentifier', 'The extension that contributed this profile.'),
269 > type: 'string'
270 > },
271 > id: {
272 > description: localize('terminalProfile.windowsExtensionId', 'The id of the extension terminal'),
273 > type: 'string'
274 > },
275 > title: {
276 > description: localize('terminalProfile.windowsExtensionTitle', 'The name of the extension terminal'),
277 > type: 'string'
278 > },
279 > ...terminalProfileBaseProperties
280 > }
281 > },
282 > { type: 'null' },
283 > terminalProfileSchema
284 > ]
285 > }
286 > },
287 > [TerminalSettingId.ProfilesMacOs]: {
288 > restricted: true,
289 > markdownDescription: createTerminalProfileMarkdownDescription(Platform.Mac),
290 > type: 'object',
291 > default: {
292 > 'bash': {
293 > path: 'bash',
294 > args: ['-l'],
295 > icon: Codicon.terminalBash.id
296 > },
297 > 'zsh': {
298 > path: 'zsh',
299 > args: ['-l']
300 > },
301 > 'fish': {
302 > path: 'fish',
303 > args: ['-l']
304 > },
305 > 'tmux': {
306 > path: 'tmux',
307 > icon: Codicon.terminalTmux.id
308 > },
309 > 'pwsh': {
310 > path: 'pwsh',
311 > icon: Codicon.terminalPowershell.id
312 > }
313 > },
314 > additionalProperties: {
315 > 'anyOf': [
316 > {
317 > type: 'object',
318 > required: ['extensionIdentifier', 'id', 'title'],
319 > properties: {
320 > extensionIdentifier: {
321 > description: localize('terminalProfile.osxExtensionIdentifier', 'The extension that contributed this profile.'),
322 > type: 'string'
323 > },
324 > id: {
325 > description: localize('terminalProfile.osxExtensionId', 'The id of the extension terminal'),
326 > type: 'string'
327 > },
328 > title: {
329 > description: localize('terminalProfile.osxExtensionTitle', 'The name of the extension terminal'),
330 > type: 'string'
331 > },
332 > ...terminalProfileBaseProperties
333 > }
334 > },
335 > { type: 'null' },
336 > terminalProfileSchema
337 > ]
338 > }
339 > },
340 > [TerminalSettingId.ProfilesLinux]: {
341 > restricted: true,
342 > markdownDescription: createTerminalProfileMarkdownDescription(Platform.Linux),
343 > type: 'object',
344 > default: {
345 > 'bash': {
346 > path: 'bash',
347 > icon: Codicon.terminalBash.id
348 > },
349 > 'zsh': {
350 > path: 'zsh'
351 > },
352 > 'fish': {
353 > path: 'fish'
354 > },
355 > 'tmux': {
356 > path: 'tmux',
357 > icon: Codicon.terminalTmux.id
358 > },
359 > 'pwsh': {
360 > path: 'pwsh',
361 > icon: Codicon.terminalPowershell.id
362 > }
363 > },
364 > additionalProperties: {
365 > 'anyOf': [
366 > {
367 > type: 'object',
368 > required: ['extensionIdentifier', 'id', 'title'],
369 > properties: {
370 > extensionIdentifier: {
371 > description: localize('terminalProfile.linuxExtensionIdentifier', 'The extension that contributed this profile.'),
372 > type: 'string'
373 > },
374 > id: {
375 > description: localize('terminalProfile.linuxExtensionId', 'The id of the extension terminal'),
376 > type: 'string'
377 > },
378 > title: {
379 > description: localize('terminalProfile.linuxExtensionTitle', 'The name of the extension terminal'),
380 > type: 'string'
381 > },
382 > ...terminalProfileBaseProperties
383 > }
384 > },
385 > { type: 'null' },
386 > terminalProfileSchema
387 > ]
388 > }
389 > },
390 > [TerminalSettingId.UseWslProfiles]: {
391 > description: localize('terminal.integrated.useWslProfiles', 'Controls whether or not WSL distros are shown in the terminal dropdown'),
392 > type: 'boolean',
393 > default: true
394 > },
395 > [TerminalSettingId.InheritEnv]: {
396 > scope: ConfigurationScope.APPLICATION,
397 > description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code, which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."),
398 > type: 'boolean',
399 > default: true
400 > },
401 > [TerminalSettingId.PersistentSessionScrollback]: {
402 > scope: ConfigurationScope.APPLICATION,
403 > markdownDescription: localize('terminal.integrated.persistentSessionScrollback', "Controls the maximum amount of lines that will be restored when reconnecting to a persistent terminal session. Increasing this will restore more lines of scrollback at the cost of more memory and increase the time it takes to connect to terminals on start up. This setting requires a restart to take effect and should be set to a value less than or equal to `#terminal.integrated.scrollback#`."),
404 > type: 'number',
405 > default: 100
406 > },
407 > [TerminalSettingId.ShowLinkHover]: {
408 > scope: ConfigurationScope.APPLICATION,
409 > description: localize('terminal.integrated.showLinkHover', "Whether to show hovers for links in the terminal output."),
410 > type: 'boolean',
411 > default: true
412 > },
413 > [TerminalSettingId.IgnoreProcessNames]: {
414 > markdownDescription: localize('terminal.integrated.confirmIgnoreProcesses', "A set of process names to ignore when using the {0} setting.", '`#terminal.integrated.confirmOnKill#`'),
415 > type: 'array',
416 > items: {
417 > type: 'string',
418 > uniqueItems: true
419 > },
420 > default: [
421 > // Popular prompt programs, these should not count as child processes
422 > 'starship',
423 > 'oh-my-posh',
424 > // Git bash may runs a subprocess of itself (bin\bash.exe -> usr\bin\bash.exe)
425 > 'bash',
426 > 'zsh',
427 > ]
428 > }
429 > }
430 > };
431 >
432 > /**
433 > * Registers terminal configurations required by shared process and remote server.
434 > */
435 > export function registerTerminalPlatformConfiguration() {
436 > Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration(terminalPlatformConfiguration);
437 > registerTerminalDefaultProfileConfiguration();
438 > }
439 >
440 > let defaultProfilesConfiguration: IConfigurationNode | undefined;
441 > export function registerTerminalDefaultProfileConfiguration(detectedProfiles?: { os: OperatingSystem; profiles: ITerminalProfile[] }, extensionContributedProfiles?: readonly IExtensionTerminalProfile[]) {
442 > const registry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
443 > let profileEnum;
444 > if (detectedProfiles) {
445 profileEnum = createProfileSchemaEnums(detectedProfiles?.profiles, extensionContributedProfiles);
446 }
447 > const oldDefaultProfilesConfiguration = defaultProfilesConfiguration; terminalPlatformConfiguration.ts
448 > defaultProfilesConfiguration = {
449 > id: 'terminal',
450 > order: 100,
451 > title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
452 > type: 'object',
453 > properties: {
454 > [TerminalSettingId.DefaultProfileLinux]: {
455 > restricted: true,
456 > markdownDescription: localize('terminal.integrated.defaultProfile.linux', "The default terminal profile on Linux."),
457 > type: ['string', 'null'],
458 > default: null,
459 > enum: detectedProfiles?.os === OperatingSystem.Linux ? profileEnum?.values : undefined,
460 > markdownEnumDescriptions: detectedProfiles?.os === OperatingSystem.Linux ? profileEnum?.markdownDescriptions : undefined
461 > },
462 > [TerminalSettingId.DefaultProfileMacOs]: {
463 > restricted: true,
464 > markdownDescription: localize('terminal.integrated.defaultProfile.osx', "The default terminal profile on macOS."),
465 > type: ['string', 'null'],
466 > default: null,
467 > enum: detectedProfiles?.os === OperatingSystem.Macintosh ? profileEnum?.values : undefined,
468 > markdownEnumDescriptions: detectedProfiles?.os === OperatingSystem.Macintosh ? profileEnum?.markdownDescriptions : undefined
469 > },
470 > [TerminalSettingId.DefaultProfileWindows]: {
471 > restricted: true,
472 > markdownDescription: localize('terminal.integrated.defaultProfile.windows', "The default terminal profile on Windows."),
473 > type: ['string', 'null'],
474 > default: null,
475 > enum: detectedProfiles?.os === OperatingSystem.Windows ? profileEnum?.values : undefined,
476 > markdownEnumDescriptions: detectedProfiles?.os === OperatingSystem.Windows ? profileEnum?.markdownDescriptions : undefined
477 > },
478 > }
479 > };
480 > registry.updateConfigurations({ add: [defaultProfilesConfiguration], remove: oldDefaultProfilesConfiguration ? [oldDefaultProfilesConfiguration] : [] });
481 > }
src/vs/platform/terminal/node/ptyHostService.ts 254 introduced LOC · 54 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- ptyHostService.ts
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 { Emitter, Event } from '../../../base/common/event.js';
7 > import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
8 > import { IProcessEnvironment, OS, OperatingSystem, isWindows } from '../../../base/common/platform.js';
9 > import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js';
10 > import { IConfigurationService } from '../../configuration/common/configuration.js';
11 > import { ILogService, ILoggerService, LogLevel } from '../../log/common/log.js';
12 > import { RemoteLoggerChannelClient } from '../../log/common/logIpc.js';
13 > import { getResolvedShellEnv } from '../../shell/node/shellEnv.js';
14 > import { IPtyHostProcessReplayEvent } from '../common/capabilities/capabilities.js';
15 > import { RequestStore } from '../common/requestStore.js';
16 > import { HeartbeatConstants, IHeartbeatService, ITerminalLaunchResult, IProcessDataEvent, IProcessProperty, IProcessPropertyMap, IProcessReadyEvent, IPtyHostLatencyMeasurement, IPtyHostService, IPtyService, IRequestResolveVariablesEvent, ISerializedTerminalState, IShellLaunchConfig, ITerminalLaunchError, ITerminalProcessOptions, ITerminalProfile, ITerminalsLayoutInfo, ProcessPropertyType, TerminalIcon, TerminalIpcChannels, TerminalSettingId, TitleEventSource } from '../common/terminal.js';
17 > import { registerTerminalPlatformConfiguration } from '../common/terminalPlatformConfiguration.js';
18 > import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISetTerminalLayoutInfoArgs } from '../common/terminalProcess.js';
19 > import { IPtyHostConnection, IPtyHostStarter } from './ptyHost.js';
20 > import { detectAvailableProfiles } from './terminalProfiles.js';
21 > import * as performance from '../../../base/common/performance.js';
22 > import { getSystemShell } from '../../../base/node/shell.js';
23 > import { StopWatch } from '../../../base/common/stopwatch.js';
24 >
25 > enum Constants {
26 > MaxRestarts = 5
27 > }
28 >
29 > /**
30 > * This service implements IPtyService by launching a pty host process, forwarding messages to and
31 > * from the pty host process and manages the connection.
32 > */
33 > export class PtyHostService extends Disposable implements IPtyHostService {
34 > declare readonly _serviceBrand: undefined;
35 >
36 > private __connection?: IPtyHostConnection;
37 > // ProxyChannel is not used here because events get lost when forwarding across multiple proxies
38 > private __proxy?: IPtyService;
39 >
40 > private get _proxy(): IPtyService {
41 > this._ensurePtyHost();
42 > return this.__proxy!;
43 > }
44 > /**
45 > * Get the proxy if it exists, otherwise undefined. This is used when calls are not needed to be
46 > * passed through to the pty host if it has not yet been spawned.
47 > */
48 > private get _optionalProxy(): IPtyService | undefined {
49 > return this.__proxy;
50 > }
51 >
52 > private _ensurePtyHost() {
53 if (!this.__connection) {
54 this._startPtyHost();
55 }
56 }
58 > private readonly _resolveVariablesRequestStore: RequestStore<string[], { workspaceId: string; originalText: string[] }>;
59 > private _wasQuitRequested = false;
60 > private _restartCount = 0;
61 > private _isResponsive = true;
62 > private _heartbeatFirstTimeout?: Timeout;
63 > private _heartbeatSecondTimeout?: Timeout;
64 >
65 > private readonly _onPtyHostExit = this._register(new Emitter<number>());
66 > readonly onPtyHostExit = this._onPtyHostExit.event;
67 > private readonly _onPtyHostStart = this._register(new Emitter<void>());
68 > readonly onPtyHostStart = this._onPtyHostStart.event;
69 > private readonly _onPtyHostUnresponsive = this._register(new Emitter<void>());
70 > readonly onPtyHostUnresponsive = this._onPtyHostUnresponsive.event;
71 > private readonly _onPtyHostResponsive = this._register(new Emitter<void>());
72 > readonly onPtyHostResponsive = this._onPtyHostResponsive.event;
73 > private readonly _onPtyHostRequestResolveVariables = this._register(new Emitter<IRequestResolveVariablesEvent>());
74 > readonly onPtyHostRequestResolveVariables = this._onPtyHostRequestResolveVariables.event;
75 >
76 > private readonly _onProcessData = this._register(new Emitter<{ id: number; event: IProcessDataEvent | string }>());
77 > readonly onProcessData = this._onProcessData.event;
78 > private readonly _onProcessReady = this._register(new Emitter<{ id: number; event: IProcessReadyEvent }>());
79 > readonly onProcessReady = this._onProcessReady.event;
80 > private readonly _onProcessReplay = this._register(new Emitter<{ id: number; event: IPtyHostProcessReplayEvent }>());
81 > readonly onProcessReplay = this._onProcessReplay.event;
82 > private readonly _onProcessOrphanQuestion = this._register(new Emitter<{ id: number }>());
83 > readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event;
84 > private readonly _onDidRequestDetach = this._register(new Emitter<{ requestId: number; workspaceId: string; instanceId: number }>());
85 > readonly onDidRequestDetach = this._onDidRequestDetach.event;
86 > private readonly _onDidChangeProperty = this._register(new Emitter<{ id: number; property: IProcessProperty }>());
87 > readonly onDidChangeProperty = this._onDidChangeProperty.event;
88 > private readonly _onProcessExit = this._register(new Emitter<{ id: number; event: number | undefined }>());
89 > readonly onProcessExit = this._onProcessExit.event;
90 >
91 > private readonly _ptyHostStore = this._register(new DisposableStore());
92 >
93 > constructor(
94 > private readonly _ptyHostStarter: IPtyHostStarter,
95 > @IConfigurationService private readonly _configurationService: IConfigurationService,
96 > @ILogService private readonly _logService: ILogService,
97 > @ILoggerService private readonly _loggerService: ILoggerService,
98 > ) {
99 > super();
100 >
101 > // Platform configuration is required on the process running the pty host (shared process or
102 > // remote server).
103 > registerTerminalPlatformConfiguration();
104 >
105 > this._register(this._ptyHostStarter);
106 > this._register(toDisposable(() => this._disposePtyHost()));
107 >
108 > this._resolveVariablesRequestStore = this._register(new RequestStore(undefined, this._logService));
109 > this._register(this._resolveVariablesRequestStore.onCreateRequest(this._onPtyHostRequestResolveVariables.fire, this._onPtyHostRequestResolveVariables));
110 >
111 > // Start the pty host when a window requests a connection, if the starter has that capability.
112 > if (this._ptyHostStarter.onRequestConnection) {
113 this._register(Event.once(this._ptyHostStarter.onRequestConnection)(() => this._ensurePtyHost()));
114 }
116 > if (this._ptyHostStarter.onWillShutdown) {
117 this._register(this._ptyHostStarter.onWillShutdown(() => this._wasQuitRequested = true));
118 }
120 >
121 > private get _ignoreProcessNames(): string[] {
122 > return this._configurationService.getValue<string[]>(TerminalSettingId.IgnoreProcessNames);
123 > }
124 >
125 > private async _refreshIgnoreProcessNames(): Promise<void> {
126 > return this._optionalProxy?.refreshIgnoreProcessNames?.(this._ignoreProcessNames);
127 > }
128 >
129 > private async _resolveShellEnv(): Promise<typeof process.env> {
130 if (isWindows) {
131 return process.env;
140 }
141 }
143 > private _startPtyHost(): void {
144 > const connection = this._ptyHostStarter.start();
145 > const client = connection.client;
146 > const store = this._ptyHostStore;
147 > // Transfer ownership of the per-host connection store so it is disposed together with the listeners below on the next restart.
148 > store.add(connection.store);
149 >
150 > // Log a full stack trace which will tell the exact reason the pty host is starting up
151 > if (this._logService.getLevel() === LogLevel.Trace) {
152 this._logService.trace('PtyHostService#_startPtyHost', new Error().stack?.replace(/^Error/, ''));
153 }
155 > // Setup heartbeat service and trigger a heartbeat immediately to reset the timeouts
156 > const heartbeatService = ProxyChannel.toService<IHeartbeatService>(client.getChannel(TerminalIpcChannels.Heartbeat));
157 > store.add(heartbeatService.onBeat(() => this._handleHeartbeat()));
158 > this._handleHeartbeat(true);
159 >
160 > // Handle exit
161 > store.add(connection.onDidProcessExit(e => {
162 this._onPtyHostExit.fire(e.code);
163 if (!this._wasQuitRequested && !this._store.isDisposed) {
170 }
171 }
172 > })); ptyHostService.ts
173 >
174 > // Create proxy and forward events
175 > const proxy = ProxyChannel.toService<IPtyService>(client.getChannel(TerminalIpcChannels.PtyHost));
176 > store.add(proxy.onProcessData(e => this._onProcessData.fire(e)));
177 > store.add(proxy.onProcessReady(e => this._onProcessReady.fire(e)));
178 > store.add(proxy.onProcessExit(e => this._onProcessExit.fire(e)));
179 > store.add(proxy.onDidChangeProperty(e => this._onDidChangeProperty.fire(e)));
180 > store.add(proxy.onProcessReplay(e => this._onProcessReplay.fire(e)));
181 > store.add(proxy.onProcessOrphanQuestion(e => this._onProcessOrphanQuestion.fire(e)));
182 > store.add(proxy.onDidRequestDetach(e => this._onDidRequestDetach.fire(e)));
183 >
184 > store.add(new RemoteLoggerChannelClient(this._loggerService, client.getChannel(TerminalIpcChannels.Logger)));
185 >
186 > this.__connection = connection;
187 > this.__proxy = proxy;
188 >
189 > this._onPtyHostStart.fire();
190 >
191 > store.add(this._configurationService.onDidChangeConfiguration(async e => {
192 if (e.affectsConfiguration(TerminalSettingId.IgnoreProcessNames)) {
193 await this._refreshIgnoreProcessNames();
194 }
195 > })); ptyHostService.ts
196 > this._refreshIgnoreProcessNames();
197 > }
198 >
199 > async createProcess(
200 shellLaunchConfig: IShellLaunchConfig,
201 cwd: string,
215 return id;
216 }
217 > updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void> { ptyHostService.ts
218 return this._proxy.updateTitle(id, title, titleSource);
219 }
220 > updateIcon(id: number, userInitiated: boolean, icon: TerminalIcon, color?: string): Promise<void> { ptyHostService.ts
221 return this._proxy.updateIcon(id, userInitiated, icon, color);
222 }
223 > attachToProcess(id: number): Promise<void> { ptyHostService.ts
224 return this._proxy.attachToProcess(id);
225 }
226 > detachFromProcess(id: number, forcePersist?: boolean): Promise<void> { ptyHostService.ts
227 return this._proxy.detachFromProcess(id, forcePersist);
228 }
229 > shutdownAll(): Promise<void> { ptyHostService.ts
230 return this._proxy.shutdownAll();
231 }
232 > listProcesses(): Promise<IProcessDetails[]> { ptyHostService.ts
233 return this._proxy.listProcesses();
234 }
235 > async getPerformanceMarks(): Promise<performance.PerformanceMark[]> { ptyHostService.ts
236 return this._optionalProxy?.getPerformanceMarks() ?? [];
237 }
238 > async reduceConnectionGraceTime(): Promise<void> { ptyHostService.ts
239 return this._optionalProxy?.reduceConnectionGraceTime();
240 }
241 > start(id: number): Promise<ITerminalLaunchError | ITerminalLaunchResult | undefined> { ptyHostService.ts
242 return this._proxy.start(id);
243 }
244 > shutdown(id: number, immediate: boolean): Promise<void> { ptyHostService.ts
245 return this._proxy.shutdown(id, immediate);
246 }
247 > input(id: number, data: string): Promise<void> { ptyHostService.ts
248 return this._proxy.input(id, data);
249 }
250 > sendSignal(id: number, signal: string): Promise<void> { ptyHostService.ts
251 return this._proxy.sendSignal(id, signal);
252 }
253 > processBinary(id: number, data: string): Promise<void> { ptyHostService.ts
254 return this._proxy.processBinary(id, data);
255 }
256 > resize(id: number, cols: number, rows: number, pixelWidth?: number, pixelHeight?: number): Promise<void> { ptyHostService.ts
257 return this._proxy.resize(id, cols, rows, pixelWidth, pixelHeight);
258 }
259 > clearBuffer(id: number): Promise<void> { ptyHostService.ts
260 return this._proxy.clearBuffer(id);
261 }
262 > acknowledgeDataEvent(id: number, charCount: number): Promise<void> { ptyHostService.ts
263 return this._proxy.acknowledgeDataEvent(id, charCount);
264 }
265 > setUnicodeVersion(id: number, version: '6' | '11'): Promise<void> { ptyHostService.ts
266 return this._proxy.setUnicodeVersion(id, version);
267 }
268 > setNextCommandId(id: number, commandLine: string, commandId: string): Promise<void> { ptyHostService.ts
269 return this._proxy.setNextCommandId(id, commandLine, commandId);
270 }
271 > getInitialCwd(id: number): Promise<string> { ptyHostService.ts
272 return this._proxy.getInitialCwd(id);
273 }
274 > getCwd(id: number): Promise<string> { ptyHostService.ts
275 return this._proxy.getCwd(id);
276 }
277 > async getLatency(): Promise<IPtyHostLatencyMeasurement[]> { ptyHostService.ts
278 const sw = new StopWatch();
279 const results = await this._proxy.getLatency();
287 ];
288 }
289 > orphanQuestionReply(id: number): Promise<void> { ptyHostService.ts
290 return this._proxy.orphanQuestionReply(id);
291 }
293 > installAutoReply(match: string, reply: string): Promise<void> {
294 return this._proxy.installAutoReply(match, reply);
295 }
296 > uninstallAllAutoReplies(): Promise<void> { ptyHostService.ts
297 return this._proxy.uninstallAllAutoReplies();
298 }
300 > getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string> {
301 return this._optionalProxy?.getDefaultSystemShell(osOverride) ?? getSystemShell(osOverride ?? OS, process.env);
302 }
303 > async getProfiles(workspaceId: string, profiles: unknown, defaultProfile: unknown, includeDetectedProfiles: boolean = false): Promise<ITerminalProfile[]> { ptyHostService.ts
304 const shellEnv = await this._resolveShellEnv();
305 return detectAvailableProfiles(profiles, defaultProfile, includeDetectedProfiles, this._configurationService, shellEnv, undefined, this._logService, this._resolveVariables.bind(this, workspaceId));
306 }
307 > async getEnvironment(): Promise<IProcessEnvironment> { ptyHostService.ts
308 // If the pty host is yet to be launched, just return the environment of this process as it
309 // is essentially the same when used to evaluate terminal profiles.
313 return this._proxy.getEnvironment();
314 }
315 > getWslPath(original: string, direction: 'unix-to-win' | 'win-to-unix'): Promise<string> { ptyHostService.ts
316 return this._proxy.getWslPath(original, direction);
317 }
319 > getRevivedPtyNewId(workspaceId: string, id: number): Promise<number | undefined> {
320 return this._proxy.getRevivedPtyNewId(workspaceId, id);
321 }
323 > setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void> {
324 return this._proxy.setTerminalLayoutInfo(args);
325 }
326 > async getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise<ITerminalsLayoutInfo | undefined> { ptyHostService.ts
327 // This is optional as we want reconnect requests to go through only if the pty host exists.
328 // Revive is handled specially as reviveTerminalProcesses is guaranteed to be called before
330 return this._optionalProxy?.getTerminalLayoutInfo(args);
331 }
333 > async requestDetachInstance(workspaceId: string, instanceId: number): Promise<IProcessDetails | undefined> {
334 return this._proxy.requestDetachInstance(workspaceId, instanceId);
335 }
337 > async acceptDetachInstanceReply(requestId: number, persistentProcessId: number): Promise<void> {
338 return this._proxy.acceptDetachInstanceReply(requestId, persistentProcessId);
339 }
341 > async freePortKillProcess(port: string): Promise<{ port: string; processId: string }> {
342 if (!this._proxy.freePortKillProcess) {
343 throw new Error('freePortKillProcess does not exist on the pty proxy');
345 return this._proxy.freePortKillProcess(port);
346 }
348 > async serializeTerminalState(ids: number[]): Promise<string> {
349 return this._proxy.serializeTerminalState(ids);
350 }
352 > async reviveTerminalProcesses(workspaceId: string, state: ISerializedTerminalState[], dateTimeFormatLocate: string) {
353 return this._proxy.reviveTerminalProcesses(workspaceId, state, dateTimeFormatLocate);
354 }
356 > async refreshProperty<T extends ProcessPropertyType>(id: number, property: T): Promise<IProcessPropertyMap[T]> {
357 return this._proxy.refreshProperty(id, property);
358
359 }
360 > async updateProperty<T extends ProcessPropertyType>(id: number, property: T, value: IProcessPropertyMap[T]): Promise<void> { ptyHostService.ts
361 return this._proxy.updateProperty(id, property, value);
362 }
364 > async restartPtyHost(): Promise<void> {
365 > this._disposePtyHost();
366 > this._isResponsive = true;
367 > this._startPtyHost();
368 > }
369 >
370 > private _disposePtyHost(): void {
371 > // Heartbeat timers are bare setTimeout handles, not disposables in the store, so they need an explicit clear.
372 > // shutdownAll() is fired before clearing the store so any in-flight exit listener still has a live proxy to read from;
373 > // the per-host listener store is cleared last so the on-exit signal isn't dropped on the floor.
374 > this._clearHeartbeatTimeouts();
375 > // Fire-and-forget: the IPC channel may already be gone; swallow rejections so we don't surface an unhandled promise.
376 > this._optionalProxy?.shutdownAll().catch(() => { });
377 > this.__connection = undefined;
378 > this.__proxy = undefined;
379 > this._ptyHostStore.clear();
380 > }
381 >
382 > private _handleHeartbeat(isConnecting?: boolean) {
383 > this._clearHeartbeatTimeouts();
384 > this._heartbeatFirstTimeout = setTimeout(() => this._handleHeartbeatFirstTimeout(), isConnecting ? HeartbeatConstants.ConnectingBeatInterval : (HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier));
385 > if (!this._isResponsive) {
386 this._isResponsive = true;
387 this._onPtyHostResponsive.fire();
388 }
390 >
391 > private _handleHeartbeatFirstTimeout() {
392 this._logService.warn(`No ptyHost heartbeat after ${HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier / 1000} seconds`);
393 this._heartbeatFirstTimeout = undefined;
394 this._heartbeatSecondTimeout = setTimeout(() => this._handleHeartbeatSecondTimeout(), HeartbeatConstants.BeatInterval * HeartbeatConstants.SecondWaitMultiplier);
395 }
397 > private _handleHeartbeatSecondTimeout() {
398 this._logService.error(`No ptyHost heartbeat after ${(HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier + HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier) / 1000} seconds`);
399 this._heartbeatSecondTimeout = undefined;
403 }
404 }
406 > private _handleUnresponsiveCreateProcess() {
407 this._clearHeartbeatTimeouts();
408 this._logService.error(`No ptyHost response to createProcess after ${HeartbeatConstants.CreateProcessTimeout / 1000} seconds`);
412 }
413 }
415 > private _clearHeartbeatTimeouts() {
416 > if (this._heartbeatFirstTimeout) {
417 > clearTimeout(this._heartbeatFirstTimeout);
418 > this._heartbeatFirstTimeout = undefined;
419 > }
420 > if (this._heartbeatSecondTimeout) {
421 clearTimeout(this._heartbeatSecondTimeout);
422 this._heartbeatSecondTimeout = undefined;
423 }
425 >
426 > private _resolveVariables(workspaceId: string, text: string[]): Promise<string[]> {
427 return this._resolveVariablesRequestStore.createRequest({ workspaceId, originalText: text });
428 }
429 > async acceptPtyHostResolvedVariables(requestId: number, resolved: string[]) { ptyHostService.ts
430 this._resolveVariablesRequestStore.acceptReply(requestId, resolved);
431 }
src/vs/base/common/codicons.ts 2 introduced LOC · 1 range

Open complete file

12 */
13 export function getAllCodicons(): ThemeIcon[] {
14 > return Object.values(Codicon); codicons.ts
15 > }
16
17 /**