diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index edfb2939422..e65421ea803 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -176,6 +176,17 @@ export enum TerminalIpcChannels { } export const IPtyService = createDecorator('ptyService'); + +export const enum TerminalPropertyType { + cwd, + initialCwd +} + +export interface TerminalProperty { + type: TerminalPropertyType, + value: any +} + export interface IPtyService { readonly _serviceBrand: undefined; @@ -196,6 +207,7 @@ export interface IPtyService { readonly onProcessOrphanQuestion: Event<{ id: number }>; readonly onDidRequestDetach: Event<{ requestId: number, workspaceId: string, instanceId: number }>; readonly onProcessDidChangeHasChildProcesses: Event<{ id: number, event: boolean }>; + readonly onDidChangeProperty: Event<{ id: number, event: TerminalProperty }> restartPtyHost?(): Promise; shutdownAll?(): Promise; @@ -471,6 +483,7 @@ export interface ITerminalChildProcess { onProcessOverrideDimensions?: Event; onProcessResolvedShellLaunchConfig?: Event; onDidChangeHasChildProcesses?: Event; + onDidChangeProperty: Event; /** * Starts the process. diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index b2d12a03173..d2fff7c7abf 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -15,7 +15,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RequestStore } from 'vs/platform/terminal/common/requestStore'; -import { HeartbeatConstants, IHeartbeatService, IProcessDataEvent, IPtyService, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, TerminalIcon, TerminalIpcChannels, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { HeartbeatConstants, IHeartbeatService, IProcessDataEvent, IPtyService, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, TerminalIcon, TerminalIpcChannels, TerminalProperty, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; import { registerTerminalPlatformConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess'; import { detectAvailableProfiles } from 'vs/platform/terminal/node/terminalProfiles'; @@ -83,6 +83,8 @@ export class PtyHostService extends Disposable implements IPtyService { readonly onDidRequestDetach = this._onDidRequestDetach.event; private readonly _onProcessDidChangeHasChildProcesses = this._register(new Emitter<{ id: number, event: boolean }>()); readonly onProcessDidChangeHasChildProcesses = this._onProcessDidChangeHasChildProcesses.event; + private readonly _onDidChangeProperty = this._register(new Emitter<{ id: number, event: TerminalProperty }>()); + readonly onDidChangeProperty = this._onDidChangeProperty.event; constructor( private readonly _reconnectConstants: IReconnectConstants, @@ -166,6 +168,7 @@ export class PtyHostService extends Disposable implements IPtyService { this._register(proxy.onProcessOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e))); this._register(proxy.onProcessResolvedShellLaunchConfig(e => this._onProcessResolvedShellLaunchConfig.fire(e))); this._register(proxy.onProcessDidChangeHasChildProcesses(e => this._onProcessDidChangeHasChildProcesses.fire(e))); + this._register(proxy.onDidChangeProperty(e => this._onDidChangeProperty.fire(e))); this._register(proxy.onProcessReplay(e => this._onProcessReplay.fire(e))); this._register(proxy.onProcessOrphanQuestion(e => this._onProcessOrphanQuestion.fire(e))); this._register(proxy.onDidRequestDetach(e => this._onDidRequestDetach.fire(e))); diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 45fb1b60631..b9d7f1bbff9 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { getSystemShell } from 'vs/base/node/shell'; import { ILogService } from 'vs/platform/log/common/log'; import { RequestStore } from 'vs/platform/terminal/common/requestStore'; -import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IRequestResolveVariablesEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, TerminalProperty, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; import { Terminal as XtermTerminal } from 'xterm-headless'; @@ -60,6 +60,8 @@ export class PtyService extends Disposable implements IPtyService { readonly onDidRequestDetach = this._onDidRequestDetach.event; private readonly _onProcessDidChangeHasChildProcesses = this._register(new Emitter<{ id: number, event: boolean }>()); readonly onProcessDidChangeHasChildProcesses = this._onProcessDidChangeHasChildProcesses.event; + private readonly _onDidChangeProperty = this._register(new Emitter<{ id: number, event: TerminalProperty }>()); + readonly onDidChangeProperty = this._onDidChangeProperty.event; constructor( private _lastPtyId: number, @@ -138,6 +140,7 @@ export class PtyService extends Disposable implements IPtyService { persistentProcess.onProcessReplay(event => this._onProcessReplay.fire({ id, event })); persistentProcess.onProcessReady(event => this._onProcessReady.fire({ id, event })); persistentProcess.onProcessTitleChanged(event => this._onProcessTitleChanged.fire({ id, event })); + persistentProcess.onDidChangeProperty(event => this._onDidChangeProperty.fire({ id, event })); persistentProcess.onProcessShellTypeChanged(event => this._onProcessShellTypeChanged.fire({ id, event })); persistentProcess.onProcessOrphanQuestion(() => this._onProcessOrphanQuestion.fire({ id })); this._ptys.set(id, persistentProcess); @@ -339,6 +342,8 @@ export class PersistentTerminalProcess extends Disposable { readonly onProcessData = this._onProcessData.event; private readonly _onProcessOrphanQuestion = this._register(new Emitter()); readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event; + private readonly _onDidChangeProperty = this._register(new Emitter()); + readonly onDidChangeProperty = this._onDidChangeProperty.event; private _inReplay = false; @@ -409,6 +414,7 @@ export class PersistentTerminalProcess extends Disposable { })); this._register(this._terminalProcess.onProcessTitleChanged(e => this._onProcessTitleChanged.fire(e))); this._register(this._terminalProcess.onProcessShellTypeChanged(e => this._onProcessShellTypeChanged.fire(e))); + this._register(this._terminalProcess.onDidChangeProperty(e => this._onDidChangeProperty.fire(e))); // Data buffering to reduce the amount of messages going to the renderer this._bufferer = new TerminalDataBufferer((_, data) => this._onProcessData.fire(data)); diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 0347773855d..374eb2f9297 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { Promises } from 'vs/base/node/pfs'; import { localize } from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; -import { FlowControlConstants, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { FlowControlConstants, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalProperty, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { ChildProcessMonitor } from 'vs/platform/terminal/node/childProcessMonitor'; import { findExecutable, getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment'; import { WindowsShellHelper } from 'vs/platform/terminal/node/windowsShellHelper'; @@ -114,6 +114,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; private readonly _onDidChangeHasChildProcesses = this._register(new Emitter()); readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event; + private readonly _onDidChangeProperty = this._register(new Emitter()); + readonly onDidChangeProperty = this._onDidChangeProperty.event; onProcessOverrideDimensions?: Event | undefined; onProcessResolvedShellLaunchConfig?: Event | undefined; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index a403f5ee5bb..ad119a9ec32 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -18,7 +18,7 @@ import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/ter import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { generateUuid } from 'vs/base/common/uuid'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { ICreateContributedTerminalProfileOptions, IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, TerminalIcon, TerminalLocation, TerminalProperty, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { withNullAsUndefined } from 'vs/base/common/types'; @@ -250,6 +250,8 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } private readonly _onProcessShellTypeChanged = new Emitter(); public readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; + private readonly _onDidChangeProperty = new Emitter(); + public readonly onDidChangeProperty = this._onDidChangeProperty.event; constructor(private readonly _pty: vscode.Pseudoterminal) { } diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index 8d57bcc7bbf..e9321672369 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -8,13 +8,14 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalProperty, TerminalPropertyType, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/terminalProcess'; import { RemoteTerminalChannelClient } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class RemotePty extends Disposable implements ITerminalChildProcess { - + initialCwd: string | undefined = undefined; + cwd: string | undefined = undefined; private readonly _onProcessData = this._register(new Emitter()); readonly onProcessData = this._onProcessData.event; private readonly _onProcessExit = this._register(new Emitter()); @@ -31,6 +32,8 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; private readonly _onDidChangeHasChildProcesses = this._register(new Emitter()); readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event; + private readonly _onDidChangeProperty = this._register(new Emitter()); + readonly onDidChangeProperty = this._onDidChangeProperty.event; private _startBarrier: Barrier; @@ -117,13 +120,21 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { } async getInitialCwd(): Promise { - await this._startBarrier.wait(); - return this._remoteTerminalChannel.getInitialCwd(this._id); + if (!this.initialCwd) { + await this._startBarrier.wait(); + this.initialCwd = await this._remoteTerminalChannel.getInitialCwd(this._id); + } + return this.initialCwd; } async getCwd(): Promise { await this._startBarrier.wait(); - return this._remoteTerminalChannel.getCwd(this._id); + const cwd = await this._remoteTerminalChannel.getCwd(this._id); + if (this.cwd !== cwd) { + this.cwd = cwd; + this.handleDidChangeProperty({ type: TerminalPropertyType.cwd, value: this.cwd }); + } + return this.cwd; } handleData(e: string | IProcessDataEvent) { @@ -157,6 +168,9 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { handleDidChangeHasChildProcesses(e: boolean) { this._onDidChangeHasChildProcesses.fire(e); } + handleDidChangeProperty(e: TerminalProperty) { + this._onDidChangeProperty.fire(e); + } async handleReplay(e: IPtyHostProcessReplayEvent) { try { diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts index 0ae07e4cfc7..4cac0e267a3 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts @@ -79,6 +79,7 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal channel.onProcessOrphanQuestion(e => this._ptys.get(e.id)?.handleOrphanQuestion()); channel.onDidRequestDetach(e => this._onDidRequestDetach.fire(e)); channel.onProcessDidChangeHasChildProcesses(e => this._ptys.get(e.id)?.handleDidChangeHasChildProcesses(e.event)); + channel.onDidChangeProperty(e => this._ptys.get(e.id)?.handleDidChangeProperty(e.event)); const allowedCommands = ['_remoteCLI.openExternal', '_remoteCLI.windowOpen', '_remoteCLI.getSystemStatus', '_remoteCLI.manageExtensions']; channel.onExecuteCommand(async e => { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 1a00db4dd12..5911e452c69 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -46,7 +46,7 @@ import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTy import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalSettingPrefix, ITerminalProfileObject, TerminalLocation } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalSettingPrefix, ITerminalProfileObject, TerminalLocation, TerminalPropertyType } from 'vs/platform/terminal/common/terminal'; import { IProductService } from 'vs/platform/product/common/productService'; import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { AutoOpenBarrier } from 'vs/base/common/async'; @@ -137,6 +137,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _terminalA11yTreeFocusContextKey: IContextKey; private _cols: number = 0; private _rows: number = 0; + private _cwd: string | undefined = undefined; + private _initialCwd: string | undefined = undefined; private _dimensionsOverride: ITerminalDimensionsOverride | undefined; private _xtermReadyPromise: Promise; private _titleReadyPromise: Promise; @@ -1151,10 +1153,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { protected _createProcessManager(): void { this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._instanceId, this._configHelper); - this._processManager.onProcessReady(() => { + this._processManager.onProcessReady(async () => { this._onProcessIdReady.fire(this); + this._initialCwd = await this.getInitialCwd(); + // Set the initial name based on the _resolved_ shell launch config, this will also // ensure the resolved icon gets shown + this._processManager.onDidChangeProperty(e => { + if (e.type === TerminalPropertyType.cwd || e.type === TerminalPropertyType.initialCwd) { + this._cwd = e.value; + this.setTitle(this.title, TitleEventSource.Api); + } + }); if (this._shellLaunchConfig.name) { this.setTitle(this._shellLaunchConfig.name, TitleEventSource.Api); } else { @@ -1492,10 +1502,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // reset cwd if it has changed, so file based url paths can be resolved const cwd = await this.getCwd(); if (cwd && this._linkManager) { - if (this._linkManager.processCwd !== cwd) { - this._linkManager.processCwd = cwd; - this.setTitle(this.title, TitleEventSource.Api); - } + this._linkManager.processCwd = cwd; } return cwd; } @@ -1781,33 +1788,32 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Remove special characters that could mess with rendering title = title.replace(/[\n\r\t]/g, ''); - this?.getCwd().then(cwd => { - const properties = { - cwd, - cwdFolder: path.basename(cwd), - local: this.shellLaunchConfig.description === 'Local' ? 'Local' : undefined, - process: this._processName || title, - sequence: this._sequence, - task: this.shellLaunchConfig.description === 'Task' ? 'Task' : undefined, - separator: { label: this._configHelper.config.tabs.separator } - }; - title = template(this._configHelper.config.tabs.title, properties); - const description = template(this._configHelper.config.tabs.description, properties); - const titleChanged = title !== this._title || description !== this.description || eventSource === TitleEventSource.Config; - if (!title || !titleChanged) { - return; - } - this._title = title; - this._description = description; - this._titleSource = eventSource; - this._setAriaLabel(this._xterm, this._instanceId, this._title); + const cwd = this._cwd || this._initialCwd || ''; + const properties = { + cwd, + cwdFolder: path.basename(cwd), + local: this.shellLaunchConfig.description === 'Local' ? 'Local' : undefined, + process: this._processName || title, + sequence: this._sequence, + task: this.shellLaunchConfig.description === 'Task' ? 'Task' : undefined, + separator: { label: this._configHelper.config.tabs.separator } + }; + title = template(this._configHelper.config.tabs.title, properties); + const description = template(this._configHelper.config.tabs.description, properties); + const titleChanged = title !== this._title || description !== this.description || eventSource === TitleEventSource.Config; + if (!title || !titleChanged) { + return; + } + this._title = title; + this._description = description; + this._titleSource = eventSource; + this._setAriaLabel(this._xterm, this._instanceId, this._title); - if (this._titleReadyComplete) { - this._titleReadyComplete(title); - this._titleReadyComplete = undefined; - } - this._onTitleChanged.fire(this); - }); + if (this._titleReadyComplete) { + this._titleReadyComplete(title); + this._titleReadyComplete = undefined; + } + this._onTitleChanged.fire(this); } waitForTitle(): Promise { @@ -1946,8 +1952,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { xterm.setOption('logLevel', isDebug ? 'info' : 'debug'); } - getInitialCwd(): Promise { - return this._processManager.getInitialCwd(); + async getInitialCwd(): Promise { + if (!this._initialCwd) { + this._initialCwd = await this._processManager.getInitialCwd(); + } + return this._initialCwd; } getCwd(): Promise { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts index 7a0a05999e5..3423d0e3baa 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts @@ -5,7 +5,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalProperty, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -46,6 +46,8 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal readonly onRequestLatency: Event = this._onRequestLatency.event; private readonly _onProcessShellTypeChanged = this._register(new Emitter()); readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; + private readonly _onDidChangeProperty = this._register(new Emitter()); + readonly onDidChangeProperty = this._onDidChangeProperty.event; private _pendingInitialCwdRequests: ((value: string | PromiseLike) => void)[] = []; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index f535a5d2ebe..f9449eeadd8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -22,7 +22,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { EnvironmentVariableInfoChangesActive, EnvironmentVariableInfoStale } from 'vs/workbench/contrib/terminal/browser/environmentVariableInfo'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IEnvironmentVariableInfo, IEnvironmentVariableService, IMergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, TerminalShellType, ITerminalDimensions, TerminalSettingId, IProcessReadyEvent } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, TerminalShellType, ITerminalDimensions, TerminalSettingId, IProcessReadyEvent, TerminalProperty } from 'vs/platform/terminal/common/terminal'; import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder'; import { localize } from 'vs/nls'; import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings'; @@ -96,6 +96,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce readonly onProcessData = this._onProcessData.event; private readonly _onProcessTitle = this._register(new Emitter()); readonly onProcessTitle = this._onProcessTitle.event; + private readonly _onDidChangeProperty = this._register(new Emitter()); + readonly onDidChangeProperty = this._onDidChangeProperty.event; private readonly _onProcessShellTypeChanged = this._register(new Emitter()); readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; private readonly _onProcessExit = this._register(new Emitter()); @@ -323,7 +325,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce }), newProcess.onProcessTitleChanged(title => this._onProcessTitle.fire(title)), newProcess.onProcessShellTypeChanged(type => this._onProcessShellTypeChanged.fire(type)), - newProcess.onProcessExit(exitCode => this._onExit(exitCode)) + newProcess.onProcessExit(exitCode => this._onExit(exitCode)), + newProcess.onDidChangeProperty(property => this._onDidChangeProperty.fire(property)) ]; if (newProcess.onProcessOverrideDimensions) { this._processListeners.push(newProcess.onProcessOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e))); diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index f500e6f3265..ecfc0bed541 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -18,7 +18,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { Schemas } from 'vs/base/common/network'; import { ILabelService } from 'vs/platform/label/common/label'; import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { IProcessDataEvent, IRequestResolveVariablesEvent, IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IRequestResolveVariablesEvent, IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalProperty, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess'; import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; @@ -124,6 +124,9 @@ export class RemoteTerminalChannelClient { get onDidRequestDetach(): Event<{ requestId: number, workspaceId: string, instanceId: number }> { return this._channel.listen<{ requestId: number, workspaceId: string, instanceId: number }>('$onDidRequestDetach'); } + get onDidChangeProperty(): Event<{ id: number, event: TerminalProperty }> { + return this._channel.listen<{ id: number, event: TerminalProperty }>('$onDidChangeProperty'); + } constructor( private readonly _remoteAuthority: string, diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 6fbe8d9f01f..6b4dc599f7e 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, TerminalProperty, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; @@ -291,6 +291,7 @@ export interface ITerminalProcessManager extends IDisposable { readonly onBeforeProcessData: Event; readonly onProcessData: Event; readonly onProcessTitle: Event; + readonly onDidChangeProperty: Event; readonly onProcessShellTypeChanged: Event; readonly onProcessExit: Event; readonly onProcessOverrideDimensions: Event; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts index b33e6bd93ea..7a6323c4473 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts @@ -6,7 +6,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; -import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalProperty, TerminalPropertyType, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/terminalProcess'; /** @@ -15,7 +15,8 @@ import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/terminal */ export class LocalPty extends Disposable implements ITerminalChildProcess { private _inReplay = false; - + initialCwd: string | undefined = undefined; + cwd: string | undefined = undefined; private readonly _onProcessData = this._register(new Emitter()); readonly onProcessData = this._onProcessData.event; private readonly _onProcessReplay = this._register(new Emitter()); @@ -34,6 +35,8 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event; private readonly _onDidChangeHasChildProcesses = this._register(new Emitter()); readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event; + private readonly _onDidChangeProperty = this._register(new Emitter()); + readonly onDidChangeProperty = this._onDidChangeProperty.event; constructor( readonly id: number, @@ -70,11 +73,19 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { } this._localPtyService.resize(this.id, cols, rows); } - getInitialCwd(): Promise { - return this._localPtyService.getInitialCwd(this.id); + async getInitialCwd(): Promise { + if (!this.initialCwd) { + this.initialCwd = await this._localPtyService.getInitialCwd(this.id); + } + return this.initialCwd; } - getCwd(): Promise { - return this._localPtyService.getCwd(this.id); + async getCwd(): Promise { + const cwd = await this._localPtyService.getCwd(this.id); + if (this.cwd !== cwd) { + this.cwd = cwd; + this.handleDidChangeProperty({ type: TerminalPropertyType.cwd, value: this.cwd }); + } + return this.cwd; } getLatency(): Promise { // TODO: The idea here was to add the result plus the time it took to get the latency @@ -114,6 +125,9 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { handleDidChangeHasChildProcesses(e: boolean) { this._onDidChangeHasChildProcesses.fire(e); } + handleDidChangeProperty(e: TerminalProperty) { + this._onDidChangeProperty.fire(e); + } async handleReplay(e: IPtyHostProcessReplayEvent) { try { diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts index fc155cf9aa0..58c6b829b03 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts @@ -66,6 +66,7 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe this._localPtyService.onProcessOverrideDimensions(e => this._ptys.get(e.id)?.handleOverrideDimensions(e.event)); this._localPtyService.onProcessResolvedShellLaunchConfig(e => this._ptys.get(e.id)?.handleResolvedShellLaunchConfig(e.event)); this._localPtyService.onProcessDidChangeHasChildProcesses(e => this._ptys.get(e.id)?.handleDidChangeHasChildProcesses(e.event)); + this._localPtyService.onDidChangeProperty(e => this._ptys.get(e.id)?.handleDidChangeProperty(e.event)); this._localPtyService.onProcessReplay(e => this._ptys.get(e.id)?.handleReplay(e.event)); this._localPtyService.onProcessOrphanQuestion(e => this._ptys.get(e.id)?.handleOrphanQuestion()); this._localPtyService.onDidRequestDetach(e => this._onDidRequestDetach.fire(e)); diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts b/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts index f4d8f777b4f..4c68b3a7708 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { DeferredPromise } from 'vs/base/common/async'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { listenStream } from 'vs/base/common/stream'; import { isDefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IProcessDataEvent, ITerminalChildProcess, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { IViewsService } from 'vs/workbench/common/views'; import { ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -147,6 +147,10 @@ export class TestingOutputTerminalService implements ITestingOutputTerminalServi } class TestOutputProcess extends Disposable implements ITerminalChildProcess { + onProcessOverrideDimensions?: Event | undefined; + onProcessResolvedShellLaunchConfig?: Event | undefined; + onDidChangeHasChildProcesses?: Event | undefined; + onDidChangeProperty = Event.None; private processDataEmitter = this._register(new Emitter()); private titleEmitter = this._register(new Emitter()); private readonly startedDeferred = new DeferredPromise(); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 51ae34715b8..7ed48f53aa4 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -126,7 +126,7 @@ import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEd import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; -import { IShellLaunchConfig, ITerminalChildProcess, ITerminalProfile, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalProfile, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { IProcessDetails, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess'; import { ICreateTerminalOptions, ITerminalInstance, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { isArray } from 'vs/base/common/types'; @@ -1705,7 +1705,11 @@ class TestTerminalChildProcess implements ITerminalChildProcess { readonly shouldPersist: boolean ) { } + onProcessOverrideDimensions?: Event | undefined; + onProcessResolvedShellLaunchConfig?: Event | undefined; + onDidChangeHasChildProcesses?: Event | undefined; + onDidChangeProperty = Event.None; onProcessData = Event.None; onProcessExit = Event.None; onProcessReady = Event.None;