Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Terminal.state proposed API and associated event #130726

Merged
merged 7 commits into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { deepEqual, doesNotThrow, equal, strictEqual, throws } from 'assert';
import { ConfigurationTarget, Disposable, env, EnvironmentVariableMutator, EnvironmentVariableMutatorType, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalOptions, UIKind, window, workspace } from 'vscode';
import { deepEqual, deepStrictEqual, doesNotThrow, equal, strictEqual, throws } from 'assert';
import { ConfigurationTarget, Disposable, env, EnvironmentVariableMutator, EnvironmentVariableMutatorType, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalOptions, TerminalState, UIKind, window, workspace } from 'vscode';
import { assertNoRpc } from '../utils';

// Disable terminal tests:
Expand Down Expand Up @@ -228,6 +228,29 @@ import { assertNoRpc } from '../utils';
});
});

test('onDidChangeTerminalState should fire after writing to a terminal', async () => {
const terminal = window.createTerminal();
deepStrictEqual(terminal.state, { interactedWith: false });
const eventState = await new Promise<TerminalState>(r => {
disposables.push(window.onDidChangeTerminalState(e => {
if (e.terminal === terminal) {
r(e.state);
}
}));
terminal.sendText('test');
});
deepStrictEqual(eventState, { interactedWith: true });
deepStrictEqual(terminal.state, { interactedWith: true });
await new Promise<void>(r => {
disposables.push(window.onDidCloseTerminal(t => {
if (t === terminal) {
r();
}
}));
terminal.dispose();
});
});

// test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => {
// const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
// equal(active, terminal);
Expand Down
53 changes: 53 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,59 @@ declare module 'vscode' {

//#endregion

//#region Terminal state event https://github.com/microsoft/vscode/issues/127717

/**
* Represents the state of a {@link Terminal}.
*/
export interface TerminalState {
/**
* Whether the {@link Terminal} has been interacted with. Interaction means that the
* terminal has sent data to the process which depending on the terminal's _mode_. By
* default input is sent when a key is pressed but based on the terminal's mode it can also
* happen on:
*
* - a pointer click event
* - a pointer scroll event
* - a pointer move event
* - terminal focus in/out
*
* For more information on events that can send data see "DEC Private Mode Set (DECSET)" on
* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
*/
readonly interactedWith: boolean;
}

export interface Terminal {
/**
* The current state of the {@link Terminal}.
*/
readonly state: TerminalState;
}

/**
* An event representing a change in a {@link Terminal.state terminal's state}.
*/
export interface TerminalStateChangeEvent {
/**
* The {@link Terminal} this event occurred on.
*/
readonly terminal: Terminal;
/**
* The {@link Terminal.state current state} of the {@link Terminal}.
*/
readonly state: TerminalState;
}

export namespace window {
/**
* An {@link Event} which fires when a {@link Terminal.state terminal's state} has changed.
*/
export const onDidChangeTerminalState: Event<TerminalStateChangeEvent>;
}

//#endregion

//#region Terminal name change event https://github.com/microsoft/vscode/issues/114898

export interface Pseudoterminal {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadTerminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._toDispose.add(_terminalService.onDidRequestStartExtensionTerminal(e => this._onRequestStartExtensionTerminal(e)));
this._toDispose.add(_terminalService.onDidChangeActiveInstance(instance => this._onActiveTerminalChanged(instance ? instance.instanceId : null)));
this._toDispose.add(_terminalService.onDidChangeInstanceTitle(instance => instance && this._onTitleChanged(instance.instanceId, instance.title)));
this._toDispose.add(_terminalService.onDidInputInstanceData(instance => this._proxy.$acceptTerminalInteraction(instance.instanceId)));

// Set initial ext host state
this._terminalService.instances.forEach(t => {
Expand Down Expand Up @@ -274,7 +275,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._proxy.$acceptTerminalMaximumDimensions(instance.instanceId, instance.maxCols, instance.maxRows);
}


private _onRequestStartExtensionTerminal(request: IStartExtensionTerminalRequest): void {
const proxy = request.proxy;
this._terminalProcessProxies.set(proxy.instanceId, proxy);
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostTerminalService.onDidChangeTerminalDimensions(listener, thisArg, disposables);
},
onDidChangeTerminalState(listener, thisArg?, disposables?) {
checkProposedApiEnabled(extension);
return extHostTerminalService.onDidChangeTerminalState(listener, thisArg, disposables);
},
onDidWriteTerminalData(listener, thisArg?, disposables?) {
checkProposedApiEnabled(extension);
return extHostTerminalService.onDidWriteTerminalData(listener, thisArg, disposables);
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,7 @@ export interface ExtHostTerminalServiceShape {
$acceptTerminalTitleChange(id: number, name: string): void;
$acceptTerminalDimensions(id: number, cols: number, rows: number): void;
$acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void;
$acceptTerminalInteraction(id: number): void;
$startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined>;
$acceptProcessAckDataEvent(id: number, charCount: number): void;
$acceptProcessInput(id: number, data: string): void;
Expand Down
43 changes: 34 additions & 9 deletions src/vs/workbench/api/common/extHostTerminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID
onDidOpenTerminal: Event<vscode.Terminal>;
onDidChangeActiveTerminal: Event<vscode.Terminal | undefined>;
onDidChangeTerminalDimensions: Event<vscode.TerminalDimensionsChangeEvent>;
onDidChangeTerminalState: Event<vscode.TerminalStateChangeEvent>;
onDidWriteTerminalData: Event<vscode.TerminalDataWriteEvent>;

createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal;
Expand Down Expand Up @@ -63,6 +64,7 @@ export class ExtHostTerminal {
private _pidPromiseComplete: ((value: number | undefined) => any) | undefined;
private _rows: number | undefined;
private _exitStatus: vscode.TerminalExitStatus | undefined;
private _state: vscode.TerminalState = { interactedWith: false };

public isOpen: boolean = false;

Expand Down Expand Up @@ -91,6 +93,9 @@ export class ExtHostTerminal {
get exitStatus(): vscode.TerminalExitStatus | undefined {
return that._exitStatus;
},
get state(): vscode.TerminalState {
return that._state;
},
sendText(text: string, addNewLine: boolean = true): void {
that._checkDisposed();
that._proxy.$sendText(that._id, text, addNewLine);
Expand Down Expand Up @@ -193,6 +198,14 @@ export class ExtHostTerminal {
return true;
}

public setInteractedWith(): boolean {
if (!this._state.interactedWith) {
this._state = { interactedWith: true };
return true;
}
return false;
}

public _setProcessId(processId: number | undefined): void {
// The event may fire 2 times when the panel is restored
if (this._pidPromiseComplete) {
Expand Down Expand Up @@ -326,16 +339,18 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
public get activeTerminal(): vscode.Terminal | undefined { return this._activeTerminal?.value; }
public get terminals(): vscode.Terminal[] { return this._terminals.map(term => term.value); }

protected readonly _onDidCloseTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidCloseTerminal(): Event<vscode.Terminal> { return this._onDidCloseTerminal && this._onDidCloseTerminal.event; }
protected readonly _onDidOpenTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidOpenTerminal(): Event<vscode.Terminal> { return this._onDidOpenTerminal && this._onDidOpenTerminal.event; }
protected readonly _onDidChangeActiveTerminal: Emitter<vscode.Terminal | undefined> = new Emitter<vscode.Terminal | undefined>();
public get onDidChangeActiveTerminal(): Event<vscode.Terminal | undefined> { return this._onDidChangeActiveTerminal && this._onDidChangeActiveTerminal.event; }
protected readonly _onDidChangeTerminalDimensions: Emitter<vscode.TerminalDimensionsChangeEvent> = new Emitter<vscode.TerminalDimensionsChangeEvent>();
public get onDidChangeTerminalDimensions(): Event<vscode.TerminalDimensionsChangeEvent> { return this._onDidChangeTerminalDimensions && this._onDidChangeTerminalDimensions.event; }
protected readonly _onDidCloseTerminal = new Emitter<vscode.Terminal>();
readonly onDidCloseTerminal = this._onDidCloseTerminal.event;
protected readonly _onDidOpenTerminal = new Emitter<vscode.Terminal>();
readonly onDidOpenTerminal = this._onDidOpenTerminal.event;
protected readonly _onDidChangeActiveTerminal = new Emitter<vscode.Terminal | undefined>();
readonly onDidChangeActiveTerminal = this._onDidChangeActiveTerminal.event;
protected readonly _onDidChangeTerminalDimensions = new Emitter<vscode.TerminalDimensionsChangeEvent>();
readonly onDidChangeTerminalDimensions = this._onDidChangeTerminalDimensions.event;
protected readonly _onDidChangeTerminalState = new Emitter<vscode.TerminalStateChangeEvent>();
readonly onDidChangeTerminalState = this._onDidChangeTerminalState.event;
protected readonly _onDidWriteTerminalData: Emitter<vscode.TerminalDataWriteEvent>;
public get onDidWriteTerminalData(): Event<vscode.TerminalDataWriteEvent> { return this._onDidWriteTerminalData && this._onDidWriteTerminalData.event; }
get onDidWriteTerminalData(): Event<vscode.TerminalDataWriteEvent> { return this._onDidWriteTerminalData.event; }

constructor(
supportsProcesses: boolean,
Expand Down Expand Up @@ -549,6 +564,16 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
this._terminalProcesses.get(id)?.input(data);
}

public $acceptTerminalInteraction(id: number): void {
const terminal = this._getTerminalById(id);
if (terminal?.setInteractedWith()) {
this._onDidChangeTerminalState.fire({
terminal: terminal.value,
state: terminal.value.state
});
}
}

public $acceptProcessResize(id: number, cols: number, rows: number): void {
try {
this._terminalProcesses.get(id)?.resize(cols, rows);
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export interface ITerminalService extends ITerminalInstanceHost {
onDidChangeInstanceIcon: Event<ITerminalInstance | undefined>;
onDidChangeInstanceColor: Event<ITerminalInstance | undefined>;
onDidChangeInstancePrimaryStatus: Event<ITerminalInstance>;
onDidInputInstanceData: Event<ITerminalInstance>;
onDidRegisterProcessSupport: Event<void>;
onDidChangeConnectionState: Event<void>;
onDidChangeAvailableProfiles: Event<ITerminalProfile[]>;
Expand Down Expand Up @@ -450,6 +451,7 @@ export interface ITerminalInstance {

onDidFocus: Event<ITerminalInstance>;
onDidBlur: Event<ITerminalInstance>;
onDidInputData: Event<ITerminalInstance>;

/**
* An event that fires when a terminal is dropped on this instance via drag and drop.
Expand Down
10 changes: 8 additions & 2 deletions src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
readonly onDidFocus = this._onDidFocus.event;
private readonly _onDidBlur = this._register(new Emitter<ITerminalInstance>());
readonly onDidBlur = this._onDidBlur.event;
private readonly _onDidInputData = this._register(new Emitter<ITerminalInstance>());
readonly onDidInputData = this._onDidInputData.event;
private readonly _onRequestAddInstanceToGroup = this._register(new Emitter<IRequestAddInstanceToGroupEvent>());
readonly onRequestAddInstanceToGroup = this._onRequestAddInstanceToGroup.event;
private readonly _onDidChangeHasChildProcesses = this._register(new Emitter<boolean>());
Expand Down Expand Up @@ -623,7 +625,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._xterm.buffer.onBufferChange(() => this._refreshAltBufferContextKey());

this._processManager.onProcessData(e => this._onProcessData(e));
this._xterm.onData(data => this._processManager.write(data));
this._xterm.onData(async data => {
await this._processManager.write(data);
this._onDidInputData.fire(this);
});
this._xterm.onBinary(data => this._processManager.processBinary(data));
this.processReady.then(async () => {
if (this._linkManager) {
Expand Down Expand Up @@ -1077,7 +1082,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}

// Send it to the process
return this._processManager.write(text);
await this._processManager.write(text);
this._onDidInputData.fire(this);
}

setVisible(visible: boolean): void {
Expand Down
3 changes: 3 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ export class TerminalService implements ITerminalService {
get onDidChangeActiveInstance(): Event<ITerminalInstance | undefined> { return this._onDidChangeActiveInstance.event; }
private readonly _onDidChangeInstancePrimaryStatus = new Emitter<ITerminalInstance>();
get onDidChangeInstancePrimaryStatus(): Event<ITerminalInstance> { return this._onDidChangeInstancePrimaryStatus.event; }
private readonly _onDidInputInstanceData = new Emitter<ITerminalInstance>();
get onDidInputInstanceData(): Event<ITerminalInstance> { return this._onDidInputInstanceData.event; }
private readonly _onDidDisposeGroup = new Emitter<ITerminalGroup>();
get onDidDisposeGroup(): Event<ITerminalGroup> { return this._onDidDisposeGroup.event; }
private readonly _onDidChangeGroups = new Emitter<void>();
Expand Down Expand Up @@ -719,6 +721,7 @@ export class TerminalService implements ITerminalService {
}
}));
instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onDidMaxiumumDimensionsChange.fire(instance)));
instance.addDisposable(instance.onDidInputData(this._onDidInputInstanceData.fire, this._onDidInputInstanceData));
instance.addDisposable(instance.onDidFocus(this._onDidChangeActiveInstance.fire, this._onDidChangeActiveInstance));
instance.addDisposable(instance.onRequestAddInstanceToGroup(async e => await this._addInstanceToGroup(instance, e)));
}
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export interface ITerminalProcessManager extends IDisposable {
detachFromProcess(): Promise<void>;
createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise<ITerminalLaunchError | undefined>;
relaunch(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean, reset: boolean): Promise<ITerminalLaunchError | undefined>;
write(data: string): void;
write(data: string): Promise<void>;
setDimensions(cols: number, rows: number): Promise<void>;
setDimensions(cols: number, rows: number, sync: false): Promise<void>;
setDimensions(cols: number, rows: number, sync: true): void;
Expand Down