Skip to content

Commit

Permalink
Sync unicode version with persistent terminal processes
Browse files Browse the repository at this point in the history
Fixes #116113
  • Loading branch information
Tyriar committed Aug 17, 2021
1 parent f7e4c0a commit 3a90216
Show file tree
Hide file tree
Showing 19 changed files with 155 additions and 35 deletions.
8 changes: 8 additions & 0 deletions src/vs/platform/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export interface IPtyService {
cwd: string,
cols: number,
rows: number,
unicodeVersion: '6' | '11',
env: IProcessEnvironment,
executableEnv: IProcessEnvironment,
windowsEnableConpty: boolean,
Expand All @@ -223,6 +224,7 @@ export interface IPtyService {
getCwd(id: number): Promise<string>;
getLatency(id: number): Promise<number>;
acknowledgeDataEvent(id: number, charCount: number): Promise<void>;
setUnicodeVersion(id: number, version: '6' | '11'): Promise<void>;
processBinary(id: number, data: string): Promise<void>;
/** Confirm the process is _not_ an orphan. */
orphanQuestionReply(id: number): Promise<void>;
Expand Down Expand Up @@ -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<void>;

getInitialCwd(): Promise<string>;
getCwd(): Promise<string>;
getLatency(): Promise<number>;
Expand Down
9 changes: 7 additions & 2 deletions src/vs/platform/terminal/common/terminalRecorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export interface IRemoteTerminalProcessReplayEvent {
export interface ITerminalSerializer {
handleData(data: string): void;
handleResize(cols: number, rows: number): void;
generateReplayEvent(): IPtyHostProcessReplayEvent;
generateReplayEvent(): Promise<IPtyHostProcessReplayEvent>;
setUnicodeVersion?(version: '6' | '11'): void;
}

export class TerminalRecorder implements ITerminalSerializer {
Expand Down Expand Up @@ -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) {
Expand All @@ -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<IPtyHostProcessReplayEvent> {
return this.generateReplayEventSync();
}
}
7 changes: 5 additions & 2 deletions src/vs/platform/terminal/node/ptyHostService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number> {
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<number> {
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;
Expand Down Expand Up @@ -221,6 +221,9 @@ export class PtyHostService extends Disposable implements IPtyService {
acknowledgeDataEvent(id: number, charCount: number): Promise<void> {
return this._proxy.acknowledgeDataEvent(id, charCount);
}
setUnicodeVersion(id: number, version: '6' | '11'): Promise<void> {
return this._proxy.setUnicodeVersion(id, version);
}
getInitialCwd(id: number): Promise<string> {
return this._proxy.getInitialCwd(id);
}
Expand Down
66 changes: 58 additions & 8 deletions src/vs/platform/terminal/node/ptyService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ 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';
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;

Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -204,6 +209,9 @@ export class PtyService extends Disposable implements IPtyService {
async acknowledgeDataEvent(id: number, charCount: number): Promise<void> {
return this._throwIfNoPty(id).acknowledgeDataEvent(charCount);
}
async setUnicodeVersion(id: number, version: '6' | '11'): Promise<void> {
return this._throwIfNoPty(id).setUnicodeVersion(version);
}
async getLatency(id: number): Promise<number> {
return 0;
}
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -479,8 +493,8 @@ export class PersistentTerminalProcess extends Disposable {
return this._terminalProcess.getLatency();
}

triggerReplay(): void {
const ev = this._serializer.generateReplayEvent();
async triggerReplay(): Promise<void> {
const ev = await this._serializer.generateReplayEvent();
let dataLength = 0;
for (const e of ev.events) {
dataLength += e.data.length;
Expand Down Expand Up @@ -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 {
Expand All @@ -555,8 +577,8 @@ class XtermSerializer implements ITerminalSerializer {
this._xterm.resize(cols, rows);
}

generateReplayEvent(): IPtyHostProcessReplayEvent {
const serialize = new SerializeAddon();
async generateReplayEvent(): Promise<IPtyHostProcessReplayEvent> {
const serialize = new (await this._getSerializeConstructor());
this._xterm.loadAddon(serialize);
const serialized = serialize.serialize(this._xterm.getOption('scrollback'));
return {
Expand All @@ -569,6 +591,34 @@ class XtermSerializer implements ITerminalSerializer {
]
};
}

async setUnicodeVersion(version: '6' | '11'): Promise<void> {
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<typeof Unicode11Addon> {
if (!Unicode11Addon) {
Unicode11Addon = (await import('xterm-addon-unicode11')).Unicode11Addon;
}
return Unicode11Addon;
}

async _getSerializeConstructor(): Promise<typeof SerializeAddon> {
if (!SerializeAddon) {
SerializeAddon = (await import('xterm-addon-serialize')).SerializeAddon;
}
return SerializeAddon;
}
}

function printTime(ms: number): string {
Expand Down
4 changes: 4 additions & 0 deletions src/vs/platform/terminal/node/terminalProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,10 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
}

async setUnicodeVersion(version: '6' | '11'): Promise<void> {
// No-op
}

getInitialCwd(): Promise<string> {
return Promise.resolve(this._initialCwd);
}
Expand Down
20 changes: 10 additions & 10 deletions src/vs/platform/terminal/test/common/terminalRecorder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
]);
Expand Down
6 changes: 5 additions & 1 deletion src/vs/workbench/api/common/extHostTerminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,14 +263,18 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess {
}

async processBinary(data: string): Promise<void> {
// No-op, processBinary is not supported in extextion owned terminals.
// No-op, processBinary is not supported in extension owned terminals.
}

acknowledgeDataEvent(charCount: number): void {
// No-op, flow control is not supported in extension owned terminals. If this is ever
// implemented it will need new pause and resume VS Code APIs.
}

async setUnicodeVersion(version: '6' | '11'): Promise<void> {
// No-op, xterm-headless isn't used for extension owned terminals.
}

getInitialCwd(): Promise<string> {
return Promise.resolve('');
}
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/remotePty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export class RemotePty extends Disposable implements ITerminalChildProcess {
});
}

async setUnicodeVersion(version: '6' | '11'): Promise<void> {
return this._remoteTerminalChannel.setUnicodeVersion(this._id, version);
}

async getInitialCwd(): Promise<string> {
await this._startBarrier.wait();
return this._remoteTerminalChannel.getInitialCwd(this._id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<ITerminalChildProcess> {
async createProcess(shellLaunchConfig: IShellLaunchConfig, configuration: ICompleteTerminalConfiguration, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, unicodeVersion: '6' | '11', shouldPersist: boolean): Promise<ITerminalChildProcess> {
if (!this._remoteTerminalChannel) {
throw new Error(`Cannot create remote terminal when there is no remote!`);
}
Expand All @@ -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);
Expand Down
10 changes: 9 additions & 1 deletion src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ITerminalChildProcess>;
createProcess(
shellLaunchConfig: IShellLaunchConfig,
configuration: ICompleteTerminalConfiguration,
activeWorkspaceRootUri: URI | undefined,
cols: number,
rows: number,
unicodeVersion: '6' | '11',
shouldPersist: boolean
): Promise<ITerminalChildProcess>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
// Flow control is disabled for extension terminals
}

async setUnicodeVersion(version: '6' | '11'): Promise<void> {
// No-op
}

async processBinary(data: string): Promise<void> {
// Disabled for extension terminals
this._onBinary.fire(data);
Expand Down
Loading

0 comments on commit 3a90216

Please sign in to comment.