diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 1888982a193..167eb17fc8e 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; @@ -16,6 +16,7 @@ import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/termi import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; import { ILogService } from 'vs/platform/log/common/log'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -46,6 +47,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @ILogService private readonly _logService: ILogService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); @@ -259,7 +261,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape executable: request.shellLaunchConfig.executable, args: request.shellLaunchConfig.args, cwd: request.shellLaunchConfig.cwd, - env: request.shellLaunchConfig.env + env: request.shellLaunchConfig.env, + flowControl: this._configurationService.getValue(TERMINAL_CONFIG_SECTION).flowControl }; this._logService.trace('Spawning ext host process', { terminalId: proxy.terminalId, shellLaunchConfigDto, request }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d3a5b973021..6777ffa6cea 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1506,6 +1506,7 @@ export interface IShellLaunchConfigDto { cwd?: string | UriComponents; env?: { [key: string]: string | null; }; hideFromUser?: boolean; + flowControl?: boolean; } export interface IShellDefinitionDto { diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 127814dbb0a..adad271b38f 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -149,7 +149,8 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { executable: shellLaunchConfigDto.executable, args: shellLaunchConfigDto.args, cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd), - env: shellLaunchConfigDto.env + env: shellLaunchConfigDto.env, + flowControl: shellLaunchConfigDto.flowControl }; // Merge in shell and args from settings diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 83f70c934b7..59eee00f4d9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1022,9 +1022,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const messageId = ++this._latestXtermWriteData; this._xterm?.write(ev.data, () => { this._latestXtermParseData = messageId; - // TODO: Disable for local processes? - // TODO: We don't need to ack everything, just count on the other side and ack every 1000/10000 bytes - this._processManager.acknowledgeDataEvent(ev.data.length); + if (this._shellLaunchConfig.flowControl) { + this._processManager.acknowledgeDataEvent(ev.data.length); + } }); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index b783fec1988..1fe02f4fed4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -133,7 +133,10 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce rows: number, isScreenReaderModeEnabled: boolean ): Promise { + shellLaunchConfig.flowControl = this._configHelper.config.flowControl; if (shellLaunchConfig.isExtensionTerminal) { + // Flow control is not supported for extension terminals + shellLaunchConfig.flowControl = false; this._processType = ProcessType.ExtensionTerminal; this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, undefined, cols, rows, this._configHelper); } else { @@ -169,7 +172,10 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper); } } else { - this._process = await this._launchProcess(shellLaunchConfig, cols, rows, this.userHome, isScreenReaderModeEnabled); + // Flow control is not needed for ptys hosted in the same process (ie. the electron + // renderer). + shellLaunchConfig.flowControl = false; + this._process = await this._launchLocalProcess(shellLaunchConfig, cols, rows, this.userHome, isScreenReaderModeEnabled); } } @@ -223,7 +229,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce return undefined; } - private async _launchProcess( + private async _launchLocalProcess( shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 78f4ef2a552..40e4f9f8963 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -138,6 +138,7 @@ export interface ITerminalConfiguration { localEchoStyle: 'bold' | 'dim' | 'italic' | 'underlined' | 'inverted' | string; serverSpawn: boolean; enablePersistentSessions: boolean; + flowControl: boolean; } export const DEFAULT_LOCAL_ECHO_EXCLUDE: ReadonlyArray = ['vim', 'vi', 'nano', 'tmux']; @@ -287,6 +288,11 @@ export interface IShellLaunchConfig { * a terminal used to drive some VS Code feature. */ isFeatureTerminal?: boolean; + + /** + * Whether flow control is enabled for this terminal. + */ + flowControl?: boolean; } /** diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index d2fe5d11fec..23ad043b2b2 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -397,6 +397,11 @@ export const terminalConfiguration: IConfigurationNode = { description: localize('terminal.integrated.enablePersistentSessions', "Experimental: persist terminal sessions for the workspace across window reloads. Currently only supported in VS Code Remote workspaces."), type: 'boolean', default: true + }, + 'terminal.integrated.flowControl': { + description: localize('terminal.integrated.flowControl', "Experimental: whether to enable flow control which will slow the program on the remote side to avoid flooding remote connections with terminal output. This setting has no effect for local terminals and terminals where the output/input is controlled by an extension. Changing this will only affect new terminals."), + type: 'boolean', + default: false } } }; diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 26c3c1f429e..04e1cbc797c 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -167,17 +167,16 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this.onProcessReady(() => c()); }); ptyProcess.onData(data => { - const fakeLatency = 1000; - this._unacknowledgedCharCount += data.length; - setTimeout(() => { - this._onProcessData.fire(data); - }, fakeLatency); - if (!this._isPtyPaused && this._unacknowledgedCharCount > FlowControlConstants.HighWatermarkChars) { - console.log(`pause (${this._unacknowledgedCharCount} > ${FlowControlConstants.HighWatermarkChars}`); - this._isPtyPaused = true; - // TODO: Expose as public API in node-pty - (ptyProcess as any).pause(); + if (this._shellLaunchConfig.flowControl) { + this._unacknowledgedCharCount += data.length; + if (!this._isPtyPaused && this._unacknowledgedCharCount > FlowControlConstants.HighWatermarkChars) { + console.log(`pause (${this._unacknowledgedCharCount} > ${FlowControlConstants.HighWatermarkChars}`); + this._isPtyPaused = true; + // TODO: Expose as public API in node-pty + (ptyProcess as any).pause(); + } } + this._onProcessData.fire(data); if (this._closeTimeout) { clearTimeout(this._closeTimeout); this._queueProcessExit(); @@ -340,6 +339,9 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } public acknowledgeDataEvent(charCount: number): void { + if (!this._shellLaunchConfig.flowControl) { + return; + } // Prevent lower than 0 to heal from errors this._unacknowledgedCharCount = Math.max(this._unacknowledgedCharCount - charCount, 0); console.log(`Ack ${charCount} chars (unacknowledged: ${this._unacknowledgedCharCount})`);