diff options
12 files changed, 113 insertions, 21 deletions
diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 59d7391c9e0..b539eb20aeb 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -99,6 +99,8 @@ export interface IOffProcessTerminalService { getWslPath(original: string): Promise<string>; getEnvironment(): Promise<IProcessEnvironment>; setTerminalLayoutInfo(layoutInfo?: ITerminalsLayoutInfoById): Promise<void>; + updateTitle(id: number, title: string): Promise<void>; + updateIcon(id: number, icon: string): Promise<void>; getTerminalLayoutInfo(): Promise<ITerminalsLayoutInfo | undefined>; reduceConnectionGraceTime(): Promise<void>; } @@ -160,7 +162,8 @@ export interface IPtyService { processBinary(id: number, data: string): Promise<void>; /** Confirm the process is _not_ an orphan. */ orphanQuestionReply(id: number): Promise<void>; - + updateTitle(id: number, title: string): Promise<void> + updateIcon(id: number, icon: string): Promise<void> getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string>; getEnvironment(): Promise<IProcessEnvironment>; getWslPath(original: string): Promise<string>; diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 12cd6b1fc69..b9f450ad4b3 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -154,6 +154,12 @@ export class PtyHostService extends Disposable implements IPtyService { lastPtyId = Math.max(lastPtyId, id); return id; } + updateTitle(id: number, title: string): Promise<void> { + return this._proxy.updateTitle(id, title); + } + updateIcon(id: number, icon: string): Promise<void> { + return this._proxy.updateIcon(id, icon); + } attachToProcess(id: number): Promise<void> { return this._proxy.attachToProcess(id); } diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 77e21a77185..8b3af0d3706 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -114,6 +114,14 @@ export class PtyService extends Disposable implements IPtyService { } } + async updateTitle(id: number, title: string): Promise<void> { + this._throwIfNoPty(id).setTitle(title); + } + + async updateIcon(id: number, icon: string): Promise<void> { + this._throwIfNoPty(id).setIcon(icon); + } + async detachFromProcess(id: number): Promise<void> { this._throwIfNoPty(id).detach(); } @@ -289,11 +297,21 @@ export class PersistentTerminalProcess extends Disposable { private _pid = -1; private _cwd = ''; + private _title: string | undefined; + get pid(): number { return this._pid; } - get title(): string { return this._terminalProcess.currentTitle; } + get title(): string { return this._title || this._terminalProcess.currentTitle; } get icon(): string | undefined { return this._icon; } + setTitle(title: string): void { + this._title = title; + } + + setIcon(icon: string): void { + this._icon = icon; + } + constructor( private _persistentProcessId: number, private readonly _terminalProcess: TerminalProcess, @@ -302,7 +320,7 @@ export class PersistentTerminalProcess extends Disposable { readonly shouldPersistTerminal: boolean, cols: number, rows: number, private readonly _logService: ILogService, - private readonly _icon?: string + private _icon?: string ) { super(); this._recorder = new TerminalRecorder(cols, rows); diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts index 23cbb1c5d18..8add8f13019 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts @@ -189,6 +189,14 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal }); } + async updateTitle(id: number, title: string): Promise<void> { + await this._remoteTerminalChannel?.updateTitle(id, title); + } + + async updateIcon(id: number, icon: string): Promise<void> { + await this._remoteTerminalChannel?.updateIcon(id, icon); + } + async getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string> { return this._remoteTerminalChannel?.getDefaultSystemShell(osOverride) || ''; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 65640673ce6..c222eaa6aee 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -93,6 +93,7 @@ export interface ITerminalService { onInstanceRequestStartExtensionTerminal: Event<IStartExtensionTerminalRequest>; onInstancesChanged: Event<void>; onInstanceTitleChanged: Event<ITerminalInstance | undefined>; + onInstanceIconChanged: Event<ITerminalInstance | undefined>; onInstancePrimaryStatusChanged: Event<ITerminalInstance>; onActiveInstanceChanged: Event<ITerminalInstance | undefined>; onRequestAvailableProfiles: Event<IAvailableProfilesRequest>; @@ -287,6 +288,11 @@ export interface ITerminalInstance { onTitleChanged: Event<ITerminalInstance>; /** + * An event that fires when the terminal instance's icon changes. + */ + onIconChanged: Event<ITerminalInstance>; + + /** * An event that fires when the terminal instance is disposed. */ onDisposed: Event<ITerminalInstance>; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 046becf93ec..7eb18af8748 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -173,7 +173,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get areLinksReady(): boolean { return this._areLinksReady; } get initialDataEvents(): string[] | undefined { return this._initialDataEvents; } get exitCode(): number | undefined { return this._exitCode; } - get title(): string { return this._title; } + get hadFocusOnExit(): boolean { return this._hadFocusOnExit; } get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; } get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; } @@ -182,6 +182,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; } get isDisconnected(): boolean { return this._processManager.isDisconnected; } get isRemote(): boolean { return this._processManager.remoteAuthority !== undefined; } + get title(): string { return this._getTitle(); } get icon(): Codicon | undefined { return this._getIcon(); } private readonly _onExit = new Emitter<number | undefined>(); @@ -198,6 +199,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get onLinksReady(): Event<ITerminalInstance> { return this._onLinksReady.event; } private readonly _onTitleChanged = new Emitter<ITerminalInstance>(); get onTitleChanged(): Event<ITerminalInstance> { return this._onTitleChanged.event; } + private readonly _onIconChanged = new Emitter<ITerminalInstance>(); + get onIconChanged(): Event<ITerminalInstance> { return this._onIconChanged.event; } private readonly _onData = new Emitter<string>(); get onData(): Event<string> { return this._onData.event; } private readonly _onBinary = new Emitter<string>(); @@ -327,6 +330,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return undefined; } + private _getTitle(): string { + if (this.shellLaunchConfig.attachPersistentProcess?.title) { + return this.shellLaunchConfig.attachPersistentProcess.title; + } + return this._title; + } + addDisposable(disposable: IDisposable): void { this._register(disposable); } @@ -1779,7 +1789,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); if (result) { this.shellLaunchConfig.icon = result.description; - this._onTitleChanged.fire(this); + this._onIconChanged.fire(this); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 412cc81f407..b77aa604252 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -110,6 +110,8 @@ export class TerminalService implements ITerminalService { get onInstancesChanged(): Event<void> { return this._onInstancesChanged.event; } private readonly _onInstanceTitleChanged = new Emitter<ITerminalInstance | undefined>(); get onInstanceTitleChanged(): Event<ITerminalInstance | undefined> { return this._onInstanceTitleChanged.event; } + private readonly _onInstanceIconChanged = new Emitter<ITerminalInstance | undefined>(); + get onInstanceIconChanged(): Event<ITerminalInstance | undefined> { return this._onInstanceIconChanged.event; } private readonly _onActiveInstanceChanged = new Emitter<ITerminalInstance | undefined>(); get onActiveInstanceChanged(): Event<ITerminalInstance | undefined> { return this._onActiveInstanceChanged.event; } private readonly _onInstancePrimaryStatusChanged = new Emitter<ITerminalInstance>(); @@ -294,12 +296,18 @@ export class TerminalService implements ITerminalService { } private _attachProcessLayoutListeners(isRemote: boolean): void { - this.onActiveTabChanged(() => isRemote ? this._updateRemoteState() : this._updateLocalState()); - this.onActiveInstanceChanged(() => isRemote ? this._updateRemoteState() : this._updateLocalState()); - this.onInstancesChanged(() => isRemote ? this._updateRemoteState() : this._updateLocalState()); + this.onActiveTabChanged(() => this._saveState(isRemote)); + this.onActiveInstanceChanged(() => this._saveState(isRemote)); + this.onInstancesChanged(() => this._saveState(isRemote)); // The state must be updated when the terminal is relaunched, otherwise the persistent // terminal ID will be stale and the process will be leaked. - this.onInstanceProcessIdReady(() => isRemote ? this._updateRemoteState() : this._updateLocalState()); + this.onInstanceProcessIdReady(() => this._saveState(isRemote)); + this.onInstanceTitleChanged(instance => { + this._updateTitle(instance); + }); + this.onInstanceIconChanged(instance => { + this._updateIcon(instance); + }); } private _handleInstanceContextKeys(): void { @@ -411,21 +419,30 @@ export class TerminalService implements ITerminalService { } @debounce(500) - private _updateRemoteState(): void { - if (!!this._environmentService.remoteAuthority) { - const state: ITerminalsLayoutInfoById = { - tabs: this.terminalGroups.map(t => t.getLayoutInfo(t === this.getActiveGroup())) - }; - this._remoteTerminalService.setTerminalLayoutInfo(state); + private _saveState(isRemote?: boolean): void { + const offProcService = isRemote ? this._remoteTerminalService : this._localTerminalService; + const state: ITerminalsLayoutInfoById = { + tabs: this.terminalGroups.map(t => t.getLayoutInfo(t === this.getActiveGroup())) + }; + offProcService!.setTerminalLayoutInfo(state); + } + + @debounce(500) + private _updateTitle(instance?: ITerminalInstance): void { + if (!instance?.persistentProcessId || !instance?.title) { + return; } + this._localTerminalService!.updateTitle(instance.persistentProcessId, instance.title); + this._saveState(); } @debounce(500) - private _updateLocalState(): void { - const state: ITerminalsLayoutInfoById = { - tabs: this.terminalGroups.map(t => t.getLayoutInfo(t === this.getActiveGroup())) - }; - this._localTerminalService!.setTerminalLayoutInfo(state); + private _updateIcon(instance?: ITerminalInstance): void { + if (!instance?.persistentProcessId || !instance?.icon) { + return; + } + this._localTerminalService!.updateIcon(instance.persistentProcessId, instance.icon.id); + this._saveState(); } private _removeTab(group: ITerminalGroup): void { @@ -630,13 +647,14 @@ export class TerminalService implements ITerminalService { protected _initInstanceListeners(instance: ITerminalInstance): void { instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); + instance.addDisposable(instance.onIconChanged(this._onInstanceIconChanged.fire, this._onInstanceIconChanged)); instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); instance.addDisposable(instance.statusList.onDidChangePrimaryStatus(() => this._onInstancePrimaryStatusChanged.fire(instance))); instance.addDisposable(instance.onLinksReady(this._onInstanceLinksReady.fire, this._onInstanceLinksReady)); instance.addDisposable(instance.onDimensionsChanged(() => { this._onInstanceDimensionsChanged.fire(instance); if (this.configHelper.config.enablePersistentSessions && this.isProcessSupportRegistered) { - !!this._environmentService.remoteAuthority ? this._updateRemoteState() : this._updateLocalState(); + this._saveState(!!this._environmentService.remoteAuthority); } })); instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance))); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 586a375436a..cba17982e1f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -74,6 +74,7 @@ export class TerminalTabList extends WorkbenchList<ITerminalInstance> { ); this._terminalService.onInstancesChanged(() => this._render()); this._terminalService.onInstanceTitleChanged(() => this._render()); + this._terminalService.onInstanceIconChanged(() => this._render()); this._terminalService.onActiveInstanceChanged(e => { if (e) { const i = this._terminalService.terminalInstances.indexOf(e); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index df15b8c1cc9..c43f4530b24 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -346,6 +346,11 @@ class SingleTerminalTabActionViewItem extends ActionViewItem { this.updateLabel(); } })); + this._register(this._terminalService.onInstanceIconChanged(e => { + if (e === this._terminalService.getActiveInstance()) { + this.updateLabel(); + } + })); } override updateLabel(): void { diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index bdba2cba32b..1844ff66f35 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -253,6 +253,14 @@ export class RemoteTerminalChannelClient { return this._channel.call<void>('$setTerminalLayoutInfo', args); } + updateTitle(id: number, title: string): Promise<string> { + return this._channel.call('$updateTitle', { id, title }); + } + + updateIcon(id: number, icon: string): Promise<string> { + return this._channel.call('$updateIcon', { id, icon }); + } + getTerminalLayoutInfo(): Promise<ITerminalsLayoutInfo | undefined> { const workspace = this._workspaceContextService.getWorkspace(); const args: IGetTerminalLayoutInfoArgs = { diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts index f32180d18bd..4d465ac7836 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts @@ -97,6 +97,13 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe })); } } + async updateTitle(id: number, title: string): Promise<void> { + await this._localPtyService.updateTitle(id, title); + } + + async updateIcon(id: number, icon: string): Promise<void> { + await this._localPtyService.updateIcon(id, icon); + } async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean): Promise<ITerminalChildProcess> { const executableEnv = await this._shellEnvironmentService.getShellEnv(); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index ad5699dc83d..1e2d16ecc29 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1617,6 +1617,8 @@ export class TestLocalTerminalService implements ILocalTerminalService { async getTerminalLayoutInfo(): Promise<ITerminalsLayoutInfo | undefined> { throw new Error('Method not implemented.'); } async reduceConnectionGraceTime(): Promise<void> { throw new Error('Method not implemented.'); } processBinary(id: number, data: string): Promise<void> { throw new Error('Method not implemented.'); } + updateTitle(id: number, title: string): Promise<void> { throw new Error('Method not implemented.'); } + updateIcon(id: number, icon: string): Promise<void> { throw new Error('Method not implemented.'); } } class TestTerminalChildProcess implements ITerminalChildProcess { |