Add terminal selection API

Part of #188173
This commit is contained in:
Daniel Imms 2023-07-18 09:47:46 -07:00
parent c780d0dfef
commit 99e31ae297
No known key found for this signature in database
GPG key ID: 7116259D505CA628
9 changed files with 62 additions and 2 deletions

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { deepStrictEqual, doesNotThrow, equal, ok, strictEqual, throws } from 'assert';
import { ConfigurationTarget, Disposable, env, EnvironmentVariableCollection, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EnvironmentVariableScope, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode';
import { commands, ConfigurationTarget, Disposable, env, EnvironmentVariableCollection, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EnvironmentVariableScope, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode';
import { assertNoRpc, poll } from '../utils';
// Disable terminal tests:
@ -347,6 +347,27 @@ import { assertNoRpc, poll } from '../utils';
});
});
suite('selection', () => {
test('should be undefined immediately after creation', async () => {
const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
equal(terminal.selection, undefined);
terminal.dispose();
});
test('should be defined after selecting all content', async () => {
const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
await commands.executeCommand('workbench.action.terminal.selectAll');
// TODO: Need to poll?
terminal.dispose();
});
test('should be undefined after clearing a selection', async () => {
const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
await commands.executeCommand('workbench.action.terminal.selectAll');
// TODO: Need to poll?
await commands.executeCommand('workbench.action.terminal.clearSelection');
terminal.dispose();
});
});
suite('window.onDidWriteTerminalData', () => {
test('should listen to all future terminal data events', (done) => {
const openEvents: string[] = [];

View file

@ -85,6 +85,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._store.add(_terminalService.onDidChangeActiveInstance(instance => this._onActiveTerminalChanged(instance ? instance.instanceId : null)));
this._store.add(_terminalService.onDidChangeInstanceTitle(instance => instance && this._onTitleChanged(instance.instanceId, instance.title)));
this._store.add(_terminalService.onDidInputInstanceData(instance => this._proxy.$acceptTerminalInteraction(instance.instanceId)));
this._store.add(_terminalService.onDidChangeSelection(instance => this._proxy.$acceptTerminalSelection(instance.instanceId, instance.selection)));
// Set initial ext host state
for (const instance of this._terminalService.instances) {

View file

@ -2012,6 +2012,7 @@ export interface ExtHostTerminalServiceShape {
$acceptTerminalDimensions(id: number, cols: number, rows: number): void;
$acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void;
$acceptTerminalInteraction(id: number): void;
$acceptTerminalSelection(id: number, selection: string | undefined): void;
$startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined>;
$acceptProcessAckDataEvent(id: number, charCount: number): void;
$acceptProcessInput(id: number, data: string): void;

View file

@ -75,6 +75,7 @@ export class ExtHostTerminal {
private _rows: number | undefined;
private _exitStatus: vscode.TerminalExitStatus | undefined;
private _state: vscode.TerminalState = { isInteractedWith: false };
private _selection: string | undefined;
public isOpen: boolean = false;
@ -106,6 +107,9 @@ export class ExtHostTerminal {
get state(): vscode.TerminalState {
return that._state;
},
get selection(): string | undefined {
return that._selection;
},
sendText(text: string, addNewLine: boolean = true): void {
that._checkDisposed();
that._proxy.$sendText(that._id, text, addNewLine);
@ -233,6 +237,10 @@ export class ExtHostTerminal {
return false;
}
public setSelection(selection: string | undefined): void {
this._selection = selection;
}
public _setProcessId(processId: number | undefined): void {
// The event may fire 2 times when the panel is restored
if (this._pidPromiseComplete) {
@ -615,6 +623,10 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
}
}
public $acceptTerminalSelection(id: number, selection: string | undefined): void {
this._getTerminalById(id)?.setSelection(selection);
}
public $acceptProcessResize(id: number, cols: number, rows: number): void {
try {
this._terminalProcesses.get(id)?.resize(cols, rows);

View file

@ -176,6 +176,7 @@ export interface ITerminalService extends ITerminalInstanceHost {
readonly onDidChangeInstanceColor: Event<{ instance: ITerminalInstance; userInitiated: boolean }>;
readonly onDidChangeInstancePrimaryStatus: Event<ITerminalInstance>;
readonly onDidInputInstanceData: Event<ITerminalInstance>;
readonly onDidChangeSelection: Event<ITerminalInstance>;
readonly onDidRegisterProcessSupport: Event<void>;
readonly onDidChangeConnectionState: Event<void>;
@ -546,6 +547,7 @@ export interface ITerminalInstance {
onDidRequestFocus: Event<void>;
onDidBlur: Event<ITerminalInstance>;
onDidInputData: Event<ITerminalInstance>;
onDidChangeSelection: Event<ITerminalInstance>;
/**
* An event that fires when a terminal is dropped on this instance via drag and drop.

View file

@ -316,6 +316,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
readonly onDidBlur = this._onDidBlur.event;
private readonly _onDidInputData = this._register(new Emitter<ITerminalInstance>());
readonly onDidInputData = this._onDidInputData.event;
private readonly _onDidChangeSelection = this._register(new Emitter<ITerminalInstance>());
readonly onDidChangeSelection = this._onDidChangeSelection.event;
private readonly _onRequestAddInstanceToGroup = this._register(new Emitter<IRequestAddInstanceToGroupEvent>());
readonly onRequestAddInstanceToGroup = this._onRequestAddInstanceToGroup.event;
private readonly _onDidChangeHasChildProcesses = this._register(new Emitter<boolean>());
@ -1722,6 +1724,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
private async _onSelectionChange(): Promise<void> {
this._onDidChangeSelection.fire(this);
if (this._configurationService.getValue(TerminalSettingId.CopyOnSelection)) {
if (this.hasSelection()) {
await this.copySelection();

View file

@ -152,6 +152,8 @@ export class TerminalService implements ITerminalService {
get onDidChangeInstancePrimaryStatus(): Event<ITerminalInstance> { return this._onDidChangeInstancePrimaryStatus.event; }
private readonly _onDidInputInstanceData = new Emitter<ITerminalInstance>();
get onDidInputInstanceData(): Event<ITerminalInstance> { return this._onDidInputInstanceData.event; }
private readonly _onDidChangeSelection = new Emitter<ITerminalInstance>();
get onDidChangeSelection(): Event<ITerminalInstance> { return this._onDidChangeSelection.event; }
private readonly _onDidDisposeGroup = new Emitter<ITerminalGroup>();
get onDidDisposeGroup(): Event<ITerminalGroup> { return this._onDidDisposeGroup.event; }
private readonly _onDidChangeGroups = new Emitter<void>();
@ -836,7 +838,8 @@ export class TerminalService implements ITerminalService {
instance.onMaximumDimensionsChanged(() => this._onDidMaxiumumDimensionsChange.fire(instance)),
instance.onDidInputData(this._onDidInputInstanceData.fire, this._onDidInputInstanceData),
instance.onDidFocus(this._onDidChangeActiveInstance.fire, this._onDidChangeActiveInstance),
instance.onRequestAddInstanceToGroup(async e => await this._addInstanceToGroup(instance, e))
instance.onRequestAddInstanceToGroup(async e => await this._addInstanceToGroup(instance, e)),
instance.onDidChangeSelection(this._onDidChangeSelection.fire, this._onDidChangeSelection)
];
instance.onDisposed(() => dispose(instanceDisposables));
}

View file

@ -86,6 +86,7 @@ export const allApiProposals = Object.freeze({
terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts',
terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts',
terminalQuickFixProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts',
terminalSelection: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts',
testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts',
testInvalidateResults: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testInvalidateResults.d.ts',
testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts',

View file

@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
// https://github.com/microsoft/vscode/issues/188173
export interface Terminal {
/**
* The selected text of the terminal or undefined if there is no selection.
*/
readonly selection: string | undefined;
}
}