Compare commits

..
6 Commits
Author SHA1 Message Date
Daniel Imms 217ed05606 Merge pull request #2426 from Tyriar/r401
v4.0.1
2019-09-12 14:57:08 -07:00
Daniel Imms 02aa803889 v4.0.1 2019-09-12 14:53:56 -07:00
Daniel Imms 9489bd8c5c Merge pull request #2418 from Tyriar/2417_edge_links
Fix web links addon support in Edge
2019-09-11 14:37:54 -07:00
Daniel Imms 324dc8d4dc Fix web links addon support in Edge
regex.flags isn't supported on Edge

Fixes #2417
2019-09-11 14:35:36 -07:00
Daniel Imms 70ecec3c92 Merge pull request #2416 from Tyriar/window_export
Expose ctor at window.Terminal
2019-09-11 08:54:18 -07:00
Daniel Imms e0997b51e1 Expose ctor at window.Terminal
Fixes #2415
2019-09-11 08:51:01 -07:00
73 changed files with 1783 additions and 1365 deletions
+2 -9
View File
@@ -8,19 +8,12 @@ RUN apt-get update \
# Verify git and process tools are installed # Verify git and process tools are installed
RUN apt-get install -y git procps RUN apt-get install -y git procps
# Install yarn, puppeteer deps # Install yarn
RUN apt-get install -y curl apt-transport-https lsb-release \ RUN apt-get install -y curl apt-transport-https lsb-release \
&& curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \ && curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \
&& echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update \ && apt-get update \
&& apt-get -y install --no-install-recommends \ && apt-get -y install --no-install-recommends yarn
yarn fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
# https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322921352
gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
# Clean up # Clean up
RUN apt-get autoremove -y \ RUN apt-get autoremove -y \
-2
View File
@@ -154,8 +154,6 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**PHP App Server**](https://github.com/cubiclesoft/php-app-server/): Create lightweight, installable almost-native applications for desktop OSes. ExecTerminal (nicely wraps the xterm.js Terminal), TerminalManager, and RunProcessSDK are self-contained, reusable ES5+ compliant Javascript components. - [**PHP App Server**](https://github.com/cubiclesoft/php-app-server/): Create lightweight, installable almost-native applications for desktop OSes. ExecTerminal (nicely wraps the xterm.js Terminal), TerminalManager, and RunProcessSDK are self-contained, reusable ES5+ compliant Javascript components.
- [**NgTerminal**](https://github.com/qwefgh90/ng-terminal): NgTerminal is a web terminal that leverages xterm.js on Angular 7+. You can easily add it into your application by adding `<ng-terminal></ng-terminal>` into your component. - [**NgTerminal**](https://github.com/qwefgh90/ng-terminal): NgTerminal is a web terminal that leverages xterm.js on Angular 7+. You can easily add it into your application by adding `<ng-terminal></ng-terminal>` into your component.
- [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet. - [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet.
- [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks.
- [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents) [And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
+1 -1
View File
@@ -12,7 +12,7 @@ npm install --save xterm-addon-attach
```ts ```ts
import { Terminal } from 'xterm'; import { Terminal } from 'xterm';
import { AttachAddon } from 'xterm-addon-attach'; import { FitAddon } from 'xterm-addon-attach';
const terminal = new Terminal(); const terminal = new Terminal();
const attachAddon = new AttachAddon(webSocket); const attachAddon = new AttachAddon(webSocket);
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "xterm-addon-attach", "name": "xterm-addon-attach",
"version": "0.3.0", "version": "0.2.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package" "prepublishOnly": "npm run package"
}, },
"peerDependencies": { "peerDependencies": {
"xterm": "^4.0.0" "xterm": "^3.14.0"
} }
} }
@@ -21,7 +21,7 @@ describe('AttachAddon', () => {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1, headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80, slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`] args: [`--window-size=${width},${height}`]
}); });
page = (await browser.pages())[0]; page = (await browser.pages())[0];
await page.setViewport({ width, height }); await page.setViewport({ width, height });
@@ -54,7 +54,7 @@ describe('AttachAddon', () => {
const server = new WebSocket.Server({ port }); const server = new WebSocket.Server({ port });
const data = new Uint8Array([102, 111, 111]); const data = new Uint8Array([102, 111, 111]);
server.on('connection', socket => socket.send(data)); server.on('connection', socket => socket.send(data));
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}'), { inputUtf8: true }))`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
server.close(); server.close();
}); });
+11 -7
View File
@@ -9,11 +9,13 @@ import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
interface IAttachOptions { interface IAttachOptions {
bidirectional?: boolean; bidirectional?: boolean;
inputUtf8?: boolean;
} }
export class AttachAddon implements ITerminalAddon { export class AttachAddon implements ITerminalAddon {
private _socket: WebSocket; private _socket: WebSocket;
private _bidirectional: boolean; private _bidirectional: boolean;
private _utf8: boolean;
private _disposables: IDisposable[] = []; private _disposables: IDisposable[] = [];
constructor(socket: WebSocket, options?: IAttachOptions) { constructor(socket: WebSocket, options?: IAttachOptions) {
@@ -21,15 +23,17 @@ export class AttachAddon implements ITerminalAddon {
// always set binary type to arraybuffer, we do not handle blobs // always set binary type to arraybuffer, we do not handle blobs
this._socket.binaryType = 'arraybuffer'; this._socket.binaryType = 'arraybuffer';
this._bidirectional = (options && options.bidirectional === false) ? false : true; this._bidirectional = (options && options.bidirectional === false) ? false : true;
this._utf8 = !!(options && options.inputUtf8);
} }
public activate(terminal: Terminal): void { public activate(terminal: Terminal): void {
this._disposables.push( if (this._utf8) {
addSocketListener(this._socket, 'message', ev => { this._disposables.push(addSocketListener(this._socket, 'message',
const data: ArrayBuffer | string = ev.data; (ev: MessageEvent | Event | CloseEvent) => terminal.writeUtf8(new Uint8Array((ev as any).data as ArrayBuffer))));
terminal.write(typeof data === 'string' ? data : new Uint8Array(data)); } else {
}) this._disposables.push(addSocketListener(this._socket, 'message',
); (ev: MessageEvent | Event | CloseEvent) => terminal.write((ev as any).data as string)));
}
if (this._bidirectional) { if (this._bidirectional) {
this._disposables.push(terminal.onData(data => this._sendData(data))); this._disposables.push(terminal.onData(data => this._sendData(data)));
@@ -53,7 +57,7 @@ export class AttachAddon implements ITerminalAddon {
} }
} }
function addSocketListener<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable { function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: MessageEvent | Event | CloseEvent) => any): IDisposable {
socket.addEventListener(type, handler); socket.addEventListener(type, handler);
return { return {
dispose: () => { dispose: () => {
@@ -11,6 +11,14 @@ declare module 'xterm-addon-attach' {
* Whether input should be written to the backend. Defaults to `true`. * Whether input should be written to the backend. Defaults to `true`.
*/ */
bidirectional?: boolean; bidirectional?: boolean;
/**
* Whether to use UTF8 binary transport for incoming messages. Defaults to `false`.
* Note: This must be in line with the server side of the websocket.
* Always send string messages from the backend if this options is false,
* otherwise always binary UTF8 data.
*/
inputUtf8?: boolean;
} }
export class AttachAddon implements ITerminalAddon { export class AttachAddon implements ITerminalAddon {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "xterm-addon-fit", "name": "xterm-addon-fit",
"version": "0.3.0", "version": "0.2.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package" "prepublishOnly": "npm run package"
}, },
"peerDependencies": { "peerDependencies": {
"xterm": "^4.0.0" "xterm": "^3.14.0"
} }
} }
-119
View File
@@ -1,119 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
const width = 1024;
const height = 768;
describe('FitAddon', () => {
before(async function(): Promise<any> {
this.timeout(20000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
await page.goto(APP);
await openTerminal();
});
after(async () => {
await browser.close();
});
it('no terminal', async function(): Promise<any> {
await page.evaluate(`window.fit = new FitAddon();`);
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
});
describe('proposeDimensions', () => {
afterEach(async () => {
return unloadFit();
});
it('default', async function(): Promise<any> {
await loadFit();
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 87,
rows: 26
});
});
it('width', async function(): Promise<any> {
await loadFit(1008);
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 110,
rows: 26
});
});
it('small', async function(): Promise<any> {
await loadFit(1, 1);
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 2,
rows: 1
});
});
});
describe('fit', () => {
afterEach(async () => {
return unloadFit();
});
it('default', async function(): Promise<any> {
await loadFit();
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 87);
assert.equal(await page.evaluate(`window.term.rows`), 26);
});
it('width', async function(): Promise<any> {
await loadFit(1008);
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 110);
assert.equal(await page.evaluate(`window.term.rows`), 26);
});
it('small', async function(): Promise<any> {
await loadFit(1, 1);
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 2);
assert.equal(await page.evaluate(`window.term.rows`), 1);
});
});
});
async function loadFit(width: number = 800, height: number = 450): Promise<void> {
await page.evaluate(`
window.fit = new FitAddon();
window.term.loadAddon(window.fit);
document.querySelector('#terminal-container').style.width='${width}px';
document.querySelector('#terminal-container').style.height='${height}px';
`);
}
async function unloadFit(): Promise<void> {
await page.evaluate(`window.fit.dispose();`);
}
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
if (options.rendererType === 'dom') {
await page.waitForSelector('.xterm-rows');
} else {
await page.waitForSelector('.xterm-text-layer');
}
}
+3 -6
View File
@@ -17,9 +17,6 @@ interface ITerminalDimensions {
cols: number; cols: number;
} }
const MINIMUM_COLS = 2;
const MINIMUM_ROWS = 1;
export class FitAddon implements ITerminalAddon { export class FitAddon implements ITerminalAddon {
private _terminal: Terminal | undefined; private _terminal: Terminal | undefined;
@@ -52,7 +49,7 @@ export class FitAddon implements ITerminalAddon {
return undefined; return undefined;
} }
if (!this._terminal.element || !this._terminal.element.parentElement) { if (!this._terminal.element.parentElement) {
return undefined; return undefined;
} }
@@ -74,8 +71,8 @@ export class FitAddon implements ITerminalAddon {
const availableHeight = parentElementHeight - elementPaddingVer; const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth; const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth;
const geometry = { const geometry = {
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth)), cols: Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth),
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight)) rows: Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight)
}; };
return geometry; return geometry;
} }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "xterm-addon-search", "name": "xterm-addon-search",
"version": "0.3.0", "version": "0.2.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package" "prepublishOnly": "npm run package"
}, },
"peerDependencies": { "peerDependencies": {
"xterm": "^4.0.0" "xterm": "^3.14.0"
} }
} }
@@ -15,13 +15,13 @@ const width = 800;
const height = 600; const height = 600;
describe('Search Tests', function (): void { describe('Search Tests', function (): void {
this.timeout(20000); this.timeout(200000);
before(async function (): Promise<any> { before(async function (): Promise<any> {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1, headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80, slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`] args: [`--window-size=${width},${height}`]
}); });
page = (await browser.pages())[0]; page = (await browser.pages())[0];
await page.setViewport({ width, height }); await page.setViewport({ width, height });
@@ -98,14 +98,6 @@ describe('Search Tests', function (): void {
await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`); await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'ABCD'); assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'ABCD');
}); });
it('Search for single result twice should not unselect it', async () => {
await writeSync('abc def');
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
});
}); });
async function openTerminal(options: ITerminalOptions = {}): Promise<void> { async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
@@ -119,7 +111,12 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
} }
async function writeSync(data: string): Promise<void> { async function writeSync(data: string): Promise<void> {
return page.evaluate(`new Promise(resolve => window.term.write('${data}', resolve))`); await page.evaluate(`window.term.write('${data}');`);
while (true) {
if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) {
break;
}
}
} }
function makeData(length: number): string { function makeData(length: number): string {
+6 -13
View File
@@ -3,7 +3,7 @@
* @license MIT * @license MIT
*/ */
import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm'; import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
export interface ISearchOptions { export interface ISearchOptions {
regex?: boolean; regex?: boolean;
@@ -59,12 +59,12 @@ export class SearchAddon implements ITerminalAddon {
let startCol = 0; let startCol = 0;
let startRow = 0; let startRow = 0;
let currentSelection: ISelectionPosition | undefined;
if (this._terminal.hasSelection()) { if (this._terminal.hasSelection()) {
const incremental = searchOptions ? searchOptions.incremental : false; const incremental = searchOptions ? searchOptions.incremental : false;
// Start from the selection end if there is a selection // Start from the selection end if there is a selection
// For incremental search, use existing row // For incremental search, use existing row
currentSelection = this._terminal.getSelectionPosition()!; const currentSelection = this._terminal.getSelectionPosition()!;
startRow = incremental ? currentSelection.startRow : currentSelection.endRow; startRow = incremental ? currentSelection.startRow : currentSelection.endRow;
startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn;
} }
@@ -97,9 +97,6 @@ export class SearchAddon implements ITerminalAddon {
} }
} }
// If there is only one result, return true.
if (!result && currentSelection) return true;
// Set selection and scroll if a result was found // Set selection and scroll if a result was found
return this._selectResult(result); return this._selectResult(result);
} }
@@ -124,11 +121,10 @@ export class SearchAddon implements ITerminalAddon {
const isReverseSearch = true; const isReverseSearch = true;
let startRow = this._terminal.buffer.baseY + this._terminal.rows; let startRow = this._terminal.buffer.baseY + this._terminal.rows;
let startCol = this._terminal.cols; let startCol = this._terminal.cols;
let result: ISearchResult | undefined; let result: ISearchResult | undefined = undefined;
const incremental = searchOptions ? searchOptions.incremental : false; const incremental = searchOptions ? searchOptions.incremental : false;
let currentSelection: ISelectionPosition | undefined;
if (this._terminal.hasSelection()) { if (this._terminal.hasSelection()) {
currentSelection = this._terminal.getSelectionPosition()!; const currentSelection = this._terminal.getSelectionPosition()!;
// Start from selection start if there is a selection // Start from selection start if there is a selection
startRow = currentSelection.startRow; startRow = currentSelection.startRow;
startCol = currentSelection.startColumn; startCol = currentSelection.startColumn;
@@ -165,9 +161,6 @@ export class SearchAddon implements ITerminalAddon {
} }
} }
// If there is only one result, return true.
if (!result && currentSelection) return true;
// Set selection and scroll if a result was found // Set selection and scroll if a result was found
return this._selectResult(result); return this._selectResult(result);
} }
@@ -351,7 +344,7 @@ export class SearchAddon implements ITerminalAddon {
} }
terminal.select(result.col, result.row, result.term.length); terminal.select(result.col, result.row, result.term.length);
// If it is not in the viewport then we scroll else it just gets selected // If it is not in the viewport then we scroll else it just gets selected
if (result.row >= (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) { if (result.row > (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
let scroll = result.row - terminal.buffer.viewportY; let scroll = result.row - terminal.buffer.viewportY;
scroll = scroll - Math.floor(terminal.rows / 2); scroll = scroll - Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll); terminal.scrollLines(scroll);
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "xterm-addon-web-links", "name": "xterm-addon-web-links",
"version": "0.2.1", "version": "0.2.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package" "prepublishOnly": "npm run package"
}, },
"peerDependencies": { "peerDependencies": {
"xterm": "^4.0.0" "xterm": "^3.14.0"
} }
} }
@@ -20,7 +20,7 @@ describe('WebLinksAddon', () => {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1, headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80, slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`] args: [`--window-size=${width},${height}`]
}); });
page = (await browser.pages())[0]; page = (await browser.pages())[0];
await page.setViewport({ width, height }); await page.setViewport({ width, height });
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "xterm-addon-webgl", "name": "xterm-addon-webgl",
"version": "0.3.0", "version": "0.2.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
@@ -16,6 +16,6 @@
"prepublishOnly": "npm run package" "prepublishOnly": "npm run package"
}, },
"peerDependencies": { "peerDependencies": {
"xterm": "^4.0.0" "xterm": "^3.14.0"
} }
} }
@@ -105,6 +105,9 @@ export class GlyphRenderer {
const gl = this._gl; const gl = this._gl;
const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource)); const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
if (program === undefined) {
throw new Error('Could not create WebGL program');
}
this._program = program; this._program = program;
// Uniform locations // Uniform locations
@@ -22,7 +22,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1, headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80, slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`] args: [`--window-size=${width},${height}`]
}); });
page = (await browser.pages())[0]; page = (await browser.pages())[0];
await page.setViewport({ width, height }); await page.setViewport({ width, height });
@@ -145,7 +145,12 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
} }
async function writeSync(data: string): Promise<void> { async function writeSync(data: string): Promise<void> {
return page.evaluate(`new Promise(resolve => window.term.write('${data}', resolve))`); await page.evaluate(`window.term.write('${data}');`);
while (true) {
if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) {
break;
}
}
} }
async function getCellColor(col: number, row: number): Promise<number[]> { async function getCellColor(col: number, row: number): Promise<number[]> {
@@ -14,7 +14,7 @@ import { IWebGL2RenderingContext } from './Types';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel';
import { Disposable } from 'common/Lifecycle'; import { Disposable } from 'common/Lifecycle';
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal } from 'xterm'; import { Terminal } from 'xterm';
import { getLuminance } from './ColorUtils'; import { getLuminance } from './ColorUtils';
import { IRenderLayer } from './renderLayer/Types'; import { IRenderLayer } from './renderLayer/Types';
@@ -92,10 +92,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._cursorBlinkStateManager) { if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose(); this._cursorBlinkStateManager.dispose();
} }
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
} }
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
} }
public onCursorMove(terminal: Terminal): void { public onCursorMove(terminal: Terminal): void {
@@ -139,17 +139,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._clearCursor(); this._clearCursor();
this._ctx.save(); this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css; this._ctx.fillStyle = this._colors.cursor.css;
const cursorStyle = terminal.getOption('cursorStyle'); this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
if (cursorStyle && cursorStyle !== 'block') {
this._cursorRenderers[cursorStyle](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
} else {
this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
}
this._ctx.restore(); this._ctx.restore();
this._state.x = terminal.buffer.cursorX; this._state.x = terminal.buffer.cursorX;
this._state.y = viewportRelativeCursorY; this._state.y = viewportRelativeCursorY;
this._state.isFocused = false; this._state.isFocused = false;
this._state.style = cursorStyle; this._state.style = terminal.getOption('cursorStyle');
this._state.width = this._cell.getWidth(); this._state.width = this._cell.getWidth();
return; return;
} }
+8 -7
View File
@@ -20,7 +20,7 @@ jobs:
yarn yarn
displayName: 'Install dependencies and build' displayName: 'Install dependencies and build'
- script: | - script: |
yarn test-unit --forbid-only yarn test-unit
displayName: 'Unit tests' displayName: 'Unit tests'
- script: | - script: |
yarn lint yarn lint
@@ -38,7 +38,7 @@ jobs:
yarn yarn
displayName: 'Install dependencies and build' displayName: 'Install dependencies and build'
- script: | - script: |
yarn test-unit --forbid-only yarn test-unit
displayName: 'Unit tests' displayName: 'Unit tests'
- script: | - script: |
yarn lint yarn lint
@@ -56,7 +56,7 @@ jobs:
yarn yarn
displayName: 'Install dependencies and build' displayName: 'Install dependencies and build'
- script: | - script: |
yarn test-unit --forbid-only yarn test-unit
displayName: 'Unit tests' displayName: 'Unit tests'
- script: | - script: |
yarn lint yarn lint
@@ -80,7 +80,7 @@ jobs:
- script: | - script: |
yarn start & yarn start &
sleep 10 sleep 10
yarn test-api --headless --forbid-only yarn test-api --headless
displayName: 'Linux Integration tests' displayName: 'Linux Integration tests'
- job: macOS_IntegrationTests - job: macOS_IntegrationTests
@@ -97,7 +97,7 @@ jobs:
- script: | - script: |
yarn start & yarn start &
sleep 10 sleep 10
yarn test-api --headless --forbid-only yarn test-api --headless
displayName: 'MacOS Integration tests' displayName: 'MacOS Integration tests'
- job: Release - job: Release
@@ -107,7 +107,7 @@ jobs:
- Windows - Windows
- Linux_IntegrationTests - Linux_IntegrationTests
- macOS_IntegrationTests - macOS_IntegrationTests
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true'))) condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/release/*')))
pool: pool:
vmImage: 'ubuntu-16.04' vmImage: 'ubuntu-16.04'
steps: steps:
@@ -121,7 +121,8 @@ jobs:
displayName: 'Install Yarn' displayName: 'Install Yarn'
- script: | - script: |
yarn yarn
BUILD_DIR=dist npm run build
displayName: 'Install dependencies and build' displayName: 'Install dependencies and build'
- script: | - script: |
NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
displayName: 'Package and publish to npm' displayName: 'Publish to npm'
+1 -19
View File
@@ -5,7 +5,6 @@
const cp = require('child_process'); const cp = require('child_process');
const fs = require('fs'); const fs = require('fs');
const os = require('os');
const path = require('path'); const path = require('path');
// Setup auth // Setup auth
@@ -19,9 +18,8 @@ if (isDryRun) {
const changedFiles = getChangedFilesInCommit('HEAD'); const changedFiles = getChangedFilesInCommit('HEAD');
// Publish xterm if any files were changed outside of the addons directory // Publish xterm if any files were changed outside of the addons directory
let isStableRelease = false;
if (changedFiles.some(e => e.search(/^addons\//) === -1)) { if (changedFiles.some(e => e.search(/^addons\//) === -1)) {
isStableRelease = checkAndPublishPackage(path.resolve(__dirname, '..')); checkAndPublishPackage(path.resolve(__dirname, '..'));
} }
// Publish addons if any files were changed inside of the addon // Publish addons if any files were changed inside of the addon
@@ -41,11 +39,6 @@ addonPackageDirs.forEach(p => {
} }
}); });
// Publish website if it's a stable release
if (isStableRelease) {
updateWebsite();
}
function checkAndPublishPackage(packageDir) { function checkAndPublishPackage(packageDir) {
const packageJson = require(path.join(packageDir, 'package.json')); const packageJson = require(path.join(packageDir, 'package.json'));
@@ -83,8 +76,6 @@ function checkAndPublishPackage(packageDir) {
} }
console.groupEnd(); console.groupEnd();
return isStableRelease;
} }
function getNextBetaVersion(packageJson) { function getNextBetaVersion(packageJson) {
@@ -124,12 +115,3 @@ function getChangedFilesInCommit(commit) {
const changedFiles = output.split('\n').filter(e => e.length > 0); const changedFiles = output.split('\n').filter(e => e.length > 0);
return changedFiles; return changedFiles;
} }
function updateWebsite() {
console.log('Updating website');
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'website-'));
const packageJson = require(path.join(path.resolve(__dirname, '..'), 'package.json'));
if (!isDryRun) {
cp.spawnSync('sh', [path.join(__dirname, 'update-website.sh'), packageJson.version], { cwd, stdio: [process.stdin, process.stdout, process.stderr] });
}
}
+5 -12
View File
@@ -15,22 +15,15 @@ let testFiles = [
'./out/**/*test.js' './out/**/*test.js'
]; ];
let flagArgs = []; // ability to inject particular test files via
// yarn test [testFileA testFileB ...]
if (process.argv.length > 2) { if (process.argv.length > 2) {
const args = process.argv.slice(2); testFiles = process.argv.slice(2);
flagArgs = args.filter(e => e.startsWith('--'));
// ability to inject particular test files via
// yarn test [testFileA testFileB ...]
files = args.filter(e => !e.startsWith('--'));
if (files.length) {
testFiles = files;
}
} }
const run = cp.spawnSync( const run = cp.spawnSync(
path.resolve(__dirname, '../node_modules/.bin/mocha'), path.resolve(__dirname, '../node_modules/.bin/mocha'),
[...testFiles, ...flagArgs], testFiles,
{ {
cwd: path.resolve(__dirname, '..'), cwd: path.resolve(__dirname, '..'),
env, env,
@@ -38,4 +31,4 @@ const run = cp.spawnSync(
} }
); );
process.exit(run.status); process.exit(run.status);
-41
View File
@@ -1,41 +0,0 @@
#!/bin/sh
# Name the arguments
VERSION=$1
# Clone docs repo and update the documentation
git clone https://github.com/xtermjs/xtermjs.org
cd xtermjs.org
yarn
./bin/update-docs
# Add changes to index and only proceed if there are changes to commit
touch test-file
git add .
if ! git diff-index --quiet HEAD --; then
# Delete the upstream branch if it exists for some reason
export BRANCH_NAME=update-$VERSION
git branch -D $BRANCH_NAME || true
git push origin :$BRANCH_NAME || true
# Create commit and push it to update-x.y.z
git checkout -b $BRANCH_NAME
git config --global user.name Daniel Imms
git config --global user.email tyriar@tyriar.com
git commit -m 'Update docs for v$VERSION'
git push --set-upstream origin update-4.2.0
git push -f
# Create a PR in the GitHub repo
curl \
-H "Authorization: token $GITHUB_TOKEN" \
-X POST \
-d "{\"title\":\"Update docs for v$VERSION\",\"base\":\"master\",\"head\":\"xtermjs:$BRANCH_NAME\"}" \
https://api.github.com/repos/xtermjs/xtermjs.org/pulls
else
echo "No changes to commit"
fi
+9 -3
View File
@@ -167,7 +167,14 @@ function createTerminal(): void {
} }
function runRealTerminal(): void { function runRealTerminal(): void {
/**
* The demo defaults to string transport by default.
* To run it with UTF8 binary transport, swap comment on
* the lines below. (Must also be switched in server.js)
*/
term.loadAddon(new AttachAddon(socket)); term.loadAddon(new AttachAddon(socket));
// term.loadAddon(new AttachAddon(socket, {inputUtf8: true}));
term._initialized = true; term._initialized = true;
} }
@@ -221,7 +228,6 @@ function initOptions(term: TerminalType): void {
bellSound: null, bellSound: null,
bellStyle: ['none', 'sound'], bellStyle: ['none', 'sound'],
cursorStyle: ['block', 'underline', 'bar'], cursorStyle: ['block', 'underline', 'bar'],
fastScrollModifier: ['alt', 'ctrl', 'shift', undefined],
fontFamily: null, fontFamily: null,
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
@@ -254,7 +260,7 @@ function initOptions(term: TerminalType): void {
}); });
html += '</div><div class="option-group">'; html += '</div><div class="option-group">';
numberOptions.forEach(o => { numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`; html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' ? '0.1' : '1'}"/></label></div>`;
}); });
html += '</div><div class="option-group">'; html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => { Object.keys(stringOptions).forEach(o => {
@@ -283,7 +289,7 @@ function initOptions(term: TerminalType): void {
console.log('change', o, input.value); console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') { if (o === 'cols' || o === 'rows') {
updateTerminalSize(); updateTerminalSize();
} else if (o === 'lineHeight' || o === 'scrollSensitivity') { } else if (o === 'lineHeight') {
term.setOption(o, parseFloat(input.value)); term.setOption(o, parseFloat(input.value));
updateTerminalSize(); updateTerminalSize();
} else { } else {
+14 -12
View File
@@ -3,8 +3,12 @@ var expressWs = require('express-ws');
var os = require('os'); var os = require('os');
var pty = require('node-pty'); var pty = require('node-pty');
// Whether to use binary transport. /**
const USE_BINARY = os.platform() !== "win32"; * Whether to use UTF8 binary transport.
* (Must also be switched in client.ts)
*/
const USE_BINARY_UTF8 = false;
function startServer() { function startServer() {
var app = express(); var app = express();
@@ -14,26 +18,24 @@ function startServer() {
logs = {}; logs = {};
app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css')); app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css'));
app.get('/logo.png', (req, res) => { // lgtm [js/missing-rate-limiting] app.get('/logo.png', (req, res) => res.sendFile(__dirname + '/logo.png'));
res.sendFile(__dirname + '/logo.png');
});
app.get('/', (req, res) => { // lgtm [js/missing-rate-limiting] app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html'); res.sendFile(__dirname + '/index.html');
}); });
app.get('/test', (req, res) => { // lgtm [js/missing-rate-limiting] app.get('/test', function(req, res){
res.sendFile(__dirname + '/test.html'); res.sendFile(__dirname + '/test.html');
}); });
app.get('/style.css', (req, res) => { // lgtm [js/missing-rate-limiting] app.get('/style.css', function(req, res){
res.sendFile(__dirname + '/style.css'); res.sendFile(__dirname + '/style.css');
}); });
app.use('/dist', express.static(__dirname + '/dist')); app.use('/dist', express.static(__dirname + '/dist'));
app.use('/src', express.static(__dirname + '/src')); app.use('/src', express.static(__dirname + '/src'));
app.post('/terminals', (req, res) => { app.post('/terminals', function (req, res) {
const env = Object.assign({}, process.env); const env = Object.assign({}, process.env);
env['COLORTERM'] = 'truecolor'; env['COLORTERM'] = 'truecolor';
var cols = parseInt(req.query.cols), var cols = parseInt(req.query.cols),
@@ -44,7 +46,7 @@ function startServer() {
rows: rows || 24, rows: rows || 24,
cwd: env.PWD, cwd: env.PWD,
env: env, env: env,
encoding: USE_BINARY ? null : 'utf8' encoding: USE_BINARY_UTF8 ? null : 'utf8'
}); });
console.log('Created terminal with PID: ' + term.pid); console.log('Created terminal with PID: ' + term.pid);
@@ -57,7 +59,7 @@ function startServer() {
res.end(); res.end();
}); });
app.post('/terminals/:pid/size', (req, res) => { app.post('/terminals/:pid/size', function (req, res) {
var pid = parseInt(req.params.pid), var pid = parseInt(req.params.pid),
cols = parseInt(req.query.cols), cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows), rows = parseInt(req.query.rows),
@@ -106,7 +108,7 @@ function startServer() {
} }
}; };
} }
const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5); const send = USE_BINARY_UTF8 ? bufferUtf8(ws, 5) : buffer(ws, 5);
term.on('data', function(data) { term.on('data', function(data) {
try { try {
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[5 A[5 A[5 A
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[5 @[5 @[5 @
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
fghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[5'}[5'}[5'}
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijk lmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB[5'~[5'~[5'~
@@ -1,25 +0,0 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijkqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "xterm", "name": "xterm",
"description": "Full xterm terminal, in your browser", "description": "Full xterm terminal, in your browser",
"version": "4.2.0", "version": "4.0.1",
"main": "lib/xterm.js", "main": "lib/xterm.js",
"style": "css/xterm.css", "style": "css/xterm.css",
"types": "typings/xterm.d.ts", "types": "typings/xterm.d.ts",
@@ -47,7 +47,7 @@
"ts-loader": "^6.0.4", "ts-loader": "^6.0.4",
"tslint": "^5.18.0", "tslint": "^5.18.0",
"tslint-consistent-codestyle": "^1.13.0", "tslint-consistent-codestyle": "^1.13.0",
"typescript": "3.6", "typescript": "3.5",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"webpack": "^4.35.3", "webpack": "^4.35.3",
"webpack-cli": "^3.1.0", "webpack-cli": "^3.1.0",
+7 -82
View File
@@ -25,14 +25,6 @@ function getCursor(term: TestTerminal): number[] {
]; ];
} }
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
const res: string[] = [];
for (let i = 0; i < limit; ++i) {
res.push(term.buffer.lines.get(i).translateToString(true));
}
return res;
}
describe('InputHandler', () => { describe('InputHandler', () => {
describe('save and restore cursor', () => { describe('save and restore cursor', () => {
const terminal = new MockInputHandlingTerminal(); const terminal = new MockInputHandlingTerminal();
@@ -1133,6 +1125,13 @@ describe('InputHandler', () => {
beforeEach(() => { beforeEach(() => {
term = new TestTerminal({cols: 10, rows: 10}); term = new TestTerminal({cols: 10, rows: 10});
}); });
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
const res: string[] = [];
for (let i = 0; i < limit; ++i) {
res.push(term.buffer.lines.get(i).translateToString(true));
}
return res;
}
it('scrollUp', () => { it('scrollUp', () => {
term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm'); term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm');
assert.deepEqual(getLines(term), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']); assert.deepEqual(getLines(term), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']);
@@ -1192,78 +1191,4 @@ describe('InputHandler', () => {
assert.deepEqual(getLines(term), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']); assert.deepEqual(getLines(term), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']);
}); });
}); });
describe('SL/SR/DECIC/DECDC', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal({cols: 5, rows: 5, scrollback: 1});
});
it('SL (scrollLeft)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[ @');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']);
term.writeSync('\x1b[0 @');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']);
term.writeSync('\x1b[2 @');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']);
});
it('SR (scrollRight)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[ A');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
term.writeSync('\x1b[0 A');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
term.writeSync('\x1b[2 A');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
});
it('insertColumns (DECIC)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[\'}');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[1\'}');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[2\'}');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
});
it('deleteColumns (DECDC)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[\'~');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[1\'~');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[2\'~');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']);
});
});
it('should parse big chunks in smaller subchunks', () => {
// max single chunk size is hardcoded as 131072
const calls: any[] = [];
const term = new TestTerminal({cols: 10, rows: 10});
(term as any)._inputHandler._parser.parse = (data: Uint32Array, length: number) => {
calls.push([data.length, length]);
};
term.writeSync('12345');
term.writeSync('a'.repeat(10000));
term.writeSync('a'.repeat(200000));
term.writeSync('a'.repeat(300000));
assert.deepEqual(calls, [
[4096, 5],
[10000, 10000],
[131072, 131072], [131072, 200000 - 131072],
[131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072]
]);
});
}); });
+40 -138
View File
@@ -28,11 +28,6 @@ import { DcsHandler } from 'common/parser/DcsParser';
*/ */
const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2}; const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2};
/**
* Max length of the UTF32 input buffer. Real memory consumption is 4 times higher.
*/
const MAX_PARSEBUFFER_LENGTH = 131072;
/** /**
* DCS subparser implementations * DCS subparser implementations
@@ -180,9 +175,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* CSI handler * CSI handler
*/ */
this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params)); this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params));
this._parser.setCsiHandler({intermediates: ' ', final: '@'}, params => this.scrollLeft(params));
this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params)); this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params));
this._parser.setCsiHandler({intermediates: ' ', final: 'A'}, params => this.scrollRight(params));
this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params)); this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params));
this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params)); this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params));
this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params)); this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params));
@@ -223,8 +216,6 @@ export class InputHandler extends Disposable implements IInputHandler {
this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params));
this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params)); this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params));
this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params)); this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params));
this._parser.setCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params));
this._parser.setCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params));
/** /**
* execute handler * execute handler
@@ -333,41 +324,40 @@ export class InputHandler extends Disposable implements IInputHandler {
super.dispose(); super.dispose();
} }
public parse(data: string | Uint8Array): void { public parse(data: string): void {
let buffer = this._bufferService.buffer; let buffer = this._bufferService.buffer;
const cursorStartX = buffer.x; const cursorStartX = buffer.x;
const cursorStartY = buffer.y; const cursorStartY = buffer.y;
this._logService.debug('parsing data', data); this._logService.debug('parsing data', data);
// resize input buffer if needed
if (this._parseBuffer.length < data.length) { if (this._parseBuffer.length < data.length) {
if (this._parseBuffer.length < MAX_PARSEBUFFER_LENGTH) { this._parseBuffer = new Uint32Array(data.length);
this._parseBuffer = new Uint32Array(Math.min(data.length, MAX_PARSEBUFFER_LENGTH));
}
}
// process big data in smaller chunks
if (data.length > MAX_PARSEBUFFER_LENGTH) {
for (let i = 0; i < data.length; i += MAX_PARSEBUFFER_LENGTH) {
const end = i + MAX_PARSEBUFFER_LENGTH < data.length ? i + MAX_PARSEBUFFER_LENGTH : data.length;
const len = (typeof data === 'string')
? this._stringDecoder.decode(data.substring(i, end), this._parseBuffer)
: this._utf8Decoder.decode(data.subarray(i, end), this._parseBuffer);
this._parser.parse(this._parseBuffer, len);
}
} else {
const len = (typeof data === 'string')
? this._stringDecoder.decode(data, this._parseBuffer)
: this._utf8Decoder.decode(data, this._parseBuffer);
this._parser.parse(this._parseBuffer, len);
} }
this._parser.parse(this._parseBuffer, this._stringDecoder.decode(data, this._parseBuffer));
buffer = this._bufferService.buffer;
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
this._onCursorMove.fire();
}
}
public parseUtf8(data: Uint8Array): void {
let buffer = this._bufferService.buffer;
const cursorStartX = buffer.x;
const cursorStartY = buffer.y;
this._logService.debug('parsing data', data);
if (this._parseBuffer.length < data.length) {
this._parseBuffer = new Uint32Array(data.length);
}
this._parser.parse(this._parseBuffer, this._utf8Decoder.decode(data, this._parseBuffer));
buffer = this._bufferService.buffer; buffer = this._bufferService.buffer;
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
this._onCursorMove.fire(); this._onCursorMove.fire();
} }
this._terminal.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
} }
public print(data: Uint32Array, start: number, end: number): void { public print(data: Uint32Array, start: number, end: number): void {
@@ -1056,108 +1046,18 @@ export class InputHandler extends Disposable implements IInputHandler {
* CSI Ps T Scroll down Ps lines (default = 1) (SD). * CSI Ps T Scroll down Ps lines (default = 1) (SD).
*/ */
public scrollDown(params: IParams): void { public scrollDown(params: IParams): void {
let param = params.params[0] || 1; if (params.length < 2) {
let param = params.params[0] || 1;
// make buffer local for faster access // make buffer local for faster access
const buffer = this._bufferService.buffer; const buffer = this._bufferService.buffer;
while (param--) { while (param--) {
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1); buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(this._terminal.eraseAttrData()));
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
} }
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
}
/**
* CSI Ps SP @ Scroll left Ps columns (default = 1) (SL) ECMA-48
*
* Notation: (Pn)
* Representation: CSI Pn 02/00 04/00
* Parameter default value: Pn = 1
* SL causes the data in the presentation component to be moved by n character positions
* if the line orientation is horizontal, or by n line positions if the line orientation
* is vertical, such that the data appear to move to the left; where n equals the value of Pn.
* The active presentation position is not affected by this control function.
*
* Supported:
* - always left shift (no line orientation setting respected)
*/
public scrollLeft(params: IParams): void {
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
return;
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = buffer.lines.get(buffer.ybase + y);
line.deleteCells(0, param, buffer.getNullCell(this._terminal.eraseAttrData()));
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
}
/**
* CSI Ps SP A Scroll right Ps columns (default = 1) (SR) ECMA-48
*
* Notation: (Pn)
* Representation: CSI Pn 02/00 04/01
* Parameter default value: Pn = 1
* SR causes the data in the presentation component to be moved by n character positions
* if the line orientation is horizontal, or by n line positions if the line orientation
* is vertical, such that the data appear to move to the right; where n equals the value of Pn.
* The active presentation position is not affected by this control function.
*
* Supported:
* - always right shift (no line orientation setting respected)
*/
public scrollRight(params: IParams): void {
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
return;
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = buffer.lines.get(buffer.ybase + y);
line.insertCells(0, param, buffer.getNullCell(this._terminal.eraseAttrData()));
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
}
/**
* CSI Pm ' }
* Insert Ps Column(s) (default = 1) (DECIC), VT420 and up.
*/
public insertColumns(params: IParams): void {
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
return;
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = this._bufferService.buffer.lines.get(buffer.ybase + y);
line.insertCells(buffer.x, param, buffer.getNullCell(this._terminal.eraseAttrData()));
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
}
/**
* CSI Pm ' ~
* Delete Ps Column(s) (default = 1) (DECDC), VT420 and up.
*/
public deleteColumns(params: IParams): void {
const buffer = this._bufferService.buffer;
if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) {
return;
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = buffer.lines.get(buffer.ybase + y);
line.deleteCells(buffer.x, param, buffer.getNullCell(this._terminal.eraseAttrData()));
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
} }
/** /**
@@ -1434,14 +1334,16 @@ export class InputHandler extends Disposable implements IInputHandler {
// focusout: ^[[O // focusout: ^[[O
this._terminal.sendFocus = true; this._terminal.sendFocus = true;
break; break;
case 1005: // utf8 ext mode mouse - removed in #2507 case 1005: // utf8 ext mode mouse
this._logService.debug('DECSET 1005 not supported (see #2507)'); // for wide terminals
// simply encodes large values as utf8 characters
this._coreMouseService.activeEncoding = 'UTF8';
break; break;
case 1006: // sgr ext mode mouse case 1006: // sgr ext mode mouse
this._coreMouseService.activeEncoding = 'SGR'; this._coreMouseService.activeEncoding = 'SGR';
break; break;
case 1015: // urxvt ext mode mouse - removed in #2507 case 1015: // urxvt ext mode mouse
this._logService.debug('DECSET 1015 not supported (see #2507)'); this._coreMouseService.activeEncoding = 'URXVT';
break; break;
case 25: // show cursor case 25: // show cursor
this._terminal.cursorHidden = false; this._terminal.cursorHidden = false;
@@ -1605,14 +1507,14 @@ export class InputHandler extends Disposable implements IInputHandler {
case 1004: // send focusin/focusout events case 1004: // send focusin/focusout events
this._terminal.sendFocus = false; this._terminal.sendFocus = false;
break; break;
case 1005: // utf8 ext mode mouse - removed in #2507 case 1005: // utf8 ext mode mouse
this._logService.debug('DECRST 1005 not supported (see #2507)'); this._coreMouseService.activeEncoding = 'DEFAULT';
break; break;
case 1006: // sgr ext mode mouse case 1006: // sgr ext mode mouse
this._coreMouseService.activeEncoding = 'DEFAULT'; this._coreMouseService.activeEncoding = 'DEFAULT';
break; break;
case 1015: // urxvt ext mode mouse - removed in #2507 case 1015: // urxvt ext mode mouse
this._logService.debug('DECRST 1015 not supported (see #2507)'); this._coreMouseService.activeEncoding = 'DEFAULT';
break; break;
case 25: // hide cursor case 25: // hide cursor
this._terminal.cursorHidden = true; this._terminal.cursorHidden = true;
+42 -37
View File
@@ -29,6 +29,11 @@ describe('Terminal', () => {
(<any>term).renderer = new MockRenderer(); (<any>term).renderer = new MockRenderer();
term.viewport = new MockViewport(); term.viewport = new MockViewport();
(<any>term)._compositionHelper = new MockCompositionHelper(); (<any>term)._compositionHelper = new MockCompositionHelper();
// Force synchronous writes
term.write = (data) => {
term.writeBuffer.push(data);
(<any>term)._innerWrite();
};
(<any>term).element = { (<any>term).element = {
classList: { classList: {
toggle: () => { }, toggle: () => { },
@@ -54,18 +59,18 @@ describe('Terminal', () => {
// }); // });
it('should fire the onCursorMove event', (done) => { it('should fire the onCursorMove event', (done) => {
term.onCursorMove(() => done()); term.onCursorMove(() => done());
term.writeSync('foo'); term.write('foo');
}); });
it('should fire the onLineFeed event', (done) => { it('should fire the onLineFeed event', (done) => {
term.onLineFeed(() => done()); term.onLineFeed(() => done());
term.writeSync('\n'); term.write('\n');
}); });
it('should fire a scroll event when scrollback is created', (done) => { it('should fire a scroll event when scrollback is created', (done) => {
term.onScroll(() => done()); term.onScroll(() => done());
term.writeSync('\n'.repeat(INIT_ROWS)); term.write('\n'.repeat(INIT_ROWS));
}); });
it('should fire a scroll event when scrollback is cleared', (done) => { it('should fire a scroll event when scrollback is cleared', (done) => {
term.writeSync('\n'.repeat(INIT_ROWS)); term.write('\n'.repeat(INIT_ROWS));
term.onScroll(() => done()); term.onScroll(() => done());
term.clear(); term.clear();
}); });
@@ -190,7 +195,7 @@ describe('Terminal', () => {
it('should clear a buffer larger than rows', () => { it('should clear a buffer larger than rows', () => {
// Fill the buffer with dummy rows // Fill the buffer with dummy rows
for (let i = 0; i < term.rows * 2; i++) { for (let i = 0; i < term.rows * 2; i++) {
term.writeSync('test\n'); term.write('test\n');
} }
const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y); const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
@@ -239,7 +244,7 @@ describe('Terminal', () => {
assert.equal(e, '\x1b[200~foo\x1b[201~'); assert.equal(e, '\x1b[200~foo\x1b[201~');
done(); done();
}); });
term.writeSync('\x1b[?2004h'); term.write('\x1b[?2004h');
term.paste('foo'); term.paste('foo');
}); });
}); });
@@ -249,7 +254,7 @@ describe('Terminal', () => {
let startYDisp: number; let startYDisp: number;
beforeEach(() => { beforeEach(() => {
for (let i = 0; i < INIT_ROWS * 2; i++) { for (let i = 0; i < INIT_ROWS * 2; i++) {
term.writeSync('test\r\n'); term.writeln('test');
} }
startYDisp = INIT_ROWS + 1; startYDisp = INIT_ROWS + 1;
}); });
@@ -284,7 +289,7 @@ describe('Terminal', () => {
let startYDisp: number; let startYDisp: number;
beforeEach(() => { beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) { for (let i = 0; i < term.rows * 3; i++) {
term.writeSync('test\r\n'); term.writeln('test');
} }
startYDisp = (term.rows * 2) + 1; startYDisp = (term.rows * 2) + 1;
}); });
@@ -307,7 +312,7 @@ describe('Terminal', () => {
describe('scrollToTop', () => { describe('scrollToTop', () => {
beforeEach(() => { beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) { for (let i = 0; i < term.rows * 3; i++) {
term.writeSync('test\r\n'); term.writeln('test');
} }
}); });
it('should scroll to the top', () => { it('should scroll to the top', () => {
@@ -321,7 +326,7 @@ describe('Terminal', () => {
let startYDisp: number; let startYDisp: number;
beforeEach(() => { beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) { for (let i = 0; i < term.rows * 3; i++) {
term.writeSync('test\r\n'); term.writeln('test');
} }
startYDisp = (term.rows * 2) + 1; startYDisp = (term.rows * 2) + 1;
}); });
@@ -342,7 +347,7 @@ describe('Terminal', () => {
let startYDisp: number; let startYDisp: number;
beforeEach(() => { beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) { for (let i = 0; i < term.rows * 3; i++) {
term.writeSync('test\r\n'); term.writeln('test');
} }
startYDisp = (term.rows * 2) + 1; startYDisp = (term.rows * 2) + 1;
}); });
@@ -387,7 +392,7 @@ describe('Terminal', () => {
it('should not scroll down, when a custom keydown handler prevents the event', () => { it('should not scroll down, when a custom keydown handler prevents the event', () => {
// Add some output to the terminal // Add some output to the terminal
for (let i = 0; i < term.rows * 3; i++) { for (let i = 0; i < term.rows * 3; i++) {
term.writeSync('test\r\n'); term.writeln('test');
} }
const startYDisp = (term.rows * 2) + 1; const startYDisp = (term.rows * 2) + 1;
term.attachCustomKeyEventHandler(() => { term.attachCustomKeyEventHandler(() => {
@@ -732,7 +737,7 @@ describe('Terminal', () => {
const high = String.fromCharCode(0xD800); const high = String.fromCharCode(0xD800);
const cell = new CellData(); const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) { for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.writeSync(high + String.fromCharCode(i)); term.write(high + String.fromCharCode(i));
const tchar = term.buffer.lines.get(0).loadCell(0, cell); const tchar = term.buffer.lines.get(0).loadCell(0, cell);
expect(tchar.getChars()).eql(high + String.fromCharCode(i)); expect(tchar.getChars()).eql(high + String.fromCharCode(i));
expect(tchar.getChars().length).eql(2); expect(tchar.getChars().length).eql(2);
@@ -746,7 +751,7 @@ describe('Terminal', () => {
const cell = new CellData(); const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) { for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1; term.buffer.x = term.cols - 1;
term.writeSync(high + String.fromCharCode(i)); term.write(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i)); expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars().length).eql(2); expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars().length).eql(2);
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(''); expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql('');
@@ -759,7 +764,7 @@ describe('Terminal', () => {
for (let i = 0xDC00; i <= 0xDCFF; ++i) { for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1; term.buffer.x = term.cols - 1;
term.wraparoundMode = true; term.wraparoundMode = true;
term.writeSync('a' + high + String.fromCharCode(i)); term.write('a' + high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a'); expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a');
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i)); expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars().length).eql(2); expect(term.buffer.lines.get(1).loadCell(0, cell).getChars().length).eql(2);
@@ -777,7 +782,7 @@ describe('Terminal', () => {
if (width !== 1) { if (width !== 1) {
continue; continue;
} }
term.writeSync('a' + high + String.fromCharCode(i)); term.write('a' + high + String.fromCharCode(i));
// auto wraparound mode should cut off the rest of the line // auto wraparound mode should cut off the rest of the line
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i)); expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(2); expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(2);
@@ -789,8 +794,8 @@ describe('Terminal', () => {
const high = String.fromCharCode(0xD800); const high = String.fromCharCode(0xD800);
const cell = new CellData(); const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) { for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.writeSync(high); term.write(high);
term.writeSync(String.fromCharCode(i)); term.write(String.fromCharCode(i));
const tchar = term.buffer.lines.get(0).loadCell(0, cell); const tchar = term.buffer.lines.get(0).loadCell(0, cell);
expect(tchar.getChars()).eql(high + String.fromCharCode(i)); expect(tchar.getChars()).eql(high + String.fromCharCode(i));
expect(tchar.getChars().length).eql(2); expect(tchar.getChars().length).eql(2);
@@ -804,7 +809,7 @@ describe('Terminal', () => {
describe('unicode - combining characters', () => { describe('unicode - combining characters', () => {
const cell = new CellData(); const cell = new CellData();
it('café', () => { it('café', () => {
term.writeSync('cafe\u0301'); term.write('cafe\u0301');
term.buffer.lines.get(0).loadCell(3, cell); term.buffer.lines.get(0).loadCell(3, cell);
expect(cell.getChars()).eql('e\u0301'); expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2); expect(cell.getChars().length).eql(2);
@@ -812,7 +817,7 @@ describe('Terminal', () => {
}); });
it('café - end of line', () => { it('café - end of line', () => {
term.buffer.x = term.cols - 1 - 3; term.buffer.x = term.cols - 1 - 3;
term.writeSync('cafe\u0301'); term.write('cafe\u0301');
term.buffer.lines.get(0).loadCell(term.cols - 1, cell); term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
expect(cell.getChars()).eql('e\u0301'); expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2); expect(cell.getChars().length).eql(2);
@@ -824,7 +829,7 @@ describe('Terminal', () => {
}); });
it('multiple combined é', () => { it('multiple combined é', () => {
term.wraparoundMode = true; term.wraparoundMode = true;
term.writeSync(Array(100).join('e\u0301')); term.write(Array(100).join('e\u0301'));
for (let i = 0; i < term.cols; ++i) { for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell); term.buffer.lines.get(0).loadCell(i, cell);
expect(cell.getChars()).eql('e\u0301'); expect(cell.getChars()).eql('e\u0301');
@@ -838,7 +843,7 @@ describe('Terminal', () => {
}); });
it('multiple surrogate with combined', () => { it('multiple surrogate with combined', () => {
term.wraparoundMode = true; term.wraparoundMode = true;
term.writeSync(Array(100).join('\uD800\uDC00\u0301')); term.write(Array(100).join('\uD800\uDC00\u0301'));
for (let i = 0; i < term.cols; ++i) { for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell); term.buffer.lines.get(0).loadCell(i, cell);
expect(cell.getChars()).eql('\uD800\uDC00\u0301'); expect(cell.getChars()).eql('\uD800\uDC00\u0301');
@@ -856,18 +861,18 @@ describe('Terminal', () => {
const cell = new CellData(); const cell = new CellData();
it('cursor movement even', () => { it('cursor movement even', () => {
expect(term.buffer.x).eql(0); expect(term.buffer.x).eql(0);
term.writeSync('¥'); term.write('¥');
expect(term.buffer.x).eql(2); expect(term.buffer.x).eql(2);
}); });
it('cursor movement odd', () => { it('cursor movement odd', () => {
term.buffer.x = 1; term.buffer.x = 1;
expect(term.buffer.x).eql(1); expect(term.buffer.x).eql(1);
term.writeSync('¥'); term.write('¥');
expect(term.buffer.x).eql(3); expect(term.buffer.x).eql(3);
}); });
it('line of ¥ even', () => { it('line of ¥ even', () => {
term.wraparoundMode = true; term.wraparoundMode = true;
term.writeSync(Array(50).join('¥')); term.write(Array(50).join('¥'));
for (let i = 0; i < term.cols; ++i) { for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell); term.buffer.lines.get(0).loadCell(i, cell);
if (i % 2) { if (i % 2) {
@@ -888,7 +893,7 @@ describe('Terminal', () => {
it('line of ¥ odd', () => { it('line of ¥ odd', () => {
term.wraparoundMode = true; term.wraparoundMode = true;
term.buffer.x = 1; term.buffer.x = 1;
term.writeSync(Array(50).join('¥')); term.write(Array(50).join('¥'));
for (let i = 1; i < term.cols - 1; ++i) { for (let i = 1; i < term.cols - 1; ++i) {
term.buffer.lines.get(0).loadCell(i, cell); term.buffer.lines.get(0).loadCell(i, cell);
if (!(i % 2)) { if (!(i % 2)) {
@@ -913,7 +918,7 @@ describe('Terminal', () => {
it('line of ¥ with combining odd', () => { it('line of ¥ with combining odd', () => {
term.wraparoundMode = true; term.wraparoundMode = true;
term.buffer.x = 1; term.buffer.x = 1;
term.writeSync(Array(50).join('¥\u0301')); term.write(Array(50).join('¥\u0301'));
for (let i = 1; i < term.cols - 1; ++i) { for (let i = 1; i < term.cols - 1; ++i) {
term.buffer.lines.get(0).loadCell(i, cell); term.buffer.lines.get(0).loadCell(i, cell);
if (!(i % 2)) { if (!(i % 2)) {
@@ -937,7 +942,7 @@ describe('Terminal', () => {
}); });
it('line of ¥ with combining even', () => { it('line of ¥ with combining even', () => {
term.wraparoundMode = true; term.wraparoundMode = true;
term.writeSync(Array(50).join('¥\u0301')); term.write(Array(50).join('¥\u0301'));
for (let i = 0; i < term.cols; ++i) { for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell); term.buffer.lines.get(0).loadCell(i, cell);
if (i % 2) { if (i % 2) {
@@ -958,7 +963,7 @@ describe('Terminal', () => {
it('line of surrogate fullwidth with combining odd', () => { it('line of surrogate fullwidth with combining odd', () => {
term.wraparoundMode = true; term.wraparoundMode = true;
term.buffer.x = 1; term.buffer.x = 1;
term.writeSync(Array(50).join('\ud843\ude6d\u0301')); term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 1; i < term.cols - 1; ++i) { for (let i = 1; i < term.cols - 1; ++i) {
term.buffer.lines.get(0).loadCell(i, cell); term.buffer.lines.get(0).loadCell(i, cell);
if (!(i % 2)) { if (!(i % 2)) {
@@ -982,7 +987,7 @@ describe('Terminal', () => {
}); });
it('line of surrogate fullwidth with combining even', () => { it('line of surrogate fullwidth with combining even', () => {
term.wraparoundMode = true; term.wraparoundMode = true;
term.writeSync(Array(50).join('\ud843\ude6d\u0301')); term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 0; i < term.cols; ++i) { for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell); term.buffer.lines.get(0).loadCell(i, cell);
if (i % 2) { if (i % 2) {
@@ -1005,11 +1010,11 @@ describe('Terminal', () => {
describe('insert mode', () => { describe('insert mode', () => {
const cell = new CellData(); const cell = new CellData();
it('halfwidth - all', () => { it('halfwidth - all', () => {
term.writeSync(Array(9).join('0123456789').slice(-80)); term.write(Array(9).join('0123456789').slice(-80));
term.buffer.x = 10; term.buffer.x = 10;
term.buffer.y = 0; term.buffer.y = 0;
term.insertMode = true; term.insertMode = true;
term.writeSync('abcde'); term.write('abcde');
expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a');
expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('e'); expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('e');
@@ -1017,11 +1022,11 @@ describe('Terminal', () => {
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('4'); expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('4');
}); });
it('fullwidth - insert', () => { it('fullwidth - insert', () => {
term.writeSync(Array(9).join('0123456789').slice(-80)); term.write(Array(9).join('0123456789').slice(-80));
term.buffer.x = 10; term.buffer.x = 10;
term.buffer.y = 0; term.buffer.y = 0;
term.insertMode = true; term.insertMode = true;
term.writeSync('¥¥¥'); term.write('¥¥¥');
expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥'); expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql(''); expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('');
@@ -1030,16 +1035,16 @@ describe('Terminal', () => {
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('3'); expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('3');
}); });
it('fullwidth - right border', () => { it('fullwidth - right border', () => {
term.writeSync(Array(41).join('¥')); term.write(Array(41).join('¥'));
term.buffer.x = 10; term.buffer.x = 10;
term.buffer.y = 0; term.buffer.y = 0;
term.insertMode = true; term.insertMode = true;
term.writeSync('a'); term.write('a');
expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a');
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('¥'); expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced
term.writeSync('b'); term.write('b');
expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('b'); expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('b');
expect(term.buffer.lines.get(0).loadCell(12, cell).getChars()).eql('¥'); expect(term.buffer.lines.get(0).loadCell(12, cell).getChars()).eql('¥');
+222 -24
View File
@@ -62,11 +62,25 @@ import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions,
import { DirtyRowService } from 'common/services/DirtyRowService'; import { DirtyRowService } from 'common/services/DirtyRowService';
import { InstantiationService } from 'common/services/InstantiationService'; import { InstantiationService } from 'common/services/InstantiationService';
import { CoreMouseService } from 'common/services/CoreMouseService'; import { CoreMouseService } from 'common/services/CoreMouseService';
import { WriteBuffer } from 'common/input/WriteBuffer';
// Let it work inside Node.js for automated testing purposes. // Let it work inside Node.js for automated testing purposes.
const document = (typeof window !== 'undefined') ? window.document : null; const document = (typeof window !== 'undefined') ? window.document : null;
/**
* The amount of write requests to queue before sending an XOFF signal to the
* pty process. This number must be small in order for ^C and similar sequences
* to be responsive.
*/
const WRITE_BUFFER_PAUSE_THRESHOLD = 5;
/**
* The max number of ms to spend on writes before allowing the renderer to
* catch up with a 0ms setTimeout. A value of < 33 to keep us close to
* 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
* depends on the time it takes for the renderer to draw the frame.
*/
const WRITE_TIMEOUT_MS = 12;
const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
export class Terminal extends Disposable implements ITerminal, IDisposable, IInputHandlingTerminal { export class Terminal extends Disposable implements ITerminal, IDisposable, IInputHandlingTerminal {
public textarea: HTMLTextAreaElement; public textarea: HTMLTextAreaElement;
@@ -76,7 +90,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
/** /**
* The HTMLElement that the terminal is created in, set by Terminal.open. * The HTMLElement that the terminal is created in, set by Terminal.open.
*/ */
private _parent: HTMLElement | null; private _parent: HTMLElement;
private _document: Document; private _document: Document;
private _viewportScrollArea: HTMLElement; private _viewportScrollArea: HTMLElement;
private _viewportElement: HTMLElement; private _viewportElement: HTMLElement;
@@ -139,8 +153,21 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
public params: (string | number)[]; public params: (string | number)[];
public currentParam: string | number; public currentParam: string | number;
// write buffer // user input states
private _writeBuffer: WriteBuffer; public writeBuffer: string[];
public writeBufferUtf8: Uint8Array[];
private _writeInProgress: boolean;
/**
* Whether _xterm.js_ sent XOFF in order to catch up with the pty process.
* This is a distinct state from writeStopped so that if the user requested
* XOFF via ^S that it will not automatically resume when the writeBuffer goes
* below threshold.
*/
private _xoffSentToCatchUp: boolean;
/** Whether writing has been stopped as a result of XOFF */
// private _writeStopped: boolean;
// Store if user went browsing history in scrollback // Store if user went browsing history in scrollback
private _userScrolling: boolean; private _userScrolling: boolean;
@@ -231,8 +258,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._setupOptionsListeners(); this._setupOptionsListeners();
this._setup(); this._setup();
this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data));
} }
public dispose(): void { public dispose(): void {
@@ -281,6 +306,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.params = []; this.params = [];
this.currentParam = 0; this.currentParam = 0;
// user input states
this.writeBuffer = [];
this.writeBufferUtf8 = [];
this._writeInProgress = false;
this._xoffSentToCatchUp = false;
// this._writeStopped = false;
this._userScrolling = false; this._userScrolling = false;
// Register input handler and refire/handle events // Register input handler and refire/handle events
@@ -383,6 +415,18 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
case 'theme': case 'theme':
this._setTheme(this.optionsService.options.theme); this._setTheme(this.optionsService.options.theme);
break; break;
case 'scrollback':
const newBufferLength = this.rows + this.optionsService.options.scrollback;
if (this.buffer.lines.length > newBufferLength) {
const amountToTrim = this.buffer.lines.length - newBufferLength;
const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);
this.buffer.lines.trimStart(amountToTrim);
this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);
this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);
if (needsRefresh) {
this.refresh(0, this.rows - 1);
}
}
case 'windowsMode': case 'windowsMode':
if (this.optionsService.options.windowsMode) { if (this.optionsService.options.windowsMode) {
if (!this._windowsMode) { if (!this._windowsMode) {
@@ -508,10 +552,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
throw new Error('Terminal requires a parent element.'); throw new Error('Terminal requires a parent element.');
} }
if (!document.body.contains(parent)) {
this._logService.warn('Terminal.open was called on an element that was not attached to the DOM');
}
this._document = this._parent.ownerDocument; this._document = this._parent.ownerDocument;
// Create main element container // Create main element container
@@ -564,8 +604,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
// Performance: Add viewport and helper elements from the fragment // Performance: Add viewport and helper elements from the fragment
this.element.appendChild(fragment); this.element.appendChild(fragment);
this._theme = this.options.theme || this._theme; this._theme = this.options.theme;
this.options.theme = undefined; this.options.theme = null;
this._colorManager = new ColorManager(document, this.options.allowTransparency); this._colorManager = new ColorManager(document, this.options.allowTransparency);
this._colorManager.setTheme(this._theme); this._colorManager.setTheme(this._theme);
@@ -1104,6 +1144,168 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
} }
} }
/**
* Writes raw utf8 bytes to the terminal.
* @param data UintArray with UTF8 bytes to write to the terminal.
*/
public writeUtf8(data: Uint8Array): void {
// Ensure the terminal isn't disposed
if (this._isDisposed) {
return;
}
// Ignore falsy data values
if (!data) {
return;
}
this.writeBufferUtf8.push(data);
// Send XOFF to pause the pty process if the write buffer becomes too large so
// xterm.js can catch up before more data is sent. This is necessary in order
// to keep signals such as ^C responsive.
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBufferUtf8.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
// XOFF - stop pty pipe
// XON will be triggered by emulator before processing data chunk
this._coreService.triggerDataEvent(C0.DC3);
this._xoffSentToCatchUp = true;
}
if (!this._writeInProgress && this.writeBufferUtf8.length > 0) {
// Kick off a write which will write all data in sequence recursively
this._writeInProgress = true;
// Kick off an async innerWrite so more writes can come in while processing data
setTimeout(() => {
this._innerWriteUtf8();
});
}
}
protected _innerWriteUtf8(bufferOffset: number = 0): void {
// Ensure the terminal isn't disposed
if (this._isDisposed) {
this.writeBufferUtf8 = [];
}
const startTime = Date.now();
while (this.writeBufferUtf8.length > bufferOffset) {
const data = this.writeBufferUtf8[bufferOffset];
bufferOffset++;
// If XOFF was sent in order to catch up with the pty process, resume it if
// we reached the end of the writeBuffer to allow more data to come in.
if (this._xoffSentToCatchUp && this.writeBufferUtf8.length === bufferOffset) {
this._coreService.triggerDataEvent(C0.DC1);
this._xoffSentToCatchUp = false;
}
this._inputHandler.parseUtf8(data);
this.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
break;
}
}
if (this.writeBufferUtf8.length > bufferOffset) {
// Allow renderer to catch up before processing the next batch
// trim already processed chunks if we are above threshold
if (bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
this.writeBufferUtf8 = this.writeBufferUtf8.slice(bufferOffset);
bufferOffset = 0;
}
setTimeout(() => this._innerWriteUtf8(bufferOffset), 0);
} else {
this._writeInProgress = false;
this.writeBufferUtf8 = [];
}
}
/**
* Writes text to the terminal.
* @param data The text to write to the terminal.
*/
public write(data: string): void {
// Ensure the terminal isn't disposed
if (this._isDisposed) {
return;
}
// Ignore falsy data values (including the empty string)
if (!data) {
return;
}
this.writeBuffer.push(data);
// Send XOFF to pause the pty process if the write buffer becomes too large so
// xterm.js can catch up before more data is sent. This is necessary in order
// to keep signals such as ^C responsive.
if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
// XOFF - stop pty pipe
// XON will be triggered by emulator before processing data chunk
this._coreService.triggerDataEvent(C0.DC3);
this._xoffSentToCatchUp = true;
}
if (!this._writeInProgress && this.writeBuffer.length > 0) {
// Kick off a write which will write all data in sequence recursively
this._writeInProgress = true;
// Kick off an async innerWrite so more writes can come in while processing data
setTimeout(() => {
this._innerWrite();
});
}
}
protected _innerWrite(bufferOffset: number = 0): void {
// Ensure the terminal isn't disposed
if (this._isDisposed) {
this.writeBuffer = [];
}
const startTime = Date.now();
while (this.writeBuffer.length > bufferOffset) {
const data = this.writeBuffer[bufferOffset];
bufferOffset++;
// If XOFF was sent in order to catch up with the pty process, resume it if
// we reached the end of the writeBuffer to allow more data to come in.
if (this._xoffSentToCatchUp && this.writeBuffer.length === bufferOffset) {
this._coreService.triggerDataEvent(C0.DC1);
this._xoffSentToCatchUp = false;
}
this._inputHandler.parse(data);
this.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
break;
}
}
if (this.writeBuffer.length > bufferOffset) {
// Allow renderer to catch up before processing the next batch
// trim already processed chunks if we are above threshold
if (bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
this.writeBuffer = this.writeBuffer.slice(bufferOffset);
bufferOffset = 0;
}
setTimeout(() => this._innerWrite(bufferOffset), 0);
} else {
this._writeInProgress = false;
this.writeBuffer = [];
}
}
/**
* Writes text to the terminal, followed by a break line character (\n).
* @param data The text to write to the terminal.
*/
public writeln(data: string): void {
this.write(data + '\r\n');
}
public paste(data: string): void { public paste(data: string): void {
paste(data, this.textarea, this.bracketedPasteMode, this._coreService); paste(data, this.textarea, this.bracketedPasteMode, this._coreService);
} }
@@ -1461,10 +1663,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._charSizeService.measure(); this._charSizeService.measure();
} }
// Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an
// invalid location
this.viewport.syncScrollArea(true);
this.refresh(0, this.rows - 1); this.refresh(0, this.rows - 1);
this._onResize.fire({ cols: x, rows: y }); this._onResize.fire({ cols: x, rows: y });
} }
@@ -1545,6 +1743,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
const customKeyEventHandler = this._customKeyEventHandler; const customKeyEventHandler = this._customKeyEventHandler;
const inputHandler = this._inputHandler; const inputHandler = this._inputHandler;
const cursorState = this.cursorState; const cursorState = this.cursorState;
const writeBuffer = this.writeBuffer;
const writeBufferUtf8 = this.writeBufferUtf8;
const writeInProgress = this._writeInProgress;
const xoffSentToCatchUp = this._xoffSentToCatchUp;
const userScrolling = this._userScrolling; const userScrolling = this._userScrolling;
this._setup(); this._setup();
@@ -1559,6 +1761,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._customKeyEventHandler = customKeyEventHandler; this._customKeyEventHandler = customKeyEventHandler;
this._inputHandler = inputHandler; this._inputHandler = inputHandler;
this.cursorState = cursorState; this.cursorState = cursorState;
this.writeBuffer = writeBuffer;
this.writeBufferUtf8 = writeBufferUtf8;
this._writeInProgress = writeInProgress;
this._xoffSentToCatchUp = xoffSentToCatchUp;
this._userScrolling = userScrolling; this._userScrolling = userScrolling;
// do a full screen refresh // do a full screen refresh
@@ -1589,14 +1795,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
// return this.options.bellStyle === 'sound' || // return this.options.bellStyle === 'sound' ||
// this.options.bellStyle === 'both'; // this.options.bellStyle === 'both';
} }
public write(data: string | Uint8Array, callback?: () => void): void {
this._writeBuffer.write(data, callback);
}
public writeSync(data: string | Uint8Array): void {
this._writeBuffer.writeSync(data);
}
} }
/** /**
+4
View File
@@ -19,6 +19,10 @@ import { IParams, IFunctionIdentifier } from 'common/parser/Types';
import { ISelectionService } from 'browser/services/Services'; import { ISelectionService } from 'browser/services/Services';
export class TestTerminal extends Terminal { export class TestTerminal extends Terminal {
writeSync(data: string): void {
this.writeBuffer.push(data);
this._innerWrite();
}
keyDown(ev: any): boolean { return this._keyDown(ev); } keyDown(ev: any): boolean { return this._keyDown(ev); }
keyPress(ev: any): boolean { return this._keyPress(ev); } keyPress(ev: any): boolean { return this._keyPress(ev); }
} }
+8 -8
View File
@@ -73,7 +73,8 @@ export interface ICompositionHelper {
* Calls the parser and handles actions generated by the parser. * Calls the parser and handles actions generated by the parser.
*/ */
export interface IInputHandler { export interface IInputHandler {
parse(data: string | Uint8Array): void; parse(data: string): void;
parseUtf8(data: Uint8Array): void;
print(data: Uint32Array, start: number, end: number): void; print(data: Uint32Array, start: number, end: number): void;
/** C0 BEL */ bell(): void; /** C0 BEL */ bell(): void;
@@ -85,9 +86,7 @@ export interface IInputHandler {
/** C0 SI */ shiftIn(): void; /** C0 SI */ shiftIn(): void;
/** CSI @ */ insertChars(params: IParams): void; /** CSI @ */ insertChars(params: IParams): void;
/** CSI SP @ */ scrollLeft(params: IParams): void;
/** CSI A */ cursorUp(params: IParams): void; /** CSI A */ cursorUp(params: IParams): void;
/** CSI SP A */ scrollRight(params: IParams): void;
/** CSI B */ cursorDown(params: IParams): void; /** CSI B */ cursorDown(params: IParams): void;
/** CSI C */ cursorForward(params: IParams): void; /** CSI C */ cursorForward(params: IParams): void;
/** CSI D */ cursorBackward(params: IParams): void; /** CSI D */ cursorBackward(params: IParams): void;
@@ -123,8 +122,6 @@ export interface IInputHandler {
/** CSI r */ setScrollRegion(params: IParams, collect?: string): void; /** CSI r */ setScrollRegion(params: IParams, collect?: string): void;
/** CSI s */ saveCursor(params: IParams): void; /** CSI s */ saveCursor(params: IParams): void;
/** CSI u */ restoreCursor(params: IParams): void; /** CSI u */ restoreCursor(params: IParams): void;
/** CSI ' } */ insertColumns(params: IParams): void;
/** CSI ' ~ */ deleteColumns(params: IParams): void;
/** OSC 0 /** OSC 0
OSC 2 */ setTitle(data: string): void; OSC 2 */ setTitle(data: string): void;
/** ESC E */ nextLine(): void; /** ESC E */ nextLine(): void;
@@ -154,6 +151,7 @@ export interface IInputHandler {
export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
screenElement: HTMLElement; screenElement: HTMLElement;
browser: IBrowser; browser: IBrowser;
writeBuffer: string[];
cursorHidden: boolean; cursorHidden: boolean;
cursorState: number; cursorState: number;
buffer: IBuffer; buffer: IBuffer;
@@ -177,7 +175,7 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc
// Portions of the public API that are required by the internal Terminal // Portions of the public API that are required by the internal Terminal
export interface IPublicTerminal extends IDisposable { export interface IPublicTerminal extends IDisposable {
textarea: HTMLTextAreaElement | undefined; textarea: HTMLTextAreaElement;
rows: number; rows: number;
cols: number; cols: number;
buffer: IBuffer; buffer: IBuffer;
@@ -194,6 +192,7 @@ export interface IPublicTerminal extends IDisposable {
blur(): void; blur(): void;
focus(): void; focus(): void;
resize(columns: number, rows: number): void; resize(columns: number, rows: number): void;
writeln(data: string): void;
open(parent: HTMLElement): void; open(parent: HTMLElement): void;
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
@@ -219,7 +218,8 @@ export interface IPublicTerminal extends IDisposable {
scrollToBottom(): void; scrollToBottom(): void;
scrollToLine(line: number): void; scrollToLine(line: number): void;
clear(): void; clear(): void;
write(data: string | Uint8Array, callback?: () => void): void; write(data: string): void;
writeUtf8(data: Uint8Array): void;
paste(data: string): void; paste(data: string): void;
refresh(start: number, end: number): void; refresh(start: number, end: number): void;
reset(): void; reset(): void;
@@ -230,7 +230,7 @@ export interface IBufferAccessor {
} }
export interface IElementAccessor { export interface IElementAccessor {
readonly element: HTMLElement | undefined; readonly element: HTMLElement;
} }
export interface ILinkifierAccessor { export interface ILinkifierAccessor {
+1 -3
View File
@@ -306,9 +306,7 @@ export class Linkifier implements ILinkifier {
e => { e => {
this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
if (matcher.hoverTooltipCallback) { if (matcher.hoverTooltipCallback) {
// Note that IViewportRange use 1-based coordinates to align with escape sequences such matcher.hoverTooltipCallback(e, uri);
// as CUP which use 1,1 as the default for row/col
matcher.hoverTooltipCallback(e, uri, { start: { x: x1, y: y1 }, end: { x: x2, y: y2 } });
} }
}, },
() => { () => {
+3 -14
View File
@@ -35,7 +35,7 @@ export interface IPartialColorSet {
export interface IViewport extends IDisposable { export interface IViewport extends IDisposable {
scrollBarWidth: number; scrollBarWidth: number;
syncScrollArea(immediate?: boolean): void; syncScrollArea(): void;
getLinesScrolled(ev: WheelEvent): number; getLinesScrolled(ev: WheelEvent): number;
onWheel(ev: WheelEvent): boolean; onWheel(ev: WheelEvent): boolean;
onTouchStart(ev: TouchEvent): void; onTouchStart(ev: TouchEvent): void;
@@ -43,25 +43,14 @@ export interface IViewport extends IDisposable {
onThemeChange(colors: IColorSet): void; onThemeChange(colors: IColorSet): void;
} }
export interface IViewportRange {
start: IViewportRangePosition;
end: IViewportRangePosition;
}
export interface IViewportRangePosition {
x: number;
y: number;
}
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void; export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
export type LinkMatcherHoverTooltipCallback = (event: MouseEvent, uri: string, position: IViewportRange) => void;
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
export interface ILinkMatcher { export interface ILinkMatcher {
id: number; id: number;
regex: RegExp; regex: RegExp;
handler: LinkMatcherHandler; handler: LinkMatcherHandler;
hoverTooltipCallback?: LinkMatcherHoverTooltipCallback; hoverTooltipCallback?: LinkMatcherHandler;
hoverLeaveCallback?: () => void; hoverLeaveCallback?: () => void;
matchIndex?: number; matchIndex?: number;
validationCallback?: LinkMatcherValidationCallback; validationCallback?: LinkMatcherValidationCallback;
@@ -107,7 +96,7 @@ export interface ILinkMatcherOptions {
/** /**
* A callback that fires when the mouse hovers over a link. * A callback that fires when the mouse hovers over a link.
*/ */
tooltipCallback?: LinkMatcherHoverTooltipCallback; tooltipCallback?: LinkMatcherHandler;
/** /**
* A callback that fires when the mouse leaves a link that was hovered. * A callback that fires when the mouse leaves a link that was hovered.
*/ */
+11 -30
View File
@@ -7,7 +7,7 @@ import { Disposable } from 'common/Lifecycle';
import { addDisposableDomListener } from 'browser/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle';
import { IColorSet, IViewport } from 'browser/Types'; import { IColorSet, IViewport } from 'browser/Types';
import { ICharSizeService, IRenderService } from 'browser/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services';
import { IBufferService, IOptionsService } from 'common/services/Services'; import { IBufferService } from 'common/services/Services';
const FALLBACK_SCROLL_BAR_WIDTH = 15; const FALLBACK_SCROLL_BAR_WIDTH = 15;
@@ -37,7 +37,6 @@ export class Viewport extends Disposable implements IViewport {
private readonly _viewportElement: HTMLElement, private readonly _viewportElement: HTMLElement,
private readonly _scrollArea: HTMLElement, private readonly _scrollArea: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService, @IBufferService private readonly _bufferService: IBufferService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICharSizeService private readonly _charSizeService: ICharSizeService, @ICharSizeService private readonly _charSizeService: ICharSizeService,
@IRenderService private readonly _renderService: IRenderService @IRenderService private readonly _renderService: IRenderService
) { ) {
@@ -61,14 +60,7 @@ export class Viewport extends Disposable implements IViewport {
* Refreshes row height, setting line-height, viewport height and scroll area height if * Refreshes row height, setting line-height, viewport height and scroll area height if
* necessary. * necessary.
*/ */
private _refresh(immediate: boolean): void { private _refresh(): void {
if (immediate) {
this._innerRefresh();
if (this._refreshAnimationFrame !== null) {
cancelAnimationFrame(this._refreshAnimationFrame);
}
return;
}
if (this._refreshAnimationFrame === null) { if (this._refreshAnimationFrame === null) {
this._refreshAnimationFrame = requestAnimationFrame(() => this._innerRefresh()); this._refreshAnimationFrame = requestAnimationFrame(() => this._innerRefresh());
} }
@@ -96,39 +88,40 @@ export class Viewport extends Disposable implements IViewport {
this._refreshAnimationFrame = null; this._refreshAnimationFrame = null;
} }
/** /**
* Updates dimensions and synchronizes the scroll area if necessary. * Updates dimensions and synchronizes the scroll area if necessary.
*/ */
public syncScrollArea(immediate: boolean = false): void { public syncScrollArea(): void {
// If buffer height changed // If buffer height changed
if (this._lastRecordedBufferLength !== this._bufferService.buffer.lines.length) { if (this._lastRecordedBufferLength !== this._bufferService.buffer.lines.length) {
this._lastRecordedBufferLength = this._bufferService.buffer.lines.length; this._lastRecordedBufferLength = this._bufferService.buffer.lines.length;
this._refresh(immediate); this._refresh();
return; return;
} }
// If viewport height changed // If viewport height changed
if (this._lastRecordedViewportHeight !== this._renderService.dimensions.canvasHeight) { if (this._lastRecordedViewportHeight !== this._renderService.dimensions.canvasHeight) {
this._refresh(immediate); this._refresh();
return; return;
} }
// If the buffer position doesn't match last scroll top // If the buffer position doesn't match last scroll top
const newScrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight; const newScrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight;
if (this._lastScrollTop !== newScrollTop) { if (this._lastScrollTop !== newScrollTop) {
this._refresh(immediate); this._refresh();
return; return;
} }
// If element's scroll top changed, this can happen when hiding the element // If element's scroll top changed, this can happen when hiding the element
if (this._lastScrollTop !== this._viewportElement.scrollTop) { if (this._lastScrollTop !== this._viewportElement.scrollTop) {
this._refresh(immediate); this._refresh();
return; return;
} }
// If row height changed // If row height changed
if (this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) { if (this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
this._refresh(immediate); this._refresh();
return; return;
} }
} }
@@ -198,7 +191,7 @@ export class Viewport extends Disposable implements IViewport {
} }
// Fallback to WheelEvent.DOM_DELTA_PIXEL // Fallback to WheelEvent.DOM_DELTA_PIXEL
let amount = this._applyScrollModifier(ev.deltaY, ev); let amount = ev.deltaY;
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
amount *= this._currentRowHeight; amount *= this._currentRowHeight;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
@@ -219,7 +212,7 @@ export class Viewport extends Disposable implements IViewport {
} }
// Fallback to WheelEvent.DOM_DELTA_LINE // Fallback to WheelEvent.DOM_DELTA_LINE
let amount = this._applyScrollModifier(ev.deltaY, ev); let amount = ev.deltaY;
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) { if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
amount /= this._currentRowHeight + 0.0; // Prevent integer division amount /= this._currentRowHeight + 0.0; // Prevent integer division
this._wheelPartialScroll += amount; this._wheelPartialScroll += amount;
@@ -231,18 +224,6 @@ export class Viewport extends Disposable implements IViewport {
return amount; return amount;
} }
private _applyScrollModifier(amount: number, ev: WheelEvent): number {
const modifier = this._optionsService.options.fastScrollModifier;
// Multiply the scroll speed when the modifier is down
if ((modifier === 'alt' && ev.altKey) ||
(modifier === 'ctrl' && ev.ctrlKey) ||
(modifier === 'shift' && ev.shiftKey)) {
return amount * this._optionsService.options.fastScrollSensitivity * this._optionsService.options.scrollSensitivity;
}
return amount * this._optionsService.options.scrollSensitivity;
}
/** /**
* Handles the touchstart event, recording the touch occurred. * Handles the touchstart event, recording the touch occurred.
* @param ev The touch event. * @param ev The touch event.
@@ -303,6 +303,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry {
// current range // current range
ranges[i - 1][1] = Math.max(newRange[1], range[1]); ranges[i - 1][1] = Math.max(newRange[1], range[1]);
ranges.splice(i, 1); ranges.splice(i, 1);
inRange = false;
return ranges; return ranges;
} }
@@ -8,7 +8,7 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { AttributeData } from 'common/buffer/AttributeData'; import { AttributeData } from 'common/buffer/AttributeData';
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData'; import { CellData } from 'common/buffer/CellData';
import { IOptionsService } from 'common/services/Services'; import { ITerminalOptions, IOptionsService } from 'common/services/Services';
export const BOLD_CLASS = 'xterm-bold'; export const BOLD_CLASS = 'xterm-bold';
export const DIM_CLASS = 'xterm-dim'; export const DIM_CLASS = 'xterm-dim';
+2 -2
View File
@@ -210,7 +210,7 @@ export class SelectionService implements ISelectionService {
for (let i = start[1] + 1; i <= end[1] - 1; i++) { for (let i = start[1] + 1; i <= end[1] - 1; i++) {
const bufferLine = buffer.lines.get(i); const bufferLine = buffer.lines.get(i);
const lineText = buffer.translateBufferLineToString(i, true); const lineText = buffer.translateBufferLineToString(i, true);
if (bufferLine && bufferLine.isWrapped) { if (bufferLine!.isWrapped) {
result[result.length - 1] += lineText; result[result.length - 1] += lineText;
} else { } else {
result.push(lineText); result.push(lineText);
@@ -221,7 +221,7 @@ export class SelectionService implements ISelectionService {
if (start[1] !== end[1]) { if (start[1] !== end[1]) {
const bufferLine = buffer.lines.get(end[1]); const bufferLine = buffer.lines.get(end[1]);
const lineText = buffer.translateBufferLineToString(end[1], true, 0, end[0]); const lineText = buffer.translateBufferLineToString(end[1], true, 0, end[0]);
if (bufferLine && bufferLine!.isWrapped) { if (bufferLine!.isWrapped) {
result[result.length - 1] += lineText; result[result.length - 1] += lineText;
} else { } else {
result.push(lineText); result.push(lineText);
+12 -18
View File
@@ -167,25 +167,19 @@ export class Buffer implements IBuffer {
if (this._rows < newRows) { if (this._rows < newRows) {
for (let y = this._rows; y < newRows; y++) { for (let y = this._rows; y < newRows; y++) {
if (this.lines.length < newRows + this.ybase) { if (this.lines.length < newRows + this.ybase) {
if (this._optionsService.options.windowsMode) { if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
// Just add the new missing rows on Windows as conpty reprints the screen with it's // There is room above the buffer and there are no empty elements below the line,
// view of the world. Once a line enters scrollback for conpty it remains there // scroll up
this.lines.push(new BufferLine(newCols, nullCell)); this.ybase--;
} else { addToY++;
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) { if (this.ydisp > 0) {
// There is room above the buffer and there are no empty elements below the line, // Viewport is at the top of the buffer, must increase downwards
// scroll up this.ydisp--;
this.ybase--;
addToY++;
if (this.ydisp > 0) {
// Viewport is at the top of the buffer, must increase downwards
this.ydisp--;
}
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
this.lines.push(new BufferLine(newCols, nullCell));
} }
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
this.lines.push(new BufferLine(newCols, nullCell));
} }
} }
} }
+1 -1
View File
@@ -5,7 +5,7 @@
import { CharData, IBufferLine, ICellData } from 'common/Types'; import { CharData, IBufferLine, ICellData } from 'common/Types';
import { stringFromCodePoint } from 'common/input/TextDecoder'; import { stringFromCodePoint } from 'common/input/TextDecoder';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants'; import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData'; import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData'; import { AttributeData } from 'common/buffer/AttributeData';
-1
View File
@@ -29,7 +29,6 @@ export class Marker extends Disposable implements IMarker {
return; return;
} }
this.isDisposed = true; this.isDisposed = true;
this.line = -1;
// Emit before super.dispose such that dispose listeners get a change to react // Emit before super.dispose such that dispose listeners get a change to react
this._onDispose.fire(); this._onDispose.fire();
} }
-12
View File
@@ -108,12 +108,6 @@ describe('Keyboard', () => {
it('should return \\x1b[5C for alt+right', () => { it('should return \\x1b[5C for alt+right', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\x1b[1;5C'); // CSI 5 C assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\x1b[1;5C'); // CSI 5 C
}); });
it('should return \\x1b[5D for alt+up', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: false }).key, '\x1b[1;5A'); // CSI 5 D
});
it('should return \\x1b[5C for alt+down', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: false }).key, '\x1b[1;5B'); // CSI 5 C
});
it('should return \\x1ba for alt+a', () => { it('should return \\x1ba for alt+a', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: false }).key, '\x1ba'); assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: false }).key, '\x1ba');
}); });
@@ -126,12 +120,6 @@ describe('Keyboard', () => {
it('should return \\x1bf for alt+right', () => { it('should return \\x1bf for alt+right', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\x1bf'); // CSI 5 C assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\x1bf'); // CSI 5 C
}); });
it('should return \\x1bb for alt+up', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: true }).key, '\x1b[1;3A'); // CSI 5 D
});
it('should return \\x1bf for alt+down', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: true }).key, '\x1b[1;3B'); // CSI 5 C
});
it('should return undefined for alt+a', () => { it('should return undefined for alt+a', () => {
assert.strictEqual(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true }).key, undefined), { isMac: true }; assert.strictEqual(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true }).key, undefined), { isMac: true };
}); });
+14 -23
View File
@@ -113,16 +113,13 @@ export function evaluateKeyboardEvent(
break; break;
case 37: case 37:
// left-arrow // left-arrow
if (ev.metaKey) {
break;
}
if (modifiers) { if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D'; result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';
// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
// http://unix.stackexchange.com/a/108106 // http://unix.stackexchange.com/a/108106
// macOS uses different escape sequences than linux // macOS uses different escape sequences than linux
if (result.key === C0.ESC + '[1;3D') { if (result.key === C0.ESC + '[1;3D') {
result.key = C0.ESC + (isMac ? 'b' : '[1;5D'); result.key = isMac ? C0.ESC + 'b' : C0.ESC + '[1;5D';
} }
} else if (applicationCursorMode) { } else if (applicationCursorMode) {
result.key = C0.ESC + 'OD'; result.key = C0.ESC + 'OD';
@@ -132,16 +129,13 @@ export function evaluateKeyboardEvent(
break; break;
case 39: case 39:
// right-arrow // right-arrow
if (ev.metaKey) {
break;
}
if (modifiers) { if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C'; result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';
// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
// http://unix.stackexchange.com/a/108106 // http://unix.stackexchange.com/a/108106
// macOS uses different escape sequences than linux // macOS uses different escape sequences than linux
if (result.key === C0.ESC + '[1;3C') { if (result.key === C0.ESC + '[1;3C') {
result.key = C0.ESC + (isMac ? 'f' : '[1;5C'); result.key = isMac ? C0.ESC + 'f' : C0.ESC + '[1;5C';
} }
} else if (applicationCursorMode) { } else if (applicationCursorMode) {
result.key = C0.ESC + 'OC'; result.key = C0.ESC + 'OC';
@@ -151,15 +145,11 @@ export function evaluateKeyboardEvent(
break; break;
case 38: case 38:
// up-arrow // up-arrow
if (ev.metaKey) {
break;
}
if (modifiers) { if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A'; result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';
// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
// http://unix.stackexchange.com/a/108106 // http://unix.stackexchange.com/a/108106
// macOS uses different escape sequences than linux if (result.key === C0.ESC + '[1;3A') {
if (!isMac && result.key === C0.ESC + '[1;3A') {
result.key = C0.ESC + '[1;5A'; result.key = C0.ESC + '[1;5A';
} }
} else if (applicationCursorMode) { } else if (applicationCursorMode) {
@@ -170,15 +160,11 @@ export function evaluateKeyboardEvent(
break; break;
case 40: case 40:
// down-arrow // down-arrow
if (ev.metaKey) {
break;
}
if (modifiers) { if (modifiers) {
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B'; result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';
// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
// http://unix.stackexchange.com/a/108106 // http://unix.stackexchange.com/a/108106
// macOS uses different escape sequences than linux if (result.key === C0.ESC + '[1;3B') {
if (!isMac && result.key === C0.ESC + '[1;3B') {
result.key = C0.ESC + '[1;5B'; result.key = C0.ESC + '[1;5B';
} }
} else if (applicationCursorMode) { } else if (applicationCursorMode) {
@@ -330,18 +316,23 @@ export function evaluateKeyboardEvent(
if (ev.keyCode >= 65 && ev.keyCode <= 90) { if (ev.keyCode >= 65 && ev.keyCode <= 90) {
result.key = String.fromCharCode(ev.keyCode - 64); result.key = String.fromCharCode(ev.keyCode - 64);
} else if (ev.keyCode === 32) { } else if (ev.keyCode === 32) {
result.key = C0.NUL; // NUL
result.key = String.fromCharCode(0);
} else if (ev.keyCode >= 51 && ev.keyCode <= 55) { } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {
// escape, file sep, group sep, record sep, unit sep // escape, file sep, group sep, record sep, unit sep
result.key = String.fromCharCode(ev.keyCode - 51 + 27); result.key = String.fromCharCode(ev.keyCode - 51 + 27);
} else if (ev.keyCode === 56) { } else if (ev.keyCode === 56) {
result.key = C0.DEL; // delete
result.key = String.fromCharCode(127);
} else if (ev.keyCode === 219) { } else if (ev.keyCode === 219) {
result.key = C0.ESC; // ^[ - Control Sequence Introducer (CSI)
result.key = String.fromCharCode(27);
} else if (ev.keyCode === 220) { } else if (ev.keyCode === 220) {
result.key = C0.FS; // ^\ - String Terminator (ST)
result.key = String.fromCharCode(28);
} else if (ev.keyCode === 221) { } else if (ev.keyCode === 221) {
result.key = C0.GS; // ^] - Operating System Command (OSC)
result.key = String.fromCharCode(29);
} }
} else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) { } else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) {
// On macOS this is a third level shift when !macOptionIsMeta. Use <Esc> instead. // On macOS this is a third level shift when !macOptionIsMeta. Use <Esc> instead.
-11
View File
@@ -7,7 +7,6 @@ import { assert } from 'chai';
import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32, utf32ToString } from 'common/input/TextDecoder'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32, utf32ToString } from 'common/input/TextDecoder';
import { encode } from 'utf8'; import { encode } from 'utf8';
// convert UTF32 codepoints to string // convert UTF32 codepoints to string
function toString(data: Uint32Array, length: number): string { function toString(data: Uint32Array, length: number): string {
if ((String as any).fromCodePoint) { if ((String as any).fromCodePoint) {
@@ -215,16 +214,6 @@ describe('text encodings', () => {
} }
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
}); });
it('test break after 3 bytes - issue #2495', () => {
const decoder = new Utf8ToUtf32();
const target = new Uint32Array(5);
const utf8Data = fromByteString('\xf0\xa0\x9c\x8e');
let written = decoder.decode(utf8Data.slice(0, 3), target);
assert.equal(written, 0);
written = decoder.decode(utf8Data.slice(3), target);
assert.equal(written, 1);
assert(toString(target, written), '𠜎');
});
}); });
}); });
}); });
+1 -1
View File
@@ -194,7 +194,7 @@ export class Utf8ToUtf32 {
target[size++] = cp; target[size++] = cp;
} }
} else { } else {
if (cp < 0x010000 || cp > 0x10FFFF) { if (codepoint < 0x010000 || codepoint > 0x10FFFF) {
// illegal codepoint // illegal codepoint
} else { } else {
target[size++] = cp; target[size++] = cp;
-88
View File
@@ -1,88 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { WriteBuffer } from './WriteBuffer';
declare let Buffer: any;
function toBytes(s: string): Uint8Array {
return Buffer.from(s);
}
function fromBytes(bytes: Uint8Array): string {
return bytes.toString();
}
describe('WriteBuffer', () => {
let wb: WriteBuffer;
let stack: (string | Uint8Array)[] = [];
let cbStack: string[] = [];
beforeEach(() => {
stack = [];
cbStack = [];
wb = new WriteBuffer(data => { stack.push(data); });
});
describe('write input', () => {
it('string', done => {
wb.write('a._');
wb.write('b.x', () => { cbStack.push('b'); });
wb.write('c._');
wb.write('d.x', () => { cbStack.push('d'); });
wb.write('e', () => {
assert.deepEqual(stack, ['a._', 'b.x', 'c._', 'd.x', 'e']);
assert.deepEqual(cbStack, ['b', 'd']);
done();
});
});
it('bytes', done => {
wb.write(toBytes('a._'));
wb.write(toBytes('b.x'), () => { cbStack.push('b'); });
wb.write(toBytes('c._'));
wb.write(toBytes('d.x'), () => { cbStack.push('d'); });
wb.write(toBytes('e'), () => {
assert.deepEqual(stack.map(val => typeof val === 'string' ? '' : fromBytes(val)), ['a._', 'b.x', 'c._', 'd.x', 'e']);
assert.deepEqual(cbStack, ['b', 'd']);
done();
});
});
it('string/bytes mixed', done => {
wb.write('a._');
wb.write('b.x', () => { cbStack.push('b'); });
wb.write(toBytes('c._'));
wb.write(toBytes('d.x'), () => { cbStack.push('d'); });
wb.write(toBytes('e'), () => {
assert.deepEqual(stack.map(val => typeof val === 'string' ? val : fromBytes(val)), ['a._', 'b.x', 'c._', 'd.x', 'e']);
assert.deepEqual(cbStack, ['b', 'd']);
done();
});
});
it('write callback works for empty chunks', done => {
wb.write('a', () => { cbStack.push('a'); });
wb.write('', () => { cbStack.push('b'); });
wb.write(toBytes('c'), () => { cbStack.push('c'); });
wb.write(new Uint8Array(0), () => { cbStack.push('d'); });
wb.write('e', () => {
assert.deepEqual(stack.map(val => typeof val === 'string' ? val : fromBytes(val)), ['a', '', 'c', '', 'e']);
assert.deepEqual(cbStack, ['a', 'b', 'c', 'd']);
done();
});
});
it('writeSync', done => {
wb.write('a', () => { cbStack.push('a'); });
wb.write('b', () => { cbStack.push('b'); });
wb.write('c', () => { cbStack.push('c'); });
wb.writeSync('d');
assert.deepEqual(stack, ['a', 'b', 'c', 'd']);
assert.deepEqual(cbStack, ['a', 'b', 'c']);
wb.write('x', () => { cbStack.push('x'); });
wb.write('', () => {
assert.deepEqual(stack, ['a', 'b', 'c', 'd', 'x', '']);
assert.deepEqual(cbStack, ['a', 'b', 'c', 'x']);
done();
});
});
});
});
-110
View File
@@ -1,110 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
declare const setTimeout: (handler: () => void, timeout?: number) => void;
/**
* Safety watermark to avoid memory exhaustion and browser engine crash on fast data input.
* Enable flow control to avoid this limit and make sure that your backend correctly
* propagates this to the underlying pty. (see docs for further instructions)
* Since this limit is meant as a safety parachute to prevent browser crashs,
* it is set to a very high number. Typically xterm.js gets unresponsive with
* a 100 times lower number (>500 kB).
*/
const DISCARD_WATERMARK = 50000000; // ~50 MB
/**
* The max number of ms to spend on writes before allowing the renderer to
* catch up with a 0ms setTimeout. A value of < 33 to keep us close to
* 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
* depends on the time it takes for the renderer to draw the frame.
*/
const WRITE_TIMEOUT_MS = 12;
/**
* Threshold of max held chunks in the write buffer, that were already processed.
* This is a tradeoff between extensive write buffer shifts (bad runtime) and high
* memory consumption by data thats not used anymore.
*/
const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
export class WriteBuffer {
private _writeBuffer: (string | Uint8Array)[] = [];
private _callbacks: ((() => void) | undefined)[] = [];
private _pendingData = 0;
private _bufferOffset = 0;
constructor(private _action: (data: string | Uint8Array) => void) { }
public writeSync(data: string | Uint8Array): void {
// force sync processing on pending data chunks to avoid in-band data scrambling
// does the same as innerWrite but without event loop
if (this._writeBuffer.length) {
for (let i = this._bufferOffset; i < this._writeBuffer.length; ++i) {
const data = this._writeBuffer[i];
const cb = this._callbacks[i];
this._action(data);
if (cb) cb();
}
// reset all to avoid reprocessing of chunks with scheduled innerWrite call
this._writeBuffer = [];
this._callbacks = [];
this._pendingData = 0;
// stop scheduled innerWrite by offset > length condition
this._bufferOffset = 0x7FFFFFFF;
}
// handle current data chunk
this._action(data);
}
public write(data: string | Uint8Array, callback?: () => void): void {
if (this._pendingData > DISCARD_WATERMARK) {
throw new Error('write data discarded, use flow control to avoid losing data');
}
// schedule chunk processing for next event loop run
if (!this._writeBuffer.length) {
this._bufferOffset = 0;
setTimeout(() => this._innerWrite());
}
this._pendingData += data.length;
this._writeBuffer.push(data);
this._callbacks.push(callback);
}
protected _innerWrite(): void {
const startTime = Date.now();
while (this._writeBuffer.length > this._bufferOffset) {
const data = this._writeBuffer[this._bufferOffset];
const cb = this._callbacks[this._bufferOffset];
this._bufferOffset++;
this._action(data);
this._pendingData -= data.length;
if (cb) cb();
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
break;
}
}
if (this._writeBuffer.length > this._bufferOffset) {
// Allow renderer to catch up before processing the next batch
// trim already processed chunks if we are above threshold
if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
this._writeBuffer = this._writeBuffer.slice(this._bufferOffset);
this._callbacks = this._callbacks.slice(this._bufferOffset);
this._bufferOffset = 0;
}
setTimeout(() => this._innerWrite(), 0);
} else {
this._writeBuffer = [];
this._callbacks = [];
this._pendingData = 0;
this._bufferOffset = 0;
}
}
}
+18 -2
View File
@@ -32,9 +32,9 @@ describe('CoreMouseService', () => {
const cms = new CoreMouseService(bufferService, coreService); const cms = new CoreMouseService(bufferService, coreService);
assert.deepEqual(Object.keys((cms as any)._protocols), ['NONE', 'X10', 'VT200', 'DRAG', 'ANY']); assert.deepEqual(Object.keys((cms as any)._protocols), ['NONE', 'X10', 'VT200', 'DRAG', 'ANY']);
}); });
it('default encodings - DEFAULT, SGR', () => { it('default encodings - DEFAULT, UTF8, SGR, URXVT', () => {
const cms = new CoreMouseService(bufferService, coreService); const cms = new CoreMouseService(bufferService, coreService);
assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'SGR']); assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'UTF8', 'SGR', 'URXVT']);
}); });
it('protocol/encoding setter, reset', () => { it('protocol/encoding setter, reset', () => {
const cms = new CoreMouseService(bufferService, coreService); const cms = new CoreMouseService(bufferService, coreService);
@@ -151,6 +151,14 @@ describe('CoreMouseService', () => {
} }
} }
}); });
it('UTF8 encoding', () => {
cms.activeProtocol = 'ANY';
cms.activeEncoding = 'UTF8';
for (let i = 0; i < bufferService.cols; ++i) {
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
}
});
it('SGR encoding', () => { it('SGR encoding', () => {
cms.activeProtocol = 'ANY'; cms.activeProtocol = 'ANY';
cms.activeEncoding = 'SGR'; cms.activeEncoding = 'SGR';
@@ -159,6 +167,14 @@ describe('CoreMouseService', () => {
assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`); assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`);
} }
}); });
it('URXVT', () => {
cms.activeProtocol = 'ANY';
cms.activeEncoding = 'URXVT';
for (let i = 0; i < bufferService.cols; ++i) {
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
assert.deepEqual(reports.pop(), `\x1b[32;${i + 1};1M`);
}
});
}); });
it('eventCodes with modifiers (DEFAULT encoding)', () => { it('eventCodes with modifiers (DEFAULT encoding)', () => {
// TODO: implement AUX button tests // TODO: implement AUX button tests
+20 -1
View File
@@ -132,6 +132,17 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
// FIXED: params = params.map(v => (v > 255) ? 0 : value); // FIXED: params = params.map(v => (v > 255) ? 0 : value);
return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`; return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
}, },
/**
* UTF8 - CSI M Pb Px Py
* Same as DEFAULT, but with optional 2-byte UTF8
* encoding for values > 223 (can encode up to 2015).
*/
UTF8: (e: ICoreMouseEvent) => {
let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
// limit to 2-byte UTF8
params = params.map(v => (v > 2047) ? 0 : v);
return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
},
/** /**
* SGR - CSI < Pb ; Px ; Py M|m * SGR - CSI < Pb ; Px ; Py M|m
* No encoding limitation. * No encoding limitation.
@@ -140,6 +151,14 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
SGR: (e: ICoreMouseEvent) => { SGR: (e: ICoreMouseEvent) => {
const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M'; const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M';
return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`; return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`;
},
/**
* URXVT - CSI Pb ; Px ; Py M
* Same button encoding as default, decimal encoding for coords.
* Ambiguity with other sequences, should not be used.
*/
URXVT: (e: ICoreMouseEvent) => {
return `\x1b[${eventCode(e, false) + 32};${e.col};${e.row}M`;
} }
}; };
@@ -148,7 +167,7 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
* *
* Provides mouse tracking reports with different protocols and encodings. * Provides mouse tracking reports with different protocols and encodings.
* - protocols: NONE (default), X10, VT200, DRAG, ANY * - protocols: NONE (default), X10, VT200, DRAG, ANY
* - encodings: DEFAULT, SGR (UTF8, URXVT removed in #2507) * - encodings: DEFAULT, SGR, UTF8, URXVT
* *
* Custom protocols/encodings can be added by `addProtocol` / `addEncoding`. * Custom protocols/encodings can be added by `addProtocol` / `addEncoding`.
* To activate a protocol/encoding, set `activeProtocol` / `activeEncoding`. * To activate a protocol/encoding, set `activeProtocol` / `activeEncoding`.
+1 -1
View File
@@ -54,7 +54,7 @@ export class CoreService implements ICoreService {
} }
// Fire onData API // Fire onData API
this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); this._logService.debug('sending data', data);
this._onData.fire(data); this._onData.fire(data);
} }
} }
+9 -24
View File
@@ -5,14 +5,12 @@
import { ILogService, IOptionsService } from 'common/services/Services'; import { ILogService, IOptionsService } from 'common/services/Services';
type LogType = (message?: any, ...optionalParams: any[]) => void;
interface IConsole { interface IConsole {
log: LogType; log(message?: any, ...optionalParams: any[]): void;
error: LogType; error(message?: any, ...optionalParams: any[]): void;
info: LogType; info(message?: any, ...optionalParams: any[]): void;
trace: LogType; trace(message?: any, ...optionalParams: any[]): void;
warn: LogType; warn(message?: any, ...optionalParams: any[]): void;
} }
// console is available on both node.js and browser contexts but the common // console is available on both node.js and browser contexts but the common
@@ -58,40 +56,27 @@ export class LogService implements ILogService {
this._logLevel = optionsKeyToLogLevel[this._optionsService.options.logLevel]; this._logLevel = optionsKeyToLogLevel[this._optionsService.options.logLevel];
} }
private _evalLazyOptionalParams(optionalParams: any[]): void {
for (let i = 0; i < optionalParams.length; i++) {
if (typeof optionalParams[i] === 'function') {
optionalParams[i] = optionalParams[i]();
}
}
}
private _log(type: LogType, message: string, optionalParams: any[]): void {
this._evalLazyOptionalParams(optionalParams);
type.call(console, LOG_PREFIX + message, ...optionalParams);
}
debug(message: string, ...optionalParams: any[]): void { debug(message: string, ...optionalParams: any[]): void {
if (this._logLevel <= LogLevel.DEBUG) { if (this._logLevel <= LogLevel.DEBUG) {
this._log(console.log, message, optionalParams); console.log.call(console, LOG_PREFIX + message, ...optionalParams);
} }
} }
info(message: string, ...optionalParams: any[]): void { info(message: string, ...optionalParams: any[]): void {
if (this._logLevel <= LogLevel.INFO) { if (this._logLevel <= LogLevel.INFO) {
this._log(console.info, message, optionalParams); console.info.call(console, LOG_PREFIX + message, ...optionalParams);
} }
} }
warn(message: string, ...optionalParams: any[]): void { warn(message: string, ...optionalParams: any[]): void {
if (this._logLevel <= LogLevel.WARN) { if (this._logLevel <= LogLevel.WARN) {
this._log(console.warn, message, optionalParams); console.warn.call(console, LOG_PREFIX + message, ...optionalParams);
} }
} }
error(message: string, ...optionalParams: any[]): void { error(message: string, ...optionalParams: any[]): void {
if (this._logLevel <= LogLevel.ERROR) { if (this._logLevel <= LogLevel.ERROR) {
this._log(console.error, message, optionalParams); console.error.call(console, LOG_PREFIX + message, ...optionalParams);
} }
} }
} }
+2 -11
View File
@@ -12,7 +12,7 @@ import { clone } from 'common/Clone';
// This sound is released under the Creative Commons Attribution 3.0 Unported // This sound is released under the Creative Commons Attribution 3.0 Unported
// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been // (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
// made, apart from the conversion to base64. // made, apart from the conversion to base64.
export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
// TODO: Freeze? // TODO: Freeze?
export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
@@ -23,8 +23,6 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
bellSound: DEFAULT_BELL_SOUND, bellSound: DEFAULT_BELL_SOUND,
bellStyle: 'none', bellStyle: 'none',
drawBoldTextInBrightColors: true, drawBoldTextInBrightColors: true,
fastScrollModifier: 'alt',
fastScrollSensitivity: 5,
fontFamily: 'courier-new, courier, monospace', fontFamily: 'courier-new, courier, monospace',
fontSize: 15, fontSize: 15,
fontWeight: 'normal', fontWeight: 'normal',
@@ -33,7 +31,6 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
letterSpacing: 0, letterSpacing: 0,
logLevel: 'info', logLevel: 'info',
scrollback: 1000, scrollback: 1000,
scrollSensitivity: 1,
screenReaderMode: false, screenReaderMode: false,
macOptionIsMeta: false, macOptionIsMeta: false,
macOptionClickForcesSelection: false, macOptionClickForcesSelection: false,
@@ -50,7 +47,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
screenKeys: false, screenKeys: false,
cancelEvents: false, cancelEvents: false,
useFlowControl: false, useFlowControl: false,
wordSeparator: ' ()[]{}\',:;"' wordSeparator: ' ()[]{}\'"'
}); });
/** /**
@@ -122,12 +119,6 @@ export class OptionsService implements IOptionsService {
throw new Error(`${key} cannot be less than 0, value: ${value}`); throw new Error(`${key} cannot be less than 0, value: ${value}`);
} }
break; break;
case 'fastScrollSensitivity':
case 'scrollSensitivity':
if (value <= 0) {
throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`);
}
break;
} }
return value; return value;
} }
-6
View File
@@ -180,8 +180,6 @@ export interface IPartialTerminalOptions {
cursorStyle?: 'block' | 'underline' | 'bar'; cursorStyle?: 'block' | 'underline' | 'bar';
disableStdin?: boolean; disableStdin?: boolean;
drawBoldTextInBrightColors?: boolean; drawBoldTextInBrightColors?: boolean;
fastScrollModifier?: 'alt' | 'ctrl' | 'shift';
fastScrollSensitivity?: number;
fontSize?: number; fontSize?: number;
fontFamily?: string; fontFamily?: string;
fontWeight?: FontWeight; fontWeight?: FontWeight;
@@ -196,7 +194,6 @@ export interface IPartialTerminalOptions {
rows?: number; rows?: number;
screenReaderMode?: boolean; screenReaderMode?: boolean;
scrollback?: number; scrollback?: number;
scrollSensitivity?: number;
tabStopWidth?: number; tabStopWidth?: number;
theme?: ITheme; theme?: ITheme;
windowsMode?: boolean; windowsMode?: boolean;
@@ -212,8 +209,6 @@ export interface ITerminalOptions {
cursorStyle: 'block' | 'underline' | 'bar'; cursorStyle: 'block' | 'underline' | 'bar';
disableStdin: boolean; disableStdin: boolean;
drawBoldTextInBrightColors: boolean; drawBoldTextInBrightColors: boolean;
fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined;
fastScrollSensitivity: number;
fontSize: number; fontSize: number;
fontFamily: string; fontFamily: string;
fontWeight: FontWeight; fontWeight: FontWeight;
@@ -228,7 +223,6 @@ export interface ITerminalOptions {
rows: number; rows: number;
screenReaderMode: boolean; screenReaderMode: boolean;
scrollback: number; scrollback: number;
scrollSensitivity: number;
tabStopWidth: number; tabStopWidth: number;
theme: ITheme; theme: ITheme;
windowsMode: boolean; windowsMode: boolean;
+11 -12
View File
@@ -33,14 +33,14 @@ export class Terminal implements ITerminalApi {
public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; } public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; }
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; } public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }
public get element(): HTMLElement | undefined { return this._core.element; } public get element(): HTMLElement { return this._core.element; }
public get parser(): IParser { public get parser(): IParser {
if (!this._parser) { if (!this._parser) {
this._parser = new ParserApi(this._core); this._parser = new ParserApi(this._core);
} }
return this._parser; return this._parser;
} }
public get textarea(): HTMLTextAreaElement | undefined { return this._core.textarea; } public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
public get rows(): number { return this._core.rows; } public get rows(): number { return this._core.rows; }
public get cols(): number { return this._core.cols; } public get cols(): number { return this._core.cols; }
public get buffer(): IBufferApi { return new BufferApiView(this._core.buffer); } public get buffer(): IBufferApi { return new BufferApiView(this._core.buffer); }
@@ -55,6 +55,9 @@ export class Terminal implements ITerminalApi {
this._verifyIntegers(columns, rows); this._verifyIntegers(columns, rows);
this._core.resize(columns, rows); this._core.resize(columns, rows);
} }
public writeln(data: string): void {
this._core.writeln(data);
}
public open(parent: HTMLElement): void { public open(parent: HTMLElement): void {
this._core.open(parent); this._core.open(parent);
} }
@@ -125,15 +128,11 @@ export class Terminal implements ITerminalApi {
public clear(): void { public clear(): void {
this._core.clear(); this._core.clear();
} }
public write(data: string | Uint8Array, callback?: () => void): void { public write(data: string): void {
this._core.write(data, callback);
}
public writeUtf8(data: Uint8Array, callback?: () => void): void {
this._core.write(data, callback);
}
public writeln(data: string | Uint8Array, callback?: () => void): void {
this._core.write(data); this._core.write(data);
this._core.write('\r\n', callback); }
public writeUtf8(data: Uint8Array): void {
this._core.writeUtf8(data);
} }
public paste(data: string): void { public paste(data: string): void {
this._core.paste(data); this._core.paste(data);
@@ -178,8 +177,8 @@ export class Terminal implements ITerminalApi {
private _verifyIntegers(...values: number[]): void { private _verifyIntegers(...values: number[]): void {
values.forEach(value => { values.forEach(value => {
if (value === Infinity || isNaN(value) || value % 1 !== 0) { if (value % 1 !== 0) {
throw new Error('This API only accepts integers'); throw new Error('This API does not accept floating point numbers');
} }
}); });
} }
+5 -10
View File
@@ -103,10 +103,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._cursorBlinkStateManager.dispose(); this._cursorBlinkStateManager.dispose();
this._cursorBlinkStateManager = null; this._cursorBlinkStateManager = null;
} }
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
} }
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
} }
public onCursorMove(): void { public onCursorMove(): void {
@@ -148,17 +148,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._clearCursor(); this._clearCursor();
this._ctx.save(); this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css; this._ctx.fillStyle = this._colors.cursor.css;
const cursorStyle = this._optionsService.options.cursorStyle; this._renderBlurCursor(this._bufferService.buffer.x, viewportRelativeCursorY, this._cell);
if (cursorStyle && cursorStyle !== 'block') {
this._cursorRenderers[cursorStyle](this._bufferService.buffer.x, viewportRelativeCursorY, this._cell);
} else {
this._renderBlurCursor(this._bufferService.buffer.x, viewportRelativeCursorY, this._cell);
}
this._ctx.restore(); this._ctx.restore();
this._state.x = this._bufferService.buffer.x; this._state.x = this._bufferService.buffer.x;
this._state.y = viewportRelativeCursorY; this._state.y = viewportRelativeCursorY;
this._state.isFocused = false; this._state.isFocused = false;
this._state.style = cursorStyle; this._state.style = this._optionsService.options.cursorStyle;
this._state.width = this._cell.getWidth(); this._state.width = this._cell.getWidth();
return; return;
} }
+3 -3
View File
@@ -183,7 +183,7 @@ export class DomRenderer extends Disposable implements IRenderer {
`}`; `}`;
// Cursor // Cursor
styles += styles +=
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` +
` outline: 1px solid ${this._colors.cursor.css};` + ` outline: 1px solid ${this._colors.cursor.css};` +
` outline-offset: -1px;` + ` outline-offset: -1px;` +
`}` + `}` +
@@ -197,10 +197,10 @@ export class DomRenderer extends Disposable implements IRenderer {
` background-color: ${this._colors.cursor.css};` + ` background-color: ${this._colors.cursor.css};` +
` color: ${this._colors.cursorAccent.css};` + ` color: ${this._colors.cursorAccent.css};` +
`}` + `}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
` box-shadow: 1px 0 0 ${this._colors.cursor.css} inset;` + ` box-shadow: 1px 0 0 ${this._colors.cursor.css} inset;` +
`}` + `}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` + ` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
`}`; `}`;
// Selection // Selection
+1 -1
View File
@@ -21,7 +21,7 @@ describe('CharWidth Integration Tests', function(): void {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1, headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80, slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`] args: [`--window-size=${width},${height}`]
}); });
page = (await browser.pages())[0]; page = (await browser.pages())[0];
await page.setViewport({ width, height }); await page.setViewport({ width, height });
+1 -31
View File
@@ -21,7 +21,7 @@ describe('InputHandler Integration Tests', function(): void {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1, headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80, slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`] args: [`--window-size=${width},${height}`]
}); });
page = (await browser.pages())[0]; page = (await browser.pages())[0];
await page.setViewport({ width, height }); await page.setViewport({ width, height });
@@ -196,36 +196,6 @@ describe('InputHandler Integration Tests', function(): void {
assert.deepEqual(await getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); assert.deepEqual(await getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']);
}); });
it('IL: Insert Ps Line(s) (default = 1) - CSI Ps L', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('foo\x1b[La')
// Explicit
window.term.write('\x1b[2Lb')
`);
assert.deepEqual(await getLinesAsArray(4), ['b', '', 'a', 'foo']);
});
it('DL: Delete Ps Line(s) (default = 1) - CSI Ps M', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('a\\nb\x1b[1F\x1b[M')
// Explicit
window.term.write('\x1b[1Ed\\ne\\nf\x1b[2F\x1b[2M')
`);
assert.deepEqual(await getLinesAsArray(5), [' b', ' f', '', '', '']);
});
it('DCH: Delete Ps Character(s) (default = 1) - CSI Ps P', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('abc\x1b[1;1H\x1b[P')
// Explicit
window.term.write('\\n\\rdef\x1b[2;1H\x1b[2P')
`);
assert.deepEqual(await getLinesAsArray(2), ['bc', 'f']);
});
describe('DSR: Device Status Report', () => { describe('DSR: Device Status Report', () => {
it('Status Report - CSI 5 n', async function(): Promise<any> { it('Status Report - CSI 5 n', async function(): Promise<any> {
await page.evaluate(` await page.evaluate(`
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -21,7 +21,7 @@ describe('Parser Integration Tests', function(): void {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1, headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80, slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`] args: [`--window-size=${width},${height}`]
}); });
page = (await browser.pages())[0]; page = (await browser.pages())[0];
await page.setViewport({ width, height }); await page.setViewport({ width, height });
+14 -76
View File
@@ -21,7 +21,7 @@ describe('API Integration Tests', function(): void {
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1, headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80, slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`] args: [`--window-size=${width},${height}`]
}); });
page = (await browser.pages())[0]; page = (await browser.pages())[0];
await page.setViewport({ width, height }); await page.setViewport({ width, height });
@@ -51,44 +51,6 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文');
}); });
it('write with callback', async function(): Promise<any> {
await openTerminal();
await page.evaluate(`
window.term.write('foo', () => { window.__x = 'a'; });
window.term.write('bar', () => { window.__x += 'b'; });
window.term.write('文', () => { window.__x += 'c'; });
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文');
assert.equal(await page.evaluate(`window.__x`), 'abc');
});
it('write - bytes (UTF8)', async function(): Promise<any> {
await openTerminal();
await page.evaluate(`
// foo
window.term.write(new Uint8Array([102, 111, 111]));
// bar
window.term.write(new Uint8Array([98, 97, 114]));
// 文
window.term.write(new Uint8Array([230, 150, 135]));
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文');
});
it('write - bytes (UTF8) with callback', async function(): Promise<any> {
await openTerminal();
await page.evaluate(`
// foo
window.term.write(new Uint8Array([102, 111, 111]), () => { window.__x = 'A'; });
// bar
window.term.write(new Uint8Array([98, 97, 114]), () => { window.__x += 'B'; });
// 文
window.term.write(new Uint8Array([230, 150, 135]), () => { window.__x += 'C'; });
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文');
assert.equal(await page.evaluate(`window.__x`), 'ABC');
});
it('writeln', async function(): Promise<any> { it('writeln', async function(): Promise<any> {
await openTerminal(); await openTerminal();
await page.evaluate(` await page.evaluate(`
@@ -101,29 +63,17 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文'); assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文');
}); });
it('writeln with callback', async function(): Promise<any> { it('writeUtf8', async function(): Promise<any> {
await openTerminal(); await openTerminal();
await page.evaluate(` await page.evaluate(`
window.term.writeln('foo', () => { window.__x = '1'; }); // foo
window.term.writeln('bar', () => { window.__x += '2'; }); window.term.writeUtf8(new Uint8Array([102, 111, 111]));
window.term.writeln('文', () => { window.__x += '3'; }); // bar
window.term.writeUtf8(new Uint8Array([98, 97, 114]));
// 文
window.term.writeUtf8(new Uint8Array([230, 150, 135]));
`); `);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文');
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar');
assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文');
assert.equal(await page.evaluate(`window.__x`), '123');
});
it('writeln - bytes (UTF8)', async function(): Promise<any> {
await openTerminal();
await page.evaluate(`
window.term.writeln(new Uint8Array([102, 111, 111]));
window.term.writeln(new Uint8Array([98, 97, 114]));
window.term.writeln(new Uint8Array([230, 150, 135]));
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar');
assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文');
}); });
it('paste', async function(): Promise<any> { it('paste', async function(): Promise<any> {
@@ -133,9 +83,11 @@ describe('API Integration Tests', function(): void {
window.term.onData(e => calls.push(e)); window.term.onData(e => calls.push(e));
window.term.paste('foo'); window.term.paste('foo');
window.term.paste('\\r\\nfoo\\nbar\\r'); window.term.paste('\\r\\nfoo\\nbar\\r');
window.term.write('\\x1b[?2004h', () => { window.term.write('\\x1b[?2004h');
window.term.paste('foo'); // TODO: Use promise/callback for write when we support that
}); // Force sync write
window.term._core._innerWrite();
window.term.paste('foo');
`); `);
assert.deepEqual(await page.evaluate(`window.calls`), ['foo', '\rfoo\rbar\r', '\x1b[200~foo\x1b[201~']); assert.deepEqual(await page.evaluate(`window.calls`), ['foo', '\rfoo\rbar\r', '\x1b[200~foo\x1b[201~']);
}); });
@@ -510,20 +462,6 @@ describe('API Integration Tests', function(): void {
}); });
}); });
}); });
it('dispose', async function(): Promise<any> {
await page.evaluate(`
window.term = new Terminal();
window.term.dispose();
`);
assert.equal(await page.evaluate(`window.term._core._isDisposed`), true);
});
it('dispose (opened)', async function(): Promise<any> {
await openTerminal();
await page.evaluate(`window.term.dispose()`);
assert.equal(await page.evaluate(`window.term._core._isDisposed`), true);
});
}); });
async function openTerminal(options: ITerminalOptions = {}): Promise<void> { async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
+16 -5
View File
@@ -9,6 +9,17 @@ import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder'; import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { Terminal } from 'Terminal'; import { Terminal } from 'Terminal';
class TestTerminal extends Terminal {
writeSync(data: string): void {
this.writeBuffer.push(data);
this._innerWrite();
}
writeSyncUtf8(data: Uint8Array): void {
this.writeBufferUtf8.push(data);
this._innerWriteUtf8();
}
}
perfContext('Terminal: ls -lR /usr/lib', () => { perfContext('Terminal: ls -lR /usr/lib', () => {
let content = ''; let content = '';
let contentUtf8: Uint8Array; let contentUtf8: Uint8Array;
@@ -45,9 +56,9 @@ perfContext('Terminal: ls -lR /usr/lib', () => {
}); });
perfContext('write', () => { perfContext('write', () => {
let terminal: Terminal; let terminal: TestTerminal;
before(() => { before(() => {
terminal = new Terminal({cols: 80, rows: 25, scrollback: 1000}); terminal = new TestTerminal({cols: 80, rows: 25, scrollback: 1000});
}); });
new ThroughputRuntimeCase('', () => { new ThroughputRuntimeCase('', () => {
terminal.writeSync(content); terminal.writeSync(content);
@@ -56,12 +67,12 @@ perfContext('Terminal: ls -lR /usr/lib', () => {
}); });
perfContext('writeUtf8', () => { perfContext('writeUtf8', () => {
let terminal: Terminal; let terminal: TestTerminal;
before(() => { before(() => {
terminal = new Terminal({cols: 80, rows: 25, scrollback: 1000}); terminal = new TestTerminal({cols: 80, rows: 25, scrollback: 1000});
}); });
new ThroughputRuntimeCase('', () => { new ThroughputRuntimeCase('', () => {
terminal.writeSync(content); terminal.writeSyncUtf8(contentUtf8);
return {payloadSize: contentUtf8.length}; return {payloadSize: contentUtf8.length};
}, {fork: false}).showAverageThroughput(); }, {fork: false}).showAverageThroughput();
}); });
+16 -76
View File
@@ -82,16 +82,6 @@ declare module 'xterm' {
*/ */
drawBoldTextInBrightColors?: boolean; drawBoldTextInBrightColors?: boolean;
/**
* The modifier key hold to multiply scroll speed.
*/
fastScrollModifier?: 'alt' | 'ctrl' | 'shift' | undefined;
/**
* The scroll speed multiplier used for fast scrolling.
*/
fastScrollSensitivity?: number;
/** /**
* The font size used to render text. * The font size used to render text.
*/ */
@@ -183,11 +173,6 @@ declare module 'xterm' {
*/ */
scrollback?: number; scrollback?: number;
/**
* The scrolling speed multiplier used for adjusting normal scrolling speed.
*/
scrollSensitivity?: number;
/** /**
* The size of tab stops in the terminal. * The size of tab stops in the terminal.
*/ */
@@ -284,7 +269,7 @@ declare module 'xterm' {
/** /**
* A callback that fires when the mouse hovers over a link for a moment. * A callback that fires when the mouse hovers over a link for a moment.
*/ */
tooltipCallback?: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void; tooltipCallback?: (event: MouseEvent, uri: string) => boolean | void;
/** /**
* A callback that fires when the mouse leaves a link. Note that this can * A callback that fires when the mouse leaves a link. Note that this can
@@ -325,8 +310,7 @@ declare module 'xterm' {
/** /**
* Represents a specific line in the terminal that is tracked when scrollback * Represents a specific line in the terminal that is tracked when scrollback
* is trimmed and lines are added or removed. This is a single line that may * is trimmed and lines are added or removed.
* be part of a larger wrapped line.
*/ */
export interface IMarker extends IDisposable { export interface IMarker extends IDisposable {
/** /**
@@ -340,8 +324,7 @@ declare module 'xterm' {
readonly isDisposed: boolean; readonly isDisposed: boolean;
/** /**
* The actual line index in the buffer at this point in time. This is set to * The actual line index in the buffer at this point in time.
* -1 if the marker has been disposed.
*/ */
readonly line: number; readonly line: number;
} }
@@ -369,12 +352,12 @@ declare module 'xterm' {
/** /**
* The element containing the terminal. * The element containing the terminal.
*/ */
readonly element: HTMLElement | undefined; readonly element: HTMLElement;
/** /**
* The textarea that accepts input for the terminal. * The textarea that accepts input for the terminal.
*/ */
readonly textarea: HTMLTextAreaElement | undefined; readonly textarea: HTMLTextAreaElement;
/** /**
* The number of rows in the terminal's viewport. Use * The number of rows in the terminal's viewport. Use
@@ -451,7 +434,7 @@ declare module 'xterm' {
onLineFeed: IEvent<void>; onLineFeed: IEvent<void>;
/** /**
* Adds an event listener for when a scroll occurs. The event value is the * Adds an event listener for when a scroll occurs. The event value is the
* new position of the viewport. * new position of the viewport.
* @returns an `IDisposable` to stop listening. * @returns an `IDisposable` to stop listening.
*/ */
@@ -667,32 +650,24 @@ declare module 'xterm' {
clear(): void; clear(): void;
/** /**
* Write data to the terminal. * Writes text to the terminal.
* @param data The data to write to the terminal. This can either be raw * @param data The text to write to the terminal.
* bytes given as Uint8Array from the pty or a string. Raw bytes will always
* be treated as UTF-8 encoded, string data as UTF-16.
* @param callback Optional callback that fires when the data was processed
* by the parser.
*/ */
write(data: string | Uint8Array, callback?: () => void): void; write(data: string): void;
/** /**
* Writes data to the terminal, followed by a break line character (\n). * Writes text to the terminal, followed by a break line character (\n).
* @param data The data to write to the terminal. This can either be raw * @param data The text to write to the terminal.
* bytes given as Uint8Array from the pty or a string. Raw bytes will always
* be treated as UTF-8 encoded, string data as UTF-16.
* @param callback Optional callback that fires when the data was processed
* by the parser.
*/ */
writeln(data: string | Uint8Array, callback?: () => void): void; writeln(data: string): void;
/** /**
* Write UTF8 data to the terminal. * Writes UTF8 data to the terminal. This has a slight performance advantage
* over the string based write method due to lesser data conversions needed
* on the way from the pty to xterm.js.
* @param data The data to write to the terminal. * @param data The data to write to the terminal.
* @param callback Optional callback when data was processed.
* @deprecated use `write` instead
*/ */
writeUtf8(data: Uint8Array, callback?: () => void): void; writeUtf8(data: Uint8Array): void;
/** /**
* Writes text to the terminal, performing the necessary transformations for pasted text. * Writes text to the terminal, performing the necessary transformations for pasted text.
@@ -859,41 +834,6 @@ declare module 'xterm' {
endRow: number; endRow: number;
} }
/**
* An object representing a range within the viewport of the terminal.
*/
export interface IViewportRange {
/**
* The start of the range.
*/
start: IViewportRangePosition;
/**
* The end of the range.
*/
end: IViewportRangePosition;
}
/**
* An object representing a cell position within the viewport of the terminal.
*/
interface IViewportRangePosition {
/**
* The x position of the cell. This is a 0-based index that refers to the
* space in between columns, not the column itself. Index 0 refers to the
* left side of the viewport, index `Terminal.cols` refers to the right side
* of the viewport. This can be thought of as how a cursor is positioned in
* a text editor.
*/
x: number;
/**
* The y position of the cell. This is a 0-based index that refers to a
* specific row.
*/
y: number;
}
/** /**
* Represents a terminal buffer. * Represents a terminal buffer.
*/ */
+1 -6
View File
@@ -4719,12 +4719,7 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@3.6: typescript@3.5, typescript@^3.5.1:
version "3.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"
integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==
typescript@^3.5.1:
version "3.5.1" version "3.5.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202"
integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==