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

Terminal renderer and activeTerminal APIs #52143

Merged
merged 30 commits into from
Jun 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
20f9bf1
Implement start of terminal renderers
Tyriar Jun 2, 2018
275135a
Prefer const
Tyriar Jun 2, 2018
6e507e8
Refactor common items into BaseExtHostTerminal
Tyriar Jun 2, 2018
01c3eb7
Register onData listener
Tyriar Jun 2, 2018
e80cdde
Merge remote-tracking branch 'origin/master' into 46192_terminal_rend…
Tyriar Jun 13, 2018
eeb284b
Merge remote-tracking branch 'origin/master' into 46192_terminal_rend…
Tyriar Jun 14, 2018
ed5b3d5
Don't freeze shelllaunchconfig
Tyriar Jun 14, 2018
3465f18
Get terminal renderers working with name, onData
Tyriar Jun 14, 2018
bae15f1
Improve documentation of terminal renderer APIs
Tyriar Jun 16, 2018
166f41a
Add support for dimensions APIs
Tyriar Jun 16, 2018
2a99753
Rename onData to onInput
Tyriar Jun 16, 2018
56d8e4f
Support sendText in onInput
Tyriar Jun 16, 2018
f957bf8
Get onData working for renderers
Tyriar Jun 16, 2018
ba937e5
Simplify events
Tyriar Jun 16, 2018
b79dc1e
More event simplification
Tyriar Jun 16, 2018
0e0cdb4
Clean up API declaration
Tyriar Jun 16, 2018
f151c4e
Don't manager xterm data event, it's done by the lib
Tyriar Jun 16, 2018
7193335
Clean up
Tyriar Jun 16, 2018
36575b8
Delete unused file
Tyriar Jun 16, 2018
38f8d23
Refactor ext host to share code
Tyriar Jun 16, 2018
2b372e7
Fix processId API for both renderer and process terms
Tyriar Jun 16, 2018
970fca8
Add some more terminal API tests
Tyriar Jun 16, 2018
524e0f5
Add test for sendText -> onInput
Tyriar Jun 16, 2018
a361c53
Reduce setProcessId delay, use constant
Tyriar Jun 16, 2018
c9fe9e0
Expose the Terminal on the TerminalRenderer
Tyriar Jun 17, 2018
ec4930a
Add activeTerminal and onDidChangeActiveTerminal
Tyriar Jun 17, 2018
0a372aa
Fix usage of terminal immediate after createTerminalRenderer
Tyriar Jun 17, 2018
7a21839
Add test for onDidChangeActiveTerminal
Tyriar Jun 17, 2018
8557d61
Also test window.activeTerminal
Tyriar Jun 17, 2018
1558fd5
Clear up references causing dispose issues in terminal
Tyriar Jun 17, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
158 changes: 132 additions & 26 deletions extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
'use strict';

import * as assert from 'assert';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind } from 'vscode';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, Terminal } from 'vscode';
import { join } from 'path';
import { closeAllEditors, pathEquals, createRandomFile } from '../utils';

Expand Down Expand Up @@ -462,37 +462,143 @@ suite('window namespace tests', () => {
});
});

test('createTerminal, Terminal.name', () => {
const terminal = window.createTerminal('foo');
assert.equal(terminal.name, 'foo');
suite('Terminal', () => {
test('createTerminal, Terminal.name', () => {
const terminal = window.createTerminal('foo');
assert.equal(terminal.name, 'foo');

assert.throws(() => {
(<any>terminal).name = 'bar';
}, 'Terminal.name should be readonly');
});
assert.throws(() => {
(<any>terminal).name = 'bar';
}, 'Terminal.name should be readonly');
terminal.dispose();
});

test('terminal, sendText immediately after createTerminal should not throw', () => {
const terminal = window.createTerminal();
assert.doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
});
test('sendText immediately after createTerminal should not throw', () => {
const terminal = window.createTerminal();
assert.doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
terminal.dispose();
});

test('terminal, onDidCloseTerminal event fires when terminal is disposed', (done) => {
const terminal = window.createTerminal();
window.onDidCloseTerminal((eventTerminal) => {
assert.equal(terminal, eventTerminal);
done();
test('onDidCloseTerminal event fires when terminal is disposed', (done) => {
const terminal = window.createTerminal();
const reg = window.onDidCloseTerminal((eventTerminal) => {
assert.equal(terminal, eventTerminal);
reg.dispose();
done();
});
terminal.dispose();
});
terminal.dispose();
});

test('terminal, processId immediately after createTerminal should fetch the pid', (done) => {
window.createTerminal().processId.then(id => {
assert.ok(id > 0);
done();
test('processId immediately after createTerminal should fetch the pid', (done) => {
const terminal = window.createTerminal();
terminal.processId.then(id => {
assert.ok(id > 0);
terminal.dispose();
done();
});
});

test('name in constructor should set terminal.name', () => {
const terminal = window.createTerminal('a');
assert.equal(terminal.name, 'a');
terminal.dispose();
});

test('onDidOpenTerminal should fire when a terminal is created', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
assert.equal(term.name, 'b');
reg1.dispose();
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
terminal.dispose();
});
const terminal = window.createTerminal('b');
});

test('createTerminalRenderer should fire onDidOpenTerminal and onDidCloseTerminal', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
assert.equal(term.name, 'c');
reg1.dispose();
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
term.dispose();
});
window.createTerminalRenderer('c');
});
});

test('terminal, name should set terminal.name', () => {
assert.equal(window.createTerminal('foo').name, 'foo');
test('terminal renderers should get maximum dimensions set when shown', (done) => {
let terminal: Terminal;
const reg1 = window.onDidOpenTerminal(term => {
reg1.dispose();
term.show();
terminal = term;
});
const renderer = window.createTerminalRenderer('foo');
const reg2 = renderer.onDidChangeMaximumDimensions(dimensions => {
assert.ok(dimensions.cols > 0);
assert.ok(dimensions.rows > 0);
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
});

test('TerminalRenderer.write should fire Terminal.onData', (done) => {
const reg1 = window.onDidOpenTerminal(terminal => {
reg1.dispose();
const reg2 = terminal.onData(data => {
assert.equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
renderer.write('bar');
});
const renderer = window.createTerminalRenderer('foo');
});

test('Terminal.sendText should fire Termnial.onInput', (done) => {
const reg1 = window.onDidOpenTerminal(terminal => {
reg1.dispose();
const reg2 = renderer.onInput(data => {
assert.equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
terminal.sendText('bar', false);
});
const renderer = window.createTerminalRenderer('foo');
});

test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => {
const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
assert.equal(active, terminal);
assert.equal(active, window.activeTerminal);
reg1.dispose();
const reg2 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
assert.equal(active, undefined);
assert.equal(active, window.activeTerminal);
reg2.dispose();
done();
});
terminal.dispose();
});
const terminal = window.createTerminal();
terminal.show();
});
});
});
136 changes: 135 additions & 1 deletion src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,11 +339,123 @@ declare module 'vscode' {
/**
* Fires when the terminal's pty slave pseudo-device is written to. In other words, this
* provides access to the raw data stream from the process running within the terminal,
* including ANSI sequences.
* including VT sequences.
*/
onData: Event<string>;
}

/**
* Represents the dimensions of a terminal.
*/
export interface TerminalDimensions {
/**
* The number of columns in the terminal.
*/
cols: number;

/**
* The number of rows in the terminal.
*/
rows: number;
}

/**
* Represents a terminal without a process where all interaction and output in the terminal is
* controlled by an extension. This is similar to an output window but has the same VT sequence
* compatility as the regular terminal.
*
* Note that an instance of [Terminal](#Terminal) will be created when a TerminalRenderer is
* created with all its APIs available for use by extensions. When using the Terminal object
* of a TerminalRenderer it acts just like normal only the extension that created the
* TerminalRenderer essentially acts as a process. For example when an
* [Terminal.onData](#Terminal.onData) listener is registered, that will fire when
* [TerminalRenderer.write](#TerminalRenderer.write) is called. Similarly when
* [Terminal.sendText](#Terminal.sendText) is triggered that will fire the
* [TerminalRenderer.onInput](#TerminalRenderer.onInput) event.
*
* **Example:** Create a terminal renderer, show it and write hello world in red
* ```typescript
* const renderer = window.createTerminalRenderer('foo');
* renderer.terminal.then(t => t.show());
* renderer.write('\x1b[31mHello world\x1b[0m');
* ```
*/
export interface TerminalRenderer {
/**
* The name of the terminal, this will appear in the terminal selector.
*/
name: string;

/**
* The dimensions of the terminal, the rows and columns of the terminal can only be set to
* a value smaller than the maximum value, if this is undefined the terminal will auto fit
* to the maximum value [maximumDimensions](TerminalRenderer.maximumDimensions).
*
* **Example:** Override the dimensions of a TerminalRenderer to 20 columns and 10 rows
* ```typescript
* terminalRenderer.dimensions = {
* cols: 20,
* rows: 10
* };
* ```
*/
dimensions: TerminalDimensions;

/**
* The maximum dimensions of the terminal, this will be undefined immediately after a
* terminal renderer is created and also until the terminal becomes visible in the UI.
* Listen to [onDidChangeMaximumDimensions](TerminalRenderer.onDidChangeMaximumDimensions)
* to get notified when this value changes.
*/
readonly maximumDimensions: TerminalDimensions;

/**
* The corressponding [Terminal](#Terminal) for this TerminalRenderer.
*/
readonly terminal: Thenable<Terminal>;

/**
* Write text to the terminal. Unlike [Terminal.sendText](#Terminal.sendText) which sends
* text to the underlying _process_, this will write the text to the terminal itself.
*
* **Example:** Write red text to the terminal
* ```typescript
* terminalRenderer.write('\x1b[31mHello world\x1b[0m');
* ```
*
* **Example:** Move the cursor to the 10th row and 20th column and write an asterisk
* ```typescript
* terminalRenderer.write('\x1b[10;20H*');
* ```
*
* @param text The text to write.
*/
write(text: string): void;

/**
* An event which fires on keystrokes in the terminal or when an extension calls
* [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their
* corresponding VT sequence representation.
*
* **Example:** Simulate interaction with the terminal from an outside extension or a
* workbench command such as `workbench.action.terminal.runSelectedText`
* ```typescript
* const terminalRenderer = window.createTerminalRenderer('test');
* terminalRenderer.onInput(data => {
* cosole.log(data); // 'Hello world'
* });
* terminalRenderer.terminal.then(t => t.sendText('Hello world'));
* ```
*/
onInput: Event<string>;

/**
* An event which fires when the [maximum dimensions](#TerminalRenderer.maimumDimensions) of
* the terminal renderer change.
*/
onDidChangeMaximumDimensions: Event<TerminalDimensions>;
}

export namespace window {
/**
* The currently opened terminals or an empty array.
Expand All @@ -352,11 +464,33 @@ declare module 'vscode' {
*/
export let terminals: Terminal[];

/**
* The currently active terminal or `undefined`. The active terminal is the one that
* currently has focus or most recently had focus.
*
* @readonly
*/
export let activeTerminal: Terminal | undefined;

/**
* An [event](#Event) which fires when the [active terminal](#window.activeTerminal)
* has changed. *Note* that the event also fires when the active editor changes
* to `undefined`.
*/
export const onDidChangeActiveTerminal: Event<Terminal | undefined>;

/**
* An [event](#Event) which fires when a terminal has been created, either through the
* [createTerminal](#window.createTerminal) API or commands.
*/
export const onDidOpenTerminal: Event<Terminal>;

/**
* Create a [TerminalRenderer](#TerminalRenderer).
*
* @param name The name of the terminal renderer, this shows up in the terminal selector.
*/
export function createTerminalRenderer(name: string): TerminalRenderer;
}

//#endregion
Expand Down