diff --git a/package.json b/package.json index eaef8f06d68..282a55d8e88 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.51.0", - "distro": "2efd579dedb12e7e9ed9ebd112120fa8b3b0922e", + "distro": "5a2a07b357e780fb7130743e24cf814126485ef9", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index c5c03a86786..17b4458a107 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -63,7 +63,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._toDispose.add(_terminalService.onInstanceRequestSpawnExtHostProcess(request => this._onRequestSpawnExtHostProcess(request))); this._toDispose.add(_terminalService.onInstanceRequestStartExtensionTerminal(e => this._onRequestStartExtensionTerminal(e))); this._toDispose.add(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null))); - this._toDispose.add(_terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title))); + this._toDispose.add(_terminalService.onInstanceTitleChanged(instance => instance && this._onTitleChanged(instance.id, instance.title))); this._toDispose.add(_terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed))); this._toDispose.add(_terminalService.onRequestAvailableShells(e => this._onRequestAvailableShells(e))); diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index d722f2b428e..97d30a82f61 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -9,7 +9,7 @@ import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShap import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ITerminalChildProcess, ITerminalDimensions, EXT_HOST_CREATION_DELAY, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalChildProcess, EXT_HOST_CREATION_DELAY, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; import { timeout } from 'vs/base/common/async'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; @@ -245,8 +245,8 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } private readonly _onProcessTitleChanged = new Emitter(); public readonly onProcessTitleChanged: Event = this._onProcessTitleChanged.event; - private readonly _onProcessOverrideDimensions = new Emitter(); - public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } + private readonly _onProcessOverrideDimensions = new Emitter(); + public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } constructor(private readonly _pty: vscode.Pseudoterminal) { } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index ebe6736547c..3669114bf2a 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -46,6 +46,9 @@ export class TreeViewPane extends ViewPane { if (options.title !== this.treeView.title) { this.updateTitle(this.treeView.title); } + if (options.titleDescription !== this.treeView.description) { + this.updateTitleDescription(this.treeView.description); + } this.updateTreeVisibility(); } diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts index 959f572ed2d..68aa3c4b42a 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts @@ -8,24 +8,21 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { URI } from 'vs/base/common/uri'; -import * as nls from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IRemoteTerminalService, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IRemoteTerminalService, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteTerminalProcessExecCommandEvent, IShellLaunchConfigDto, RemoteTerminalChannelClient, REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; -import { IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IProcessDataEvent, IRemoteTerminalAttachTarget, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensionsOverride, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class RemoteTerminalService extends Disposable implements IRemoteTerminalService { public _serviceBrand: undefined; private readonly _remoteTerminalChannel: RemoteTerminalChannelClient | null; - private _hasConnectedToRemote = false; constructor( - @ITerminalService _terminalService: ITerminalService, @ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @ILogService private readonly _logService: ILogService, @@ -46,34 +43,42 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal throw new Error(`Cannot create remote terminal when there is no remote!`); } - let isPreconnectionTerminal = false; - if (!this._hasConnectedToRemote) { - isPreconnectionTerminal = true; - this._remoteAgentService.getEnvironment().then(() => { - this._hasConnectedToRemote = true; - }); - } + return new RemoteTerminalProcess(terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper, this._remoteTerminalChannel, this._remoteAgentService, this._logService, this._commandService); + } - return new RemoteTerminalProcess(terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper, isPreconnectionTerminal, this._remoteTerminalChannel, this._remoteAgentService, this._logService, this._commandService); + public async listTerminals(): Promise { + const terms = this._remoteTerminalChannel ? await this._remoteTerminalChannel.listTerminals() : []; + return terms.map(termDto => { + return { + id: termDto.id, + pid: termDto.pid, + title: termDto.title, + cwd: termDto.cwd + }; + }); } } export class RemoteTerminalProcess extends Disposable implements ITerminalChildProcess { - public readonly _onProcessData = this._register(new Emitter()); - public readonly onProcessData: Event = this._onProcessData.event; + public readonly _onProcessData = this._register(new Emitter()); + public readonly onProcessData: Event = this._onProcessData.event; private readonly _onProcessExit = this._register(new Emitter()); public readonly onProcessExit: Event = this._onProcessExit.event; public readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>()); public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } private readonly _onProcessTitleChanged = this._register(new Emitter()); public readonly onProcessTitleChanged: Event = this._onProcessTitleChanged.event; + private readonly _onProcessOverrideDimensions = this._register(new Emitter()); + public readonly onProcessOverrideDimensions: Event = this._onProcessOverrideDimensions.event; private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter()); public get onProcessResolvedShellLaunchConfig(): Event { return this._onProcessResolvedShellLaunchConfig.event; } private _startBarrier: Barrier; private _remoteTerminalId: number; + private _inReplay = false; + constructor( private readonly _terminalId: number, private readonly _shellLaunchConfig: IShellLaunchConfig, @@ -81,7 +86,6 @@ export class RemoteTerminalProcess extends Disposable implements ITerminalChildP private readonly _cols: number, private readonly _rows: number, private readonly _configHelper: ITerminalConfigHelper, - private readonly _isPreconnectionTerminal: boolean, private readonly _remoteTerminalChannel: RemoteTerminalChannelClient, private readonly _remoteAgentService: IRemoteAgentService, private readonly _logService: ILogService, @@ -94,11 +98,6 @@ export class RemoteTerminalProcess extends Disposable implements ITerminalChildP public async start(): Promise { - // Add a loading title only if this terminal is instantiated before a connection is up and running - if (this._isPreconnectionTerminal) { - setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0); - } - // Fetch the environment to check shell permissions const env = await this._remoteAgentService.getEnvironment(); if (!env) { @@ -106,49 +105,45 @@ export class RemoteTerminalProcess extends Disposable implements ITerminalChildP throw new Error('Could not fetch remote environment'); } - const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(env.os); + if (!this._shellLaunchConfig.remoteAttach) { + const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(env.os); - const shellLaunchConfigDto: IShellLaunchConfigDto = { - name: this._shellLaunchConfig.name, - executable: this._shellLaunchConfig.executable, - args: this._shellLaunchConfig.args, - cwd: this._shellLaunchConfig.cwd, - env: this._shellLaunchConfig.env - }; + const shellLaunchConfigDto: IShellLaunchConfigDto = { + name: this._shellLaunchConfig.name, + executable: this._shellLaunchConfig.executable, + args: this._shellLaunchConfig.args, + cwd: this._shellLaunchConfig.cwd, + env: this._shellLaunchConfig.env + }; - this._logService.trace('Spawning remote agent process', { terminalId: this._terminalId, shellLaunchConfigDto }); + this._logService.trace('Spawning remote agent process', { terminalId: this._terminalId, shellLaunchConfigDto }); - const result = await this._remoteTerminalChannel.createTerminalProcess( - shellLaunchConfigDto, - this._activeWorkspaceRootUri, - this._cols, - this._rows, - isWorkspaceShellAllowed, - ); + const result = await this._remoteTerminalChannel.createTerminalProcess( + shellLaunchConfigDto, + this._activeWorkspaceRootUri, + this._cols, + this._rows, + isWorkspaceShellAllowed, + ); - this._remoteTerminalId = result.terminalId; - this._register(this._remoteTerminalChannel.onTerminalProcessEvent(this._remoteTerminalId)(event => { - switch (event.type) { - case 'ready': - return this._onProcessReady.fire({ pid: event.pid, cwd: event.cwd }); - case 'titleChanged': - return this._onProcessTitleChanged.fire(event.title); - case 'data': - return this._onProcessData.fire(event.data); - case 'exit': - return this._onProcessExit.fire(event.exitCode); - case 'execCommand': - return this._execCommand(event); + this._remoteTerminalId = result.terminalId; + this.setupTerminalEventListener(); + this._onProcessResolvedShellLaunchConfig.fire(reviveIShellLaunchConfig(result.resolvedShellLaunchConfig)); + + const startResult = await this._remoteTerminalChannel.startTerminalProcess(this._remoteTerminalId); + + if (typeof startResult !== 'undefined') { + // An error occurred + return startResult; } - })); + } else { + this._remoteTerminalId = this._shellLaunchConfig.remoteAttach.id; + this._onProcessReady.fire({ pid: this._shellLaunchConfig.remoteAttach.pid, cwd: this._shellLaunchConfig.remoteAttach.cwd }); + this.setupTerminalEventListener(); - this._onProcessResolvedShellLaunchConfig.fire(reviveIShellLaunchConfig(result.resolvedShellLaunchConfig)); - - const startResult = await this._remoteTerminalChannel.startTerminalProcess(this._remoteTerminalId); - - if (typeof startResult !== 'undefined') { - // An error occurred - return startResult; + setTimeout(() => { + this._onProcessTitleChanged.fire(this._shellLaunchConfig.remoteAttach!.title); + }, 0); } this._startBarrier.open(); @@ -162,13 +157,62 @@ export class RemoteTerminalProcess extends Disposable implements ITerminalChildP } public input(data: string): void { + if (this._inReplay) { + return; + } + this._startBarrier.wait().then(_ => { this._remoteTerminalChannel.sendInputToTerminalProcess(this._remoteTerminalId, data); }); } + private setupTerminalEventListener(): void { + this._register(this._remoteTerminalChannel.onTerminalProcessEvent(this._remoteTerminalId)(event => { + switch (event.type) { + case 'ready': + return this._onProcessReady.fire({ pid: event.pid, cwd: event.cwd }); + case 'titleChanged': + return this._onProcessTitleChanged.fire(event.title); + case 'data': + return this._onProcessData.fire({ data: event.data, sync: false }); + case 'replay': { + try { + this._inReplay = true; + + for (const e of event.events) { + if (e.cols !== 0 || e.rows !== 0) { + // never override with 0x0 as that is a marker for an unknown initial size + this._onProcessOverrideDimensions.fire({ cols: e.cols, rows: e.rows, forceExactSize: true }); + } + this._onProcessData.fire({ data: e.data, sync: true }); + } + } finally { + this._inReplay = false; + } + + // remove size override + this._onProcessOverrideDimensions.fire(undefined); + + return; + } + case 'exit': + return this._onProcessExit.fire(event.exitCode); + case 'execCommand': + return this._execCommand(event); + case 'orphan?': { + this._remoteTerminalChannel.orphanQuestionReply(this._remoteTerminalId); + return; + } + } + })); + } + public resize(cols: number, rows: number): void { + if (this._inReplay) { + return; + } this._startBarrier.wait().then(_ => { + this._remoteTerminalChannel.resizeTerminalProcess(this._remoteTerminalId, cols, rows); }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 1e8c415f41e..2ba4774be33 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -7,7 +7,7 @@ import type { Terminal as XTermTerminal } from 'xterm'; import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11'; import type { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; -import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions, ITerminalLaunchError, ITerminalNativeWindowsDelegate, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions, ITerminalLaunchError, ITerminalNativeWindowsDelegate, LinuxDistro, IRemoteTerminalAttachTarget } from 'vs/workbench/contrib/terminal/common/terminal'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; import { Event } from 'vs/base/common/event'; @@ -79,6 +79,7 @@ export interface ITerminalService { terminalTabs: ITerminalTab[]; isProcessSupportRegistered: boolean; + initializeTerminals(): Promise; onActiveTabChanged: Event; onTabDisposed: Event; onInstanceCreated: Event; @@ -89,7 +90,7 @@ export interface ITerminalService { onInstanceRequestSpawnExtHostProcess: Event; onInstanceRequestStartExtensionTerminal: Event; onInstancesChanged: Event; - onInstanceTitleChanged: Event; + onInstanceTitleChanged: Event; onActiveInstanceChanged: Event; onRequestAvailableShells: Event; onDidRegisterProcessSupport: Event; @@ -180,6 +181,7 @@ export interface IRemoteTerminalService { dispose(): void; + listTerminals(): Promise; createRemoteTerminalProcess(terminalId: number, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, configHelper: ITerminalConfigHelper,): Promise; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 002bfbabf9c..23bf8cf8cd1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -79,23 +79,9 @@ export class ToggleTerminalAction extends ToggleViewAction { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITerminalService private readonly terminalService: ITerminalService ) { super(id, label, TERMINAL_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService); } - - async run() { - if (this.terminalService.isProcessSupportRegistered && this.terminalService.terminalInstances.length === 0) { - // If there is not yet an instance attempt to create it here so that we can suggest a - // new shell on Windows (and not do so when the panel is restored on reload). - const newTerminalInstance = this.terminalService.createTerminal(undefined); - const toDispose = newTerminalInstance.onProcessIdReady(() => { - newTerminalInstance.focus(); - toDispose.dispose(); - }); - } - return super.run(); - } } export class KillTerminalAction extends Action { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index cb3b9f19a07..544318a4085 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -25,7 +25,7 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalLaunchError, IProcessDataEvent, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; @@ -94,7 +94,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _terminalA11yTreeFocusContextKey: IContextKey; private _cols: number = 0; private _rows: number = 0; - private _dimensionsOverride: ITerminalDimensions | undefined; + private _dimensionsOverride: ITerminalDimensionsOverride | undefined; private _windowsShellHelper: IWindowsShellHelper | undefined; private _xtermReadyPromise: Promise; private _titleReadyPromise: Promise; @@ -117,12 +117,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public get id(): number { return this._id; } public get cols(): number { if (this._dimensionsOverride && this._dimensionsOverride.cols) { + if (this._dimensionsOverride.forceExactSize) { + return this._dimensionsOverride.cols; + } return Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols); } return this._cols; } public get rows(): number { if (this._dimensionsOverride && this._dimensionsOverride.rows) { + if (this._dimensionsOverride.forceExactSize) { + return this._dimensionsOverride.rows; + } return Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows); } return this._rows; @@ -415,7 +421,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._xterm.onKey(e => this._onKey(e.key, e.domEvent)); this._xterm.onSelectionChange(async () => this._onSelectionChange()); - this._processManager.onProcessData(data => this._onProcessData(data)); + this._processManager.onProcessData(e => this._onProcessData(e)); this._xterm.onData(data => this._processManager.write(data)); this.processReady.then(async () => { if (this._linkManager) { @@ -882,11 +888,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._id, this._configHelper); this._processManager.onProcessReady(() => this._onProcessIdReady.fire(this)); this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode)); - this._processManager.onProcessData(data => { - this._initialDataEvents?.push(data); - this._onData.fire(data); + this._processManager.onProcessData(ev => { + this._initialDataEvents?.push(ev.data); + this._onData.fire(ev.data); }); - this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e)); + this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e, true)); this._processManager.onProcessResolvedShellLaunchConfig(e => this._setResolvedShellLaunchConfig(e)); this._processManager.onEnvironmentVariableInfoChanged(e => this._onEnvironmentVariableInfoChanged(e)); @@ -959,9 +965,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - private _onProcessData(data: string): void { - const messageId = ++this._latestXtermWriteData; - this._xterm?.write(data, () => this._latestXtermParseData = messageId); + private _onProcessData(ev: IProcessDataEvent): void { + if (ev.sync) { + this._xtermCore?.writeSync(ev.data); + } else { + const messageId = ++this._latestXtermWriteData; + this._xterm?.write(ev.data, () => this._latestXtermParseData = messageId); + } } /** @@ -1349,6 +1359,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @debounce(50) private async _resize(): Promise { + this._resizeNow(false); + } + + private async _resizeNow(immediate: boolean): Promise { let cols = this.cols; let rows = this.rows; @@ -1398,8 +1412,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - await this._processManager.ptyProcessReady; - this._processManager.setDimensions(cols, rows); + if (immediate) { + // do not await, call setDimensions synchronously + this._processManager.setDimensions(cols, rows); + } else { + await this._processManager.ptyProcessReady; + this._processManager.setDimensions(cols, rows); + } } public setShellType(shellType: TerminalShellType) { @@ -1442,9 +1461,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return this._titleReadyPromise; } - public setDimensions(dimensions: ITerminalDimensions | undefined): void { + public setDimensions(dimensions: ITerminalDimensionsOverride | undefined, immediate: boolean = false): void { + if (this._dimensionsOverride && this._dimensionsOverride.forceExactSize && !dimensions && this._rows === 0 && this._cols === 0) { + // this terminal never had a real size => keep the last dimensions override exact size + this._cols = this._dimensionsOverride.cols; + this._rows = this._dimensionsOverride.rows; + } this._dimensionsOverride = dimensions; - this._resize(); + if (immediate) { + this._resizeNow(true); + } else { + this._resize(); + } } private _setResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts index 84f18d3c998..c7069f89ff0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -23,8 +23,8 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } private readonly _onProcessTitleChanged = this._register(new Emitter()); public readonly onProcessTitleChanged: Event = this._onProcessTitleChanged.event; - private readonly _onProcessOverrideDimensions = this._register(new Emitter()); - public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } + private readonly _onProcessOverrideDimensions = this._register(new Emitter()); + public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter()); public get onProcessResolvedShellLaunchConfig(): Event { return this._onProcessResolvedShellLaunchConfig.event; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 0dd449b36f4..d9bb4929c97 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -6,7 +6,7 @@ import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { env as processEnv } from 'vs/base/common/process'; -import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalDimensions, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalLaunchError, IProcessDataEvent, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; import { ILogService } from 'vs/platform/log/common/log'; import { Emitter, Event } from 'vs/base/common/event'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -69,14 +69,14 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce public get onProcessReady(): Event { return this._onProcessReady.event; } private readonly _onBeforeProcessData = this._register(new Emitter()); public get onBeforeProcessData(): Event { return this._onBeforeProcessData.event; } - private readonly _onProcessData = this._register(new Emitter()); - public get onProcessData(): Event { return this._onProcessData.event; } + private readonly _onProcessData = this._register(new Emitter()); + public get onProcessData(): Event { return this._onProcessData.event; } private readonly _onProcessTitle = this._register(new Emitter()); public get onProcessTitle(): Event { return this._onProcessTitle.event; } private readonly _onProcessExit = this._register(new Emitter()); public get onProcessExit(): Event { return this._onProcessExit.event; } - private readonly _onProcessOverrideDimensions = this._register(new Emitter()); - public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } + private readonly _onProcessOverrideDimensions = this._register(new Emitter()); + public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } private readonly _onProcessOverrideShellLaunchConfig = this._register(new Emitter()); public get onProcessResolvedShellLaunchConfig(): Event { return this._onProcessOverrideShellLaunchConfig.event; } private readonly _onEnvironmentVariableInfoChange = this._register(new Emitter()); @@ -158,8 +158,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); - const enableRemoteAgentTerminals = this._workspaceConfigurationService.getValue('terminal.integrated.serverSpawn'); - if (enableRemoteAgentTerminals) { + const enableRemoteAgentTerminals = this._workspaceConfigurationService.getValue('terminal.integrated.serverSpawn'); + if (enableRemoteAgentTerminals !== false) { this._process = await this._remoteTerminalService.createRemoteTerminalProcess(this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper); } else { this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper); @@ -171,11 +171,13 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this.processState = ProcessState.LAUNCHING; - this._process.onProcessData(data => { + this._process.onProcessData(ev => { + const data = (typeof ev === 'string' ? ev : ev.data); + const sync = (typeof ev === 'string' ? false : ev.sync); const beforeProcessDataEvent: IBeforeProcessDataEvent = { data }; this._onBeforeProcessData.fire(beforeProcessDataEvent); if (beforeProcessDataEvent.data && beforeProcessDataEvent.data.length > 0) { - this._onProcessData.fire(beforeProcessDataEvent.data); + this._onProcessData.fire({ data: beforeProcessDataEvent.data, sync }); } }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index f6500de4078..9f8a4350bb2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -14,7 +14,7 @@ import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; -import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType, ITerminalExternalLinkProvider } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType, ITerminalExternalLinkProvider, IRemoteTerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; @@ -85,8 +85,8 @@ export class TerminalService implements ITerminalService { public get onInstanceMaximumDimensionsChanged(): Event { return this._onInstanceMaximumDimensionsChanged.event; } private readonly _onInstancesChanged = new Emitter(); public get onInstancesChanged(): Event { return this._onInstancesChanged.event; } - private readonly _onInstanceTitleChanged = new Emitter(); - public get onInstanceTitleChanged(): Event { return this._onInstanceTitleChanged.event; } + private readonly _onInstanceTitleChanged = new Emitter(); + public get onInstanceTitleChanged(): Event { return this._onInstanceTitleChanged.event; } private readonly _onActiveInstanceChanged = new Emitter(); public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } private readonly _onTabDisposed = new Emitter(); @@ -108,7 +108,9 @@ export class TerminalService implements ITerminalService { @IConfigurationService private _configurationService: IConfigurationService, @IViewsService private _viewsService: IViewsService, @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + @IConfigurationService private readonly _workspaceConfigurationService: IConfigurationService, + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IRemoteTerminalService private readonly _remoteTerminalService: IRemoteTerminalService ) { this._activeTabIndex = 0; this._isShuttingDown = false; @@ -221,7 +223,7 @@ export class TerminalService implements ITerminalService { } public getTabLabels(): string[] { - return this._terminalTabs.filter(tab => tab.terminalInstances.length > 0).map((tab, index) => `${index + 1}: ${tab.title ? tab.title : ''}`); + return this._terminalTabs.map((tab, index) => `${index + 1}: ${tab.title ? tab.title : ''}`); } public getFindState(): FindReplaceState { @@ -339,6 +341,31 @@ export class TerminalService implements ITerminalService { } } + public async initializeTerminals(): Promise { + const enableRemoteAgentTerminals = this._workspaceConfigurationService.getValue('terminal.integrated.serverSpawn'); + if (!!this._environmentService.remoteAuthority && enableRemoteAgentTerminals !== false) { + const emptyTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, undefined); + this._terminalTabs.push(emptyTab); + this._onInstanceTitleChanged.fire(undefined); + const remoteTerms = await this._remoteTerminalService.listTerminals(); + if (remoteTerms.length > 0) { + // Reattach to all remote terms + this.createTerminal({ remoteAttach: remoteTerms[0] }, emptyTab); + for (let term of remoteTerms.slice(1)) { + this.createTerminal({ remoteAttach: term }); + } + } else { + // Remote, no terminals to attach to + this.createTerminal(undefined, emptyTab); + } + } else { + if (this.terminalInstances.length === 0) { + // Local, just create a terminal + this.createTerminal(); + } + } + } + private _getInstanceFromGlobalInstanceIndex(index: number): { tab: ITerminalTab, tabIndex: number, instance: ITerminalInstance, localInstanceIndex: number } | null { let currentTabIndex = 0; while (index >= 0 && currentTabIndex < this._terminalTabs.length) { @@ -610,7 +637,7 @@ export class TerminalService implements ITerminalService { return instance; } - public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance { + public createTerminal(shell: IShellLaunchConfig = {}, terminalTab?: TerminalTab): ITerminalInstance { if (!this.isProcessSupportRegistered) { throw new Error('Could not create terminal when process support is not registered'); } @@ -620,8 +647,14 @@ export class TerminalService implements ITerminalService { this._initInstanceListeners(instance); return instance; } - const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, shell); - this._terminalTabs.push(terminalTab); + + if (terminalTab) { + terminalTab.addInstance(shell); + } else { + terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, shell); + this._terminalTabs.push(terminalTab); + } + const instance = terminalTab.terminalInstances[0]; terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged)); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts index eb45ee52381..a798660b2f7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IShellLaunchConfig, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; @@ -228,7 +229,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { constructor( private _container: HTMLElement | undefined, - shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance, + shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance | undefined, @ITerminalService private readonly _terminalService: ITerminalService, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, @@ -236,6 +237,18 @@ export class TerminalTab extends Disposable implements ITerminalTab { ) { super(); + if (shellLaunchConfigOrInstance) { + this.addInstance(shellLaunchConfigOrInstance); + } + + this._activeInstanceIndex = 0; + + if (this._container) { + this.attachToElement(this._container); + } + } + + public addInstance(shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance): void { let instance: ITerminalInstance; if ('id' in shellLaunchConfigOrInstance) { instance = shellLaunchConfigOrInstance; @@ -244,11 +257,12 @@ export class TerminalTab extends Disposable implements ITerminalTab { } this._terminalInstances.push(instance); this._initInstanceListeners(instance); - this._activeInstanceIndex = 0; - if (this._container) { - this.attachToElement(this._container); + if (this._splitPaneContainer) { + this._splitPaneContainer!.split(instance); } + + this._onInstancesChanged.fire(); } public dispose(): void { @@ -358,6 +372,10 @@ export class TerminalTab extends Disposable implements ITerminalTab { } public get title(): string { + if (!this.terminalInstances.length) { + return nls.localize('terminal.integrated.starting', "Starting..."); + } + let title = this.terminalInstances[0].title; for (let i = 1; i < this.terminalInstances.length; i++) { if (this.terminalInstances[i].title) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index ae87e42d990..8d9a84d5116 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -41,6 +41,7 @@ export class TerminalViewPane extends ViewPane { private _terminalContainer: HTMLElement | undefined; private _findWidget: TerminalFindWidget | undefined; private _splitTerminalAction: IAction | undefined; + private _terminalsInitialized = false; private _bodyDimensions: { width: number, height: number } = { width: 0, height: 0 }; constructor( @@ -109,14 +110,21 @@ export class TerminalViewPane extends ViewPane { this._register(this.onDidChangeBodyVisibility(visible => { if (visible) { - const hadTerminals = this._terminalService.terminalInstances.length > 0; - if (!hadTerminals) { - this._terminalService.createTerminal(); + const hadTerminals = !!this._terminalService.terminalTabs.length; + if (this._terminalsInitialized) { + if (!hadTerminals) { + this._terminalService.createTerminal(); + } + } else { + this._terminalsInitialized = true; + this._terminalService.initializeTerminals(); } + this._updateTheme(); if (hadTerminals) { this._terminalService.getActiveTab()?.setVisible(visible); } else { + // TODO@Tyriar - this call seems unnecessary this.layoutBody(this._bodyDimensions.height, this._bodyDimensions.width); } } else { diff --git a/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts b/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts index c559ff7b8db..3ae0210352d 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts @@ -26,6 +26,8 @@ export interface XTermCore { }; _onIntersectionChange: any; }; + + writeSync(data: string | Uint8Array): void; } export interface IEventEmitter { diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index c84ca3c0819..7ee9b719d9c 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -116,6 +116,21 @@ export interface ISendCommandResultToTerminalProcessArguments { payload: any; } +export interface IOrphanQuestionReplyArgs { + id: number; +} + +export interface IRemoteTerminalDescriptionDto { + id: number; + pid: number; + title: string; + cwd: string; +} + +export interface ITriggerTerminalDataReplayArguments { + id: number; +} + export interface IRemoteTerminalProcessReadyEvent { type: 'ready'; pid: number; @@ -126,11 +141,16 @@ export interface IRemoteTerminalProcessTitleChangedEvent { title: string; } export interface IRemoteTerminalProcessDataEvent { - type: 'data' + type: 'data'; data: string; } +export interface ReplayEntry { cols: number; rows: number; data: string; } +export interface IRemoteTerminalProcessReplayEvent { + type: 'replay'; + events: ReplayEntry[]; +} export interface IRemoteTerminalProcessExitEvent { - type: 'exit' + type: 'exit'; exitCode: number | undefined; } export interface IRemoteTerminalProcessExecCommandEvent { @@ -139,12 +159,17 @@ export interface IRemoteTerminalProcessExecCommandEvent { commandId: string; commandArgs: any[]; } +export interface IRemoteTerminalProcessOrphanQuestionEvent { + type: 'orphan?'; +} export type IRemoteTerminalProcessEvent = ( IRemoteTerminalProcessReadyEvent | IRemoteTerminalProcessTitleChangedEvent | IRemoteTerminalProcessDataEvent + | IRemoteTerminalProcessReplayEvent | IRemoteTerminalProcessExitEvent | IRemoteTerminalProcessExecCommandEvent + | IRemoteTerminalProcessOrphanQuestionEvent ); export interface IOnTerminalProcessEventArguments { @@ -166,7 +191,7 @@ export class RemoteTerminalChannelClient { ) { } - private _readSingleTemrinalConfiguration(key: string): ISingleTerminalConfiguration { + private _readSingleTerminalConfiguration(key: string): ISingleTerminalConfiguration { const result = this._configurationService.inspect(key); return { userValue: result.userValue, @@ -178,18 +203,18 @@ export class RemoteTerminalChannelClient { public async createTerminalProcess(shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { const terminalConfig = this._configurationService.getValue(TERMINAL_CONFIG_SECTION); const configuration: ICompleteTerminalConfiguration = { - 'terminal.integrated.automationShell.windows': this._readSingleTemrinalConfiguration('terminal.integrated.automationShell.windows'), - 'terminal.integrated.automationShell.osx': this._readSingleTemrinalConfiguration('terminal.integrated.automationShell.osx'), - 'terminal.integrated.automationShell.linux': this._readSingleTemrinalConfiguration('terminal.integrated.automationShell.linux'), - 'terminal.integrated.shell.windows': this._readSingleTemrinalConfiguration('terminal.integrated.shell.windows'), - 'terminal.integrated.shell.osx': this._readSingleTemrinalConfiguration('terminal.integrated.shell.osx'), - 'terminal.integrated.shell.linux': this._readSingleTemrinalConfiguration('terminal.integrated.shell.linux'), - 'terminal.integrated.shellArgs.windows': this._readSingleTemrinalConfiguration('terminal.integrated.shellArgs.windows'), - 'terminal.integrated.shellArgs.osx': this._readSingleTemrinalConfiguration('terminal.integrated.shellArgs.osx'), - 'terminal.integrated.shellArgs.linux': this._readSingleTemrinalConfiguration('terminal.integrated.shellArgs.linux'), - 'terminal.integrated.env.windows': this._readSingleTemrinalConfiguration('terminal.integrated.env.windows'), - 'terminal.integrated.env.osx': this._readSingleTemrinalConfiguration('terminal.integrated.env.osx'), - 'terminal.integrated.env.linux': this._readSingleTemrinalConfiguration('terminal.integrated.env.linux'), + 'terminal.integrated.automationShell.windows': this._readSingleTerminalConfiguration('terminal.integrated.automationShell.windows'), + 'terminal.integrated.automationShell.osx': this._readSingleTerminalConfiguration('terminal.integrated.automationShell.osx'), + 'terminal.integrated.automationShell.linux': this._readSingleTerminalConfiguration('terminal.integrated.automationShell.linux'), + 'terminal.integrated.shell.windows': this._readSingleTerminalConfiguration('terminal.integrated.shell.windows'), + 'terminal.integrated.shell.osx': this._readSingleTerminalConfiguration('terminal.integrated.shell.osx'), + 'terminal.integrated.shell.linux': this._readSingleTerminalConfiguration('terminal.integrated.shell.linux'), + 'terminal.integrated.shellArgs.windows': this._readSingleTerminalConfiguration('terminal.integrated.shellArgs.windows'), + 'terminal.integrated.shellArgs.osx': this._readSingleTerminalConfiguration('terminal.integrated.shellArgs.osx'), + 'terminal.integrated.shellArgs.linux': this._readSingleTerminalConfiguration('terminal.integrated.shellArgs.linux'), + 'terminal.integrated.env.windows': this._readSingleTerminalConfiguration('terminal.integrated.env.windows'), + 'terminal.integrated.env.osx': this._readSingleTerminalConfiguration('terminal.integrated.env.osx'), + 'terminal.integrated.env.linux': this._readSingleTerminalConfiguration('terminal.integrated.env.linux'), 'terminal.integrated.inheritEnv': terminalConfig.inheritEnv, 'terminal.integrated.cwd': terminalConfig.cwd, 'terminal.integrated.detectLocale': terminalConfig.detectLocale, @@ -306,4 +331,15 @@ export class RemoteTerminalChannelClient { }; return this._channel.call('$sendCommandResultToTerminalProcess', args); } + + public orphanQuestionReply(id: number): Promise { + const args: IOrphanQuestionReplyArgs = { + id + }; + return this._channel.call('$orphanQuestionReply', args); + } + + public listTerminals(): Promise { + return this._channel.call('$listTerminals'); + } } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index c8e63273704..83f5b682d17 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -165,6 +165,13 @@ export interface ITerminalEnvironment { [key: string]: string | null; } +export interface IRemoteTerminalAttachTarget { + id: number; + pid: number; + title: string; + cwd: string; +} + export interface IShellLaunchConfig { /** * The name of the terminal, if this is not set the name of the process will be used. @@ -217,6 +224,11 @@ export interface IShellLaunchConfig { */ isExtensionTerminal?: boolean; + /** + * This is a terminal that attaches to an already running remote terminal. + */ + remoteAttach?: { id: number; pid: number; title: string; cwd: string; }; + /** * Whether the terminal process environment should be exactly as provided in * `TerminalOptions.env`. When this is false (default), the environment will be based on the @@ -268,6 +280,13 @@ export interface ITerminalDimensions { readonly rows: number; } +export interface ITerminalDimensionsOverride extends ITerminalDimensions { + /** + * indicate that xterm must receive these exact dimensions, even if they overflow the ui! + */ + forceExactSize?: boolean; +} + export interface ICommandTracker { scrollToPreviousCommand(): void; scrollToNextCommand(): void; @@ -291,6 +310,11 @@ export interface IBeforeProcessDataEvent { data: string; } +export interface IProcessDataEvent { + data: string; + sync: boolean; +} + export interface ITerminalProcessManager extends IDisposable { readonly processState: ProcessState; readonly ptyProcessReady: Promise; @@ -302,10 +326,10 @@ export interface ITerminalProcessManager extends IDisposable { readonly onProcessReady: Event; readonly onBeforeProcessData: Event; - readonly onProcessData: Event; + readonly onProcessData: Event; readonly onProcessTitle: Event; readonly onProcessExit: Event; - readonly onProcessOverrideDimensions: Event; + readonly onProcessOverrideDimensions: Event; readonly onProcessResolvedShellLaunchConfig: Event; readonly onEnvironmentVariableInfoChanged: Event; @@ -416,11 +440,11 @@ export interface ITerminalLaunchError { * child_process.ChildProcess node.js interface. */ export interface ITerminalChildProcess { - onProcessData: Event; + onProcessData: Event; onProcessExit: Event; onProcessReady: Event<{ pid: number, cwd: string }>; onProcessTitleChanged: Event; - onProcessOverrideDimensions?: Event; + onProcessOverrideDimensions?: Event; onProcessResolvedShellLaunchConfig?: Event; /** diff --git a/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts b/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts index 82f3fe6f1d0..db8c7f57d9b 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts @@ -5,6 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { IProcessDataEvent } from 'vs/workbench/contrib/terminal/common/terminal'; interface TerminalDataBuffer extends IDisposable { data: string[]; @@ -23,19 +24,20 @@ export class TerminalDataBufferer implements IDisposable { } } - startBuffering(id: number, event: Event, throttleBy: number = 5): IDisposable { + startBuffering(id: number, event: Event, throttleBy: number = 5): IDisposable { let disposable: IDisposable; - disposable = event((e: string) => { + disposable = event((e: string | IProcessDataEvent) => { + const data = (typeof e === 'string' ? e : e.data); let buffer = this._terminalBufferMap.get(id); if (buffer) { - buffer.data.push(e); + buffer.data.push(data); return; } const timeoutId = setTimeout(() => this._flushBuffer(id), throttleBy); buffer = { - data: [e], + data: [data], timeoutId: timeoutId, dispose: () => { clearTimeout(timeoutId);