From 3a902162b48ef268f0da6a0280cbca9c322ef6c2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Aug 2021 07:06:13 -0700 Subject: [PATCH] Sync unicode version with persistent terminal processes Fixes #116113 --- src/vs/platform/terminal/common/terminal.ts | 8 +++ .../terminal/common/terminalRecorder.ts | 9 ++- .../platform/terminal/node/ptyHostService.ts | 7 +- src/vs/platform/terminal/node/ptyService.ts | 66 ++++++++++++++++--- .../platform/terminal/node/terminalProcess.ts | 4 ++ .../test/common/terminalRecorder.test.ts | 20 +++--- .../api/common/extHostTerminalService.ts | 6 +- .../contrib/terminal/browser/remotePty.ts | 4 ++ .../terminal/browser/remoteTerminalService.ts | 4 +- .../contrib/terminal/browser/terminal.ts | 10 ++- .../terminal/browser/terminalInstance.ts | 5 +- .../browser/terminalProcessExtHostProxy.ts | 4 ++ .../browser/terminalProcessManager.ts | 10 ++- .../terminal/common/remoteTerminalChannel.ts | 7 +- .../contrib/terminal/common/terminal.ts | 12 +++- .../terminal/electron-sandbox/localPty.ts | 3 + .../electron-sandbox/localTerminalService.ts | 4 +- .../browser/testingOutputTerminalService.ts | 4 ++ .../test/browser/workbenchTestServices.ts | 3 +- 19 files changed, 155 insertions(+), 35 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 11f01e129baca..a9338c15c97dd 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -200,6 +200,7 @@ export interface IPtyService { cwd: string, cols: number, rows: number, + unicodeVersion: '6' | '11', env: IProcessEnvironment, executableEnv: IProcessEnvironment, windowsEnableConpty: boolean, @@ -223,6 +224,7 @@ export interface IPtyService { getCwd(id: number): Promise; getLatency(id: number): Promise; acknowledgeDataEvent(id: number, charCount: number): Promise; + setUnicodeVersion(id: number, version: '6' | '11'): Promise; processBinary(id: number, data: string): Promise; /** Confirm the process is _not_ an orphan. */ orphanQuestionReply(id: number): Promise; @@ -485,6 +487,12 @@ export interface ITerminalChildProcess { */ acknowledgeDataEvent(charCount: number): void; + /** + * Sets the unicode version for the process, this drives the size of some characters in the + * xterm-headless instance. + */ + setUnicodeVersion(version: '6' | '11'): Promise; + getInitialCwd(): Promise; getCwd(): Promise; getLatency(): Promise; diff --git a/src/vs/platform/terminal/common/terminalRecorder.ts b/src/vs/platform/terminal/common/terminalRecorder.ts index d7a9fec77765e..0555a462a8946 100644 --- a/src/vs/platform/terminal/common/terminalRecorder.ts +++ b/src/vs/platform/terminal/common/terminalRecorder.ts @@ -20,7 +20,8 @@ export interface IRemoteTerminalProcessReplayEvent { export interface ITerminalSerializer { handleData(data: string): void; handleResize(cols: number, rows: number): void; - generateReplayEvent(): IPtyHostProcessReplayEvent; + generateReplayEvent(): Promise; + setUnicodeVersion?(version: '6' | '11'): void; } export class TerminalRecorder implements ITerminalSerializer { @@ -82,7 +83,7 @@ export class TerminalRecorder implements ITerminalSerializer { } } - generateReplayEvent(): IPtyHostProcessReplayEvent { + generateReplayEventSync(): IPtyHostProcessReplayEvent { // normalize entries to one element per data array this._entries.forEach((entry) => { if (entry.data.length > 0) { @@ -93,4 +94,8 @@ export class TerminalRecorder implements ITerminalSerializer { events: this._entries.map(entry => ({ cols: entry.cols, rows: entry.rows, data: entry.data[0] ?? '' })) }; } + + async generateReplayEvent(): Promise { + return this.generateReplayEventSync(); + } } diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index be87c41e30e6f..b2d12a0317337 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -178,9 +178,9 @@ export class PtyHostService extends Disposable implements IPtyService { super.dispose(); } - async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, executableEnv: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean, workspaceId: string, workspaceName: string): Promise { + async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, unicodeVersion: '6' | '11', env: IProcessEnvironment, executableEnv: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean, workspaceId: string, workspaceName: string): Promise { const timeout = setTimeout(() => this._handleUnresponsiveCreateProcess(), HeartbeatConstants.CreateProcessTimeout); - const id = await this._proxy.createProcess(shellLaunchConfig, cwd, cols, rows, env, executableEnv, windowsEnableConpty, shouldPersist, workspaceId, workspaceName); + const id = await this._proxy.createProcess(shellLaunchConfig, cwd, cols, rows, unicodeVersion, env, executableEnv, windowsEnableConpty, shouldPersist, workspaceId, workspaceName); clearTimeout(timeout); lastPtyId = Math.max(lastPtyId, id); return id; @@ -221,6 +221,9 @@ export class PtyHostService extends Disposable implements IPtyService { acknowledgeDataEvent(id: number, charCount: number): Promise { return this._proxy.acknowledgeDataEvent(id, charCount); } + setUnicodeVersion(id: number, version: '6' | '11'): Promise { + return this._proxy.setUnicodeVersion(id, version); + } getInitialCwd(id: number): Promise { return this._proxy.getInitialCwd(id); } diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 9915e61b5b858..9d4e1f542b5ab 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -16,7 +16,8 @@ import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanc import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; import { Terminal as XtermTerminal } from 'xterm-headless'; -import { SerializeAddon } from 'xterm-addon-serialize'; +import type { SerializeAddon as XtermSerializeAddon } from 'xterm-addon-serialize'; +import type { Unicode11Addon as XtermUnicode11Addon } from 'xterm-addon-unicode11'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto } from 'vs/platform/terminal/common/terminalProcess'; import { ITerminalSerializer, TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder'; import { getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment'; @@ -24,6 +25,9 @@ import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess'; type WorkspaceId = string; +let SerializeAddon: typeof XtermSerializeAddon; +let Unicode11Addon: typeof XtermUnicode11Addon; + export class PtyService extends Disposable implements IPtyService { declare readonly _serviceBrand: undefined; @@ -102,6 +106,7 @@ export class PtyService extends Disposable implements IPtyService { cwd: string, cols: number, rows: number, + unicodeVersion: '6' | '11', env: IProcessEnvironment, executableEnv: IProcessEnvironment, windowsEnableConpty: boolean, @@ -125,7 +130,7 @@ export class PtyService extends Disposable implements IPtyService { if (process.onDidChangeHasChildProcesses) { process.onDidChangeHasChildProcesses(event => this._onProcessDidChangeHasChildProcesses.fire({ id, event })); } - const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, this._reconnectConstants, this._logService, shellLaunchConfig.icon); + const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, unicodeVersion, this._reconnectConstants, this._logService, shellLaunchConfig.icon); process.onProcessExit(() => { persistentProcess.dispose(); this._ptys.delete(id); @@ -204,6 +209,9 @@ export class PtyService extends Disposable implements IPtyService { async acknowledgeDataEvent(id: number, charCount: number): Promise { return this._throwIfNoPty(id).acknowledgeDataEvent(charCount); } + async setUnicodeVersion(id: number, version: '6' | '11'): Promise { + return this._throwIfNoPty(id).setUnicodeVersion(version); + } async getLatency(id: number): Promise { return 0; } @@ -364,6 +372,7 @@ export class PersistentTerminalProcess extends Disposable { readonly shouldPersistTerminal: boolean, cols: number, rows: number, + unicodeVersion: '6' | '11', reconnectConstants: IReconnectConstants, private readonly _logService: ILogService, private _icon?: TerminalIcon, @@ -376,7 +385,8 @@ export class PersistentTerminalProcess extends Disposable { this._serializer = new XtermSerializer( cols, rows, - reconnectConstants.scrollback + reconnectConstants.scrollback, + unicodeVersion ); } else { this._serializer = new TerminalRecorder(cols, rows); @@ -463,6 +473,10 @@ export class PersistentTerminalProcess extends Disposable { this._bufferer.flushBuffer(this._persistentProcessId); return this._terminalProcess.resize(cols, rows); } + setUnicodeVersion(version: '6' | '11'): void { + this._serializer.setUnicodeVersion?.(version); + // TODO: Pass in unicode version in ctor + } acknowledgeDataEvent(charCount: number): void { if (this._inReplay) { return; @@ -479,8 +493,8 @@ export class PersistentTerminalProcess extends Disposable { return this._terminalProcess.getLatency(); } - triggerReplay(): void { - const ev = this._serializer.generateReplayEvent(); + async triggerReplay(): Promise { + const ev = await this._serializer.generateReplayEvent(); let dataLength = 0; for (const e of ev.events) { dataLength += e.data.length; @@ -543,8 +557,16 @@ export class PersistentTerminalProcess extends Disposable { class XtermSerializer implements ITerminalSerializer { private _xterm: XtermTerminal; - constructor(cols: number, rows: number, scrollback: number) { + private _unicodeAddon?: XtermUnicode11Addon; + + constructor( + cols: number, + rows: number, + scrollback: number, + unicodeVersion: '6' | '11' + ) { this._xterm = new XtermTerminal({ cols, rows, scrollback }); + this.setUnicodeVersion(unicodeVersion); } handleData(data: string): void { @@ -555,8 +577,8 @@ class XtermSerializer implements ITerminalSerializer { this._xterm.resize(cols, rows); } - generateReplayEvent(): IPtyHostProcessReplayEvent { - const serialize = new SerializeAddon(); + async generateReplayEvent(): Promise { + const serialize = new (await this._getSerializeConstructor()); this._xterm.loadAddon(serialize); const serialized = serialize.serialize(this._xterm.getOption('scrollback')); return { @@ -569,6 +591,34 @@ class XtermSerializer implements ITerminalSerializer { ] }; } + + async setUnicodeVersion(version: '6' | '11'): Promise { + if (this._xterm.unicode.activeVersion === version) { + return; + } + if (version === '11') { + this._unicodeAddon = new (await this._getUnicode11Constructor()); + this._xterm.loadAddon(this._unicodeAddon); + } else { + this._unicodeAddon?.dispose(); + this._unicodeAddon = undefined; + } + this._xterm.unicode.activeVersion = version; + } + + async _getUnicode11Constructor(): Promise { + if (!Unicode11Addon) { + Unicode11Addon = (await import('xterm-addon-unicode11')).Unicode11Addon; + } + return Unicode11Addon; + } + + async _getSerializeConstructor(): Promise { + if (!SerializeAddon) { + SerializeAddon = (await import('xterm-addon-serialize')).SerializeAddon; + } + return SerializeAddon; + } } function printTime(ms: number): string { diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 3b257295290e0..0347773855da0 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -470,6 +470,10 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } } + async setUnicodeVersion(version: '6' | '11'): Promise { + // No-op + } + getInitialCwd(): Promise { return Promise.resolve(this._initialCwd); } diff --git a/src/vs/platform/terminal/test/common/terminalRecorder.test.ts b/src/vs/platform/terminal/test/common/terminalRecorder.test.ts index 193428d2810db..9670c17e8c392 100644 --- a/src/vs/platform/terminal/test/common/terminalRecorder.test.ts +++ b/src/vs/platform/terminal/test/common/terminalRecorder.test.ts @@ -7,44 +7,44 @@ import * as assert from 'assert'; import { ReplayEntry } from 'vs/platform/terminal/common/terminalProcess'; import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder'; -function eventsEqual(recorder: TerminalRecorder, expected: ReplayEntry[]) { - const actual = recorder.generateReplayEvent().events; +async function eventsEqual(recorder: TerminalRecorder, expected: ReplayEntry[]) { + const actual = (await recorder.generateReplayEvent()).events; for (let i = 0; i < expected.length; i++) { assert.deepStrictEqual(actual[i], expected[i]); } } suite('TerminalRecorder', () => { - test('should record dimensions', () => { + test('should record dimensions', async () => { const recorder = new TerminalRecorder(1, 2); - eventsEqual(recorder, [ + await eventsEqual(recorder, [ { cols: 1, rows: 2, data: '' } ]); recorder.handleData('a'); recorder.handleResize(3, 4); - eventsEqual(recorder, [ + await eventsEqual(recorder, [ { cols: 1, rows: 2, data: 'a' }, { cols: 3, rows: 4, data: '' } ]); }); - test('should ignore resize events without data', () => { + test('should ignore resize events without data', async () => { const recorder = new TerminalRecorder(1, 2); - eventsEqual(recorder, [ + await eventsEqual(recorder, [ { cols: 1, rows: 2, data: '' } ]); recorder.handleResize(3, 4); - eventsEqual(recorder, [ + await eventsEqual(recorder, [ { cols: 3, rows: 4, data: '' } ]); }); - test('should record data and combine it into the previous resize event', () => { + test('should record data and combine it into the previous resize event', async () => { const recorder = new TerminalRecorder(1, 2); recorder.handleData('a'); recorder.handleData('b'); recorder.handleResize(3, 4); recorder.handleData('c'); recorder.handleData('d'); - eventsEqual(recorder, [ + await eventsEqual(recorder, [ { cols: 1, rows: 2, data: 'ab' }, { cols: 3, rows: 4, data: 'cd' } ]); diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 5fc90fbe530cd..01134e8ff14c4 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -263,7 +263,7 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { } async processBinary(data: string): Promise { - // No-op, processBinary is not supported in extextion owned terminals. + // No-op, processBinary is not supported in extension owned terminals. } acknowledgeDataEvent(charCount: number): void { @@ -271,6 +271,10 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { // implemented it will need new pause and resume VS Code APIs. } + async setUnicodeVersion(version: '6' | '11'): Promise { + // No-op, xterm-headless isn't used for extension owned terminals. + } + getInitialCwd(): Promise { return Promise.resolve(''); } diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index 96727f5c892c4..8d57bcc7bbf5b 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -112,6 +112,10 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { }); } + async setUnicodeVersion(version: '6' | '11'): Promise { + return this._remoteTerminalChannel.setUnicodeVersion(this._id, version); + } + async getInitialCwd(): Promise { await this._startBarrier.wait(); return this._remoteTerminalChannel.getInitialCwd(this._id); diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts index 018f2ad7a3c63..0ae07e4cfc7fc 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts @@ -22,7 +22,6 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { RemotePty } from 'vs/workbench/contrib/terminal/browser/remotePty'; import { IRemoteTerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ICompleteTerminalConfiguration, RemoteTerminalChannelClient, REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; -import { ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -173,7 +172,7 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal return this._remoteTerminalChannel.acceptDetachInstanceReply(requestId, persistentProcessId); } - async createProcess(shellLaunchConfig: IShellLaunchConfig, configuration: ICompleteTerminalConfiguration, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, shouldPersist: boolean, configHelper: ITerminalConfigHelper): Promise { + async createProcess(shellLaunchConfig: IShellLaunchConfig, configuration: ICompleteTerminalConfiguration, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, unicodeVersion: '6' | '11', shouldPersist: boolean): Promise { if (!this._remoteTerminalChannel) { throw new Error(`Cannot create remote terminal when there is no remote!`); } @@ -200,6 +199,7 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal shouldPersist, cols, rows, + unicodeVersion ); const pty = new RemotePty(result.persistentTerminalId, shouldPersist, this._remoteTerminalChannel, this._remoteAgentService, this._logService); this._ptys.set(result.persistentTerminalId, pty); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index cf02ed3a46396..f51867a263ecb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -320,7 +320,15 @@ export interface ITerminalFindHost { } export interface IRemoteTerminalService extends IOffProcessTerminalService { - createProcess(shellLaunchConfig: IShellLaunchConfig, configuration: ICompleteTerminalConfiguration, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, shouldPersist: boolean, configHelper: ITerminalConfigHelper): Promise; + createProcess( + shellLaunchConfig: IShellLaunchConfig, + configuration: ICompleteTerminalConfiguration, + activeWorkspaceRootUri: URI | undefined, + cols: number, + rows: number, + unicodeVersion: '6' | '11', + shouldPersist: boolean + ): Promise; } /** diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 4535432fc0937..f0bee88a4a6f2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1575,7 +1575,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._xtermUnicode11 = new Addon(); this._xterm.loadAddon(this._xtermUnicode11); } - this._xterm.unicode.activeVersion = this._configHelper.config.unicodeVersion; + if (this._xterm.unicode.activeVersion !== this._configHelper.config.unicodeVersion) { + this._xterm.unicode.activeVersion = this._configHelper.config.unicodeVersion; + this._processManager.setUnicodeVersion(this._configHelper.config.unicodeVersion); + } } updateAccessibilitySupport(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts index e191726ff13bd..7a0a05999e531 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts @@ -124,6 +124,10 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal // Flow control is disabled for extension terminals } + async setUnicodeVersion(version: '6' | '11'): Promise { + // No-op + } + async processBinary(data: string): Promise { // Disabled for extension terminals this._onBinary.fire(data); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index c3e5ecd52ff36..f535a5d2ebe96 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -267,7 +267,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce 'terminal.integrated.cwd': this._configurationService.getValue(TerminalSettingId.Cwd) as string, 'terminal.integrated.detectLocale': terminalConfig.detectLocale }; - newProcess = await this._remoteTerminalService.createProcess(shellLaunchConfig, configuration, activeWorkspaceRootUri, cols, rows, shouldPersist, this._configHelper); + newProcess = await this._remoteTerminalService.createProcess(shellLaunchConfig, configuration, activeWorkspaceRootUri, cols, rows, this._configHelper.config.unicodeVersion, shouldPersist); } if (!this._isDisposed) { this._setupPtyHostListeners(this._remoteTerminalService); @@ -430,7 +430,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled; const shouldPersist = this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isFeatureTerminal; - return await localTerminalService.createProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty, shouldPersist); + return await localTerminalService.createProcess(shellLaunchConfig, initialCwd, cols, rows, this._configHelper.config.unicodeVersion, env, useConpty, shouldPersist); } private _setupPtyHostListeners(offProcessTerminalService: IOffProcessTerminalService) { @@ -492,6 +492,10 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce return this.ptyProcessReady.then(() => this._resize(cols, rows)); } + async setUnicodeVersion(version: '6' | '11'): Promise { + return this._process?.setUnicodeVersion(version); + } + private _resize(cols: number, rows: number) { if (!this._process) { return; @@ -756,6 +760,6 @@ class SeamlessRelaunchDataFilter extends Disposable { } private _getDataFromRecorder(recorder: TerminalRecorder): string { - return recorder.generateReplayEvent().events.filter(e => !!e.data).map(e => e.data).join(''); + return recorder.generateReplayEventSync().events.filter(e => !!e.data).map(e => e.data).join(''); } } diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index 429bcadc2b175..f500e6f326554 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -62,6 +62,7 @@ export interface ICreateTerminalProcessArguments { shouldPersistTerminal: boolean; cols: number; rows: number; + unicodeVersion: '6' | '11'; resolverEnv: { [key: string]: string | null; } | undefined } @@ -141,7 +142,7 @@ export class RemoteTerminalChannelClient { return this._channel.call('$restartPtyHost', []); } - async createProcess(shellLaunchConfig: IShellLaunchConfigDto, configuration: ICompleteTerminalConfiguration, activeWorkspaceRootUri: URI | undefined, shouldPersistTerminal: boolean, cols: number, rows: number): Promise { + async createProcess(shellLaunchConfig: IShellLaunchConfigDto, configuration: ICompleteTerminalConfiguration, activeWorkspaceRootUri: URI | undefined, shouldPersistTerminal: boolean, cols: number, rows: number, unicodeVersion: '6' | '11'): Promise { // Be sure to first wait for the remote configuration await this._configurationService.whenRemoteConfigurationLoaded(); @@ -196,6 +197,7 @@ export class RemoteTerminalChannelClient { shouldPersistTerminal, cols, rows, + unicodeVersion, resolverEnv }; return await this._channel.call('$createProcess', args); @@ -231,6 +233,9 @@ export class RemoteTerminalChannelClient { acknowledgeDataEvent(id: number, charCount: number): Promise { return this._channel.call('$acknowledgeDataEvent', [id, charCount]); } + setUnicodeVersion(id: number, version: '6' | '11'): Promise { + return this._channel.call('$setUnicodeVersion', [id, version]); + } shutdown(id: number, immediate: boolean): Promise { return this._channel.call('$shutdown', [id, immediate]); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index d097e5a5fe485..0cb577cf6ef10 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -101,7 +101,16 @@ export interface IOffProcessTerminalService { export const ILocalTerminalService = createDecorator('localTerminalService'); export interface ILocalTerminalService extends IOffProcessTerminalService { - createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean): Promise; + createProcess( + shellLaunchConfig: IShellLaunchConfig, + cwd: string, + cols: number, + rows: number, + unicodeVersion: '6' | '11', + env: IProcessEnvironment, + windowsEnableConpty: boolean, + shouldPersist: boolean + ): Promise; } export type FontWeight = 'normal' | 'bold' | number; @@ -293,6 +302,7 @@ export interface ITerminalProcessManager extends IDisposable { setDimensions(cols: number, rows: number): Promise; setDimensions(cols: number, rows: number, sync: false): Promise; setDimensions(cols: number, rows: number, sync: true): void; + setUnicodeVersion(version: '6' | '11'): Promise; acknowledgeDataEvent(charCount: number): void; processBinary(data: string): void; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts index f810b739cc4eb..b33e6bd93ea85 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts @@ -86,6 +86,9 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { } this._localPtyService.acknowledgeDataEvent(this.id, charCount); } + setUnicodeVersion(version: '6' | '11'): Promise { + return this._localPtyService.setUnicodeVersion(this.id, version); + } handleData(e: string | IProcessDataEvent) { this._onProcessData.fire(e); diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts index ae219c309f211..fc155cf9aa0f8 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts @@ -146,9 +146,9 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe await this._localPtyService.updateIcon(id, icon, color); } - async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean): Promise { + async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, unicodeVersion: '6' | '11', env: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean): Promise { const executableEnv = await this._shellEnvironmentService.getShellEnv(); - const id = await this._localPtyService.createProcess(shellLaunchConfig, cwd, cols, rows, env, executableEnv, windowsEnableConpty, shouldPersist, this._getWorkspaceId(), this._getWorkspaceName()); + const id = await this._localPtyService.createProcess(shellLaunchConfig, cwd, cols, rows, unicodeVersion, env, executableEnv, windowsEnableConpty, shouldPersist, this._getWorkspaceId(), this._getWorkspaceName()); const pty = this._instantiationService.createInstance(LocalPty, id, shouldPersist); this._ptys.set(id, pty); return pty; diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts b/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts index d9dbf8a112f5f..f4d8f777b4f77 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputTerminalService.ts @@ -203,6 +203,10 @@ class TestOutputProcess extends Disposable implements ITerminalChildProcess { public acknowledgeDataEvent(): void { // no-op, flow control not currently implemented } + public setUnicodeVersion(): Promise { + // no-op + return Promise.resolve(); + } public getInitialCwd(): Promise { return Promise.resolve(''); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 3bacc2c983711..c17d89e7e668a 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1667,7 +1667,7 @@ export class TestLocalTerminalService implements ILocalTerminalService { onDidMoveWindowInstance = Event.None; onDidRequestDetach = Event.None; - async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean): Promise { return new TestTerminalChildProcess(shouldPersist); } + async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, unicodeVersion: '6' | '11', env: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean): Promise { return new TestTerminalChildProcess(shouldPersist); } async attachToProcess(id: number): Promise { throw new Error('Method not implemented.'); } async listProcesses(): Promise { throw new Error('Method not implemented.'); } getDefaultSystemShell(osOverride?: OperatingSystem): Promise { throw new Error('Method not implemented.'); } @@ -1703,6 +1703,7 @@ class TestTerminalChildProcess implements ITerminalChildProcess { input(data: string): void { } resize(cols: number, rows: number): void { } acknowledgeDataEvent(charCount: number): void { } + async setUnicodeVersion(version: '6' | '11'): Promise { } async getInitialCwd(): Promise { return ''; } async getCwd(): Promise { return ''; } async getLatency(): Promise { return 0; }