Skip to content

Commit f503e53

Browse files
authored
Merge pull request microsoft#188178 from microsoft/tyriar/188173_term_selection
Add terminal selection API
2 parents 6ae441b + 31b9e9a commit f503e53

File tree

9 files changed

+80
-2
lines changed

9 files changed

+80
-2
lines changed

extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { deepStrictEqual, doesNotThrow, equal, ok, strictEqual, throws } from 'assert';
7-
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';
7+
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';
88
import { assertNoRpc, poll } from '../utils';
99

1010
// Disable terminal tests:
@@ -347,6 +347,45 @@ import { assertNoRpc, poll } from '../utils';
347347
});
348348
});
349349

350+
suite('selection', () => {
351+
test('should be undefined immediately after creation', async () => {
352+
const terminal = window.createTerminal({ name: 'selection test' });
353+
terminal.show();
354+
equal(terminal.selection, undefined);
355+
terminal.dispose();
356+
});
357+
test('should be defined after selecting all content', async () => {
358+
const terminal = window.createTerminal({ name: 'selection test' });
359+
terminal.show();
360+
// Wait for some terminal data
361+
await new Promise<void>(r => {
362+
const disposable = window.onDidWriteTerminalData(() => {
363+
disposable.dispose();
364+
r();
365+
});
366+
});
367+
await commands.executeCommand('workbench.action.terminal.selectAll');
368+
await poll<void>(() => Promise.resolve(), () => terminal.selection !== undefined, 'selection should be defined');
369+
terminal.dispose();
370+
});
371+
test('should be undefined after clearing a selection', async () => {
372+
const terminal = window.createTerminal({ name: 'selection test' });
373+
terminal.show();
374+
// Wait for some terminal data
375+
await new Promise<void>(r => {
376+
const disposable = window.onDidWriteTerminalData(() => {
377+
disposable.dispose();
378+
r();
379+
});
380+
});
381+
await commands.executeCommand('workbench.action.terminal.selectAll');
382+
await poll<void>(() => Promise.resolve(), () => terminal.selection !== undefined, 'selection should be defined');
383+
await commands.executeCommand('workbench.action.terminal.clearSelection');
384+
await poll<void>(() => Promise.resolve(), () => terminal.selection === undefined, 'selection should not be defined');
385+
terminal.dispose();
386+
});
387+
});
388+
350389
suite('window.onDidWriteTerminalData', () => {
351390
test('should listen to all future terminal data events', (done) => {
352391
const openEvents: string[] = [];

src/vs/workbench/api/browser/mainThreadTerminalService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
8585
this._store.add(_terminalService.onDidChangeActiveInstance(instance => this._onActiveTerminalChanged(instance ? instance.instanceId : null)));
8686
this._store.add(_terminalService.onDidChangeInstanceTitle(instance => instance && this._onTitleChanged(instance.instanceId, instance.title)));
8787
this._store.add(_terminalService.onDidInputInstanceData(instance => this._proxy.$acceptTerminalInteraction(instance.instanceId)));
88+
this._store.add(_terminalService.onDidChangeSelection(instance => this._proxy.$acceptTerminalSelection(instance.instanceId, instance.selection)));
8889

8990
// Set initial ext host state
9091
for (const instance of this._terminalService.instances) {

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2012,6 +2012,7 @@ export interface ExtHostTerminalServiceShape {
20122012
$acceptTerminalDimensions(id: number, cols: number, rows: number): void;
20132013
$acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void;
20142014
$acceptTerminalInteraction(id: number): void;
2015+
$acceptTerminalSelection(id: number, selection: string | undefined): void;
20152016
$startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise<ITerminalLaunchError | undefined>;
20162017
$acceptProcessAckDataEvent(id: number, charCount: number): void;
20172018
$acceptProcessInput(id: number, data: string): void;

src/vs/workbench/api/common/extHostTerminalService.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export class ExtHostTerminal {
7575
private _rows: number | undefined;
7676
private _exitStatus: vscode.TerminalExitStatus | undefined;
7777
private _state: vscode.TerminalState = { isInteractedWith: false };
78+
private _selection: string | undefined;
7879

7980
public isOpen: boolean = false;
8081

@@ -106,6 +107,9 @@ export class ExtHostTerminal {
106107
get state(): vscode.TerminalState {
107108
return that._state;
108109
},
110+
get selection(): string | undefined {
111+
return that._selection;
112+
},
109113
sendText(text: string, addNewLine: boolean = true): void {
110114
that._checkDisposed();
111115
that._proxy.$sendText(that._id, text, addNewLine);
@@ -233,6 +237,10 @@ export class ExtHostTerminal {
233237
return false;
234238
}
235239

240+
public setSelection(selection: string | undefined): void {
241+
this._selection = selection;
242+
}
243+
236244
public _setProcessId(processId: number | undefined): void {
237245
// The event may fire 2 times when the panel is restored
238246
if (this._pidPromiseComplete) {
@@ -615,6 +623,10 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
615623
}
616624
}
617625

626+
public $acceptTerminalSelection(id: number, selection: string | undefined): void {
627+
this._getTerminalById(id)?.setSelection(selection);
628+
}
629+
618630
public $acceptProcessResize(id: number, cols: number, rows: number): void {
619631
try {
620632
this._terminalProcesses.get(id)?.resize(cols, rows);

src/vs/workbench/contrib/terminal/browser/terminal.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export interface ITerminalService extends ITerminalInstanceHost {
176176
readonly onDidChangeInstanceColor: Event<{ instance: ITerminalInstance; userInitiated: boolean }>;
177177
readonly onDidChangeInstancePrimaryStatus: Event<ITerminalInstance>;
178178
readonly onDidInputInstanceData: Event<ITerminalInstance>;
179+
readonly onDidChangeSelection: Event<ITerminalInstance>;
179180
readonly onDidRegisterProcessSupport: Event<void>;
180181
readonly onDidChangeConnectionState: Event<void>;
181182

@@ -546,6 +547,7 @@ export interface ITerminalInstance {
546547
onDidRequestFocus: Event<void>;
547548
onDidBlur: Event<ITerminalInstance>;
548549
onDidInputData: Event<ITerminalInstance>;
550+
onDidChangeSelection: Event<ITerminalInstance>;
549551

550552
/**
551553
* An event that fires when a terminal is dropped on this instance via drag and drop.

src/vs/workbench/contrib/terminal/browser/terminalInstance.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
316316
readonly onDidBlur = this._onDidBlur.event;
317317
private readonly _onDidInputData = this._register(new Emitter<ITerminalInstance>());
318318
readonly onDidInputData = this._onDidInputData.event;
319+
private readonly _onDidChangeSelection = this._register(new Emitter<ITerminalInstance>());
320+
readonly onDidChangeSelection = this._onDidChangeSelection.event;
319321
private readonly _onRequestAddInstanceToGroup = this._register(new Emitter<IRequestAddInstanceToGroupEvent>());
320322
readonly onRequestAddInstanceToGroup = this._onRequestAddInstanceToGroup.event;
321323
private readonly _onDidChangeHasChildProcesses = this._register(new Emitter<boolean>());
@@ -1722,6 +1724,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
17221724
}
17231725

17241726
private async _onSelectionChange(): Promise<void> {
1727+
this._onDidChangeSelection.fire(this);
17251728
if (this._configurationService.getValue(TerminalSettingId.CopyOnSelection)) {
17261729
if (this.hasSelection()) {
17271730
await this.copySelection();

src/vs/workbench/contrib/terminal/browser/terminalService.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ export class TerminalService implements ITerminalService {
152152
get onDidChangeInstancePrimaryStatus(): Event<ITerminalInstance> { return this._onDidChangeInstancePrimaryStatus.event; }
153153
private readonly _onDidInputInstanceData = new Emitter<ITerminalInstance>();
154154
get onDidInputInstanceData(): Event<ITerminalInstance> { return this._onDidInputInstanceData.event; }
155+
private readonly _onDidChangeSelection = new Emitter<ITerminalInstance>();
156+
get onDidChangeSelection(): Event<ITerminalInstance> { return this._onDidChangeSelection.event; }
155157
private readonly _onDidDisposeGroup = new Emitter<ITerminalGroup>();
156158
get onDidDisposeGroup(): Event<ITerminalGroup> { return this._onDidDisposeGroup.event; }
157159
private readonly _onDidChangeGroups = new Emitter<void>();
@@ -836,7 +838,8 @@ export class TerminalService implements ITerminalService {
836838
instance.onMaximumDimensionsChanged(() => this._onDidMaxiumumDimensionsChange.fire(instance)),
837839
instance.onDidInputData(this._onDidInputInstanceData.fire, this._onDidInputInstanceData),
838840
instance.onDidFocus(this._onDidChangeActiveInstance.fire, this._onDidChangeActiveInstance),
839-
instance.onRequestAddInstanceToGroup(async e => await this._addInstanceToGroup(instance, e))
841+
instance.onRequestAddInstanceToGroup(async e => await this._addInstanceToGroup(instance, e)),
842+
instance.onDidChangeSelection(this._onDidChangeSelection.fire, this._onDidChangeSelection)
840843
];
841844
instance.onDisposed(() => dispose(instanceDisposables));
842845
}

src/vs/workbench/services/extensions/common/extensionsApiProposals.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export const allApiProposals = Object.freeze({
8585
terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts',
8686
terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts',
8787
terminalQuickFixProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts',
88+
terminalSelection: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts',
8889
testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts',
8990
testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts',
9091
textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts',
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
declare module 'vscode' {
7+
8+
// https://github.com/microsoft/vscode/issues/188173
9+
10+
export interface Terminal {
11+
/**
12+
* The selected text of the terminal or undefined if there is no selection.
13+
*/
14+
readonly selection: string | undefined;
15+
}
16+
}

0 commit comments

Comments
 (0)