Merge pull request #2164 from Tyriar/addons_in_repo
Move new format addons into repo, remove old addons
This commit is contained in:
@@ -50,3 +50,4 @@ fixtures/
|
|||||||
demo/
|
demo/
|
||||||
.devcontainer/
|
.devcontainer/
|
||||||
out/
|
out/
|
||||||
|
addons/
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
lib
|
||||||
|
node_modules
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
**/*.api.js
|
||||||
|
**/*.api.ts
|
||||||
|
tsconfig.json
|
||||||
|
.yarnrc
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "@xtermjs/addon-attach",
|
||||||
|
"version": "0.1.0-beta8",
|
||||||
|
"author": {
|
||||||
|
"name": "The xterm.js authors",
|
||||||
|
"url": "https://xtermjs.org/"
|
||||||
|
},
|
||||||
|
"main": "lib/AttachAddon.js",
|
||||||
|
"types": "typings/attach.d.ts",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"prepublishOnly": "../../../node_modules/.bin/tsc -p src"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"xterm": "^3.14.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
import WebSocket = require('ws');
|
||||||
|
|
||||||
|
const APP = 'http://127.0.0.1:3000/test';
|
||||||
|
|
||||||
|
let browser: puppeteer.Browser;
|
||||||
|
let page: puppeteer.Page;
|
||||||
|
const width = 800;
|
||||||
|
const height = 600;
|
||||||
|
|
||||||
|
describe('AttachAddon', () => {
|
||||||
|
before(async function(): Promise<any> {
|
||||||
|
this.timeout(10000);
|
||||||
|
browser = await puppeteer.launch({
|
||||||
|
headless: process.argv.indexOf('--headless') !== -1,
|
||||||
|
slowMo: 80,
|
||||||
|
args: [`--window-size=${width},${height}`]
|
||||||
|
});
|
||||||
|
page = (await browser.pages())[0];
|
||||||
|
await page.setViewport({ width, height });
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async function(): Promise<any> {
|
||||||
|
this.timeout(5000);
|
||||||
|
await page.goto(APP);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('string', async function(): Promise<any> {
|
||||||
|
this.timeout(20000);
|
||||||
|
await openTerminal({ rendererType: 'dom' });
|
||||||
|
const port = 8080;
|
||||||
|
const server = new WebSocket.Server({ port });
|
||||||
|
server.on('connection', socket => socket.send('foo'));
|
||||||
|
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||||
|
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('utf8', async function(): Promise<any> {
|
||||||
|
this.timeout(20000);
|
||||||
|
await openTerminal({ rendererType: 'dom' });
|
||||||
|
const port = 8080;
|
||||||
|
const server = new WebSocket.Server({ port });
|
||||||
|
const data = new Uint8Array([102, 111, 111]);
|
||||||
|
server.on('connection', socket => socket.send(data));
|
||||||
|
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');
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2014, 2019 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*
|
||||||
|
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||||
|
|
||||||
|
interface IAttachOptions {
|
||||||
|
bidirectional?: boolean;
|
||||||
|
inputUtf8?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AttachAddon implements ITerminalAddon {
|
||||||
|
private _socket: WebSocket;
|
||||||
|
private _bidirectional: boolean;
|
||||||
|
private _utf8: boolean;
|
||||||
|
private _disposables: IDisposable[] = [];
|
||||||
|
|
||||||
|
constructor(socket: WebSocket, options?: IAttachOptions) {
|
||||||
|
this._socket = socket;
|
||||||
|
// always set binary type to arraybuffer, we do not handle blobs
|
||||||
|
this._socket.binaryType = 'arraybuffer';
|
||||||
|
this._bidirectional = (options && options.bidirectional === false) ? false : true;
|
||||||
|
this._utf8 = !!(options && options.inputUtf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public activate(terminal: Terminal): void {
|
||||||
|
if (this._utf8) {
|
||||||
|
this._disposables.push(addSocketListener(this._socket, 'message',
|
||||||
|
(ev: MessageEvent | Event | CloseEvent) => terminal.writeUtf8(new Uint8Array((ev as any).data as ArrayBuffer))));
|
||||||
|
} else {
|
||||||
|
this._disposables.push(addSocketListener(this._socket, 'message',
|
||||||
|
(ev: MessageEvent | Event | CloseEvent) => terminal.write((ev as any).data as string)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._bidirectional) {
|
||||||
|
this._disposables.push(terminal.onData(data => this._sendData(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));
|
||||||
|
this._disposables.push(addSocketListener(this._socket, 'error', () => this.dispose()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this._disposables.forEach(d => d.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sendData(data: string): void {
|
||||||
|
// TODO: do something better than just swallowing
|
||||||
|
// the data if the socket is not in a working condition
|
||||||
|
if (this._socket.readyState !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._socket.send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: MessageEvent | Event | CloseEvent) => any): IDisposable {
|
||||||
|
socket.addEventListener(type, handler);
|
||||||
|
return {
|
||||||
|
dispose: () => {
|
||||||
|
if (!handler) {
|
||||||
|
// Already disposed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.removeEventListener(type, handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2015",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es2015"
|
||||||
|
],
|
||||||
|
"rootDir": ".",
|
||||||
|
"outDir": "../lib",
|
||||||
|
"sourceMap": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./**/*",
|
||||||
|
"../../../../typings/xterm.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm';
|
||||||
|
|
||||||
|
declare module '@xtermjs/addon-attach' {
|
||||||
|
export interface IAttachOptions {
|
||||||
|
/**
|
||||||
|
* Whether input should be written to the backend. Defaults to `true`.
|
||||||
|
*/
|
||||||
|
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 {
|
||||||
|
constructor(socket: WebSocket, options?: IAttachOptions);
|
||||||
|
public activate(terminal: Terminal): void;
|
||||||
|
public dispose(): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
lib
|
||||||
|
node_modules
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
**/*.api.js
|
||||||
|
**/*.api.ts
|
||||||
|
tsconfig.json
|
||||||
|
.yarnrc
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "@xtermjs/addon-fit",
|
||||||
|
"version": "0.1.0-beta7",
|
||||||
|
"author": {
|
||||||
|
"name": "The xterm.js authors",
|
||||||
|
"url": "https://xtermjs.org/"
|
||||||
|
},
|
||||||
|
"main": "lib/FitAddon.js",
|
||||||
|
"types": "typings/fit.d.ts",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"prepublishOnly": "../../../node_modules/.bin/tsc -p src"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"xterm": "^3.14.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Terminal, ITerminalAddon } from 'xterm';
|
||||||
|
|
||||||
|
interface ITerminalDimensions {
|
||||||
|
/**
|
||||||
|
* The number of rows in the terminal.
|
||||||
|
*/
|
||||||
|
rows: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of columns in the terminal.
|
||||||
|
*/
|
||||||
|
cols: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FitAddon implements ITerminalAddon {
|
||||||
|
private _terminal: Terminal | undefined;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public activate(terminal: Terminal): void {
|
||||||
|
this._terminal = terminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {}
|
||||||
|
|
||||||
|
public fit(): void {
|
||||||
|
const dims = this.proposeDimensions();
|
||||||
|
if (!dims || !this._terminal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove reliance on private API
|
||||||
|
const core = (<any>this._terminal)._core;
|
||||||
|
|
||||||
|
// Force a full render
|
||||||
|
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {
|
||||||
|
core._renderCoordinator.clear();
|
||||||
|
this._terminal.resize(dims.cols, dims.rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public proposeDimensions(): ITerminalDimensions | undefined {
|
||||||
|
if (!this._terminal) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._terminal.element.parentElement) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove reliance on private API
|
||||||
|
const core = (<any>this._terminal)._core;
|
||||||
|
|
||||||
|
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
|
||||||
|
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||||
|
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||||
|
const elementStyle = window.getComputedStyle(this._terminal.element);
|
||||||
|
const elementPadding = {
|
||||||
|
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||||
|
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||||
|
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||||
|
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||||
|
};
|
||||||
|
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||||
|
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||||
|
const availableHeight = parentElementHeight - elementPaddingVer;
|
||||||
|
const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth;
|
||||||
|
const geometry = {
|
||||||
|
cols: Math.floor(availableWidth / core._renderCoordinator.dimensions.actualCellWidth),
|
||||||
|
rows: Math.floor(availableHeight / core._renderCoordinator.dimensions.actualCellHeight)
|
||||||
|
};
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,16 +4,16 @@
|
|||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es6",
|
"es2015"
|
||||||
],
|
],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../../../lib/addons/attach/",
|
"outDir": "../lib",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"declaration": true
|
"strict": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"./**/*",
|
||||||
"../../../typings/xterm.d.ts"
|
"../../../../typings/xterm.d.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Terminal, ITerminalAddon } from 'xterm';
|
||||||
|
|
||||||
|
declare module '@xtermjs/addon-fit' {
|
||||||
|
/**
|
||||||
|
* An xterm.js addon that enables resizing the terminal to the dimensions of
|
||||||
|
* its containing element.
|
||||||
|
*/
|
||||||
|
export class FitAddon implements ITerminalAddon {
|
||||||
|
/**
|
||||||
|
* Creates a new fit addon.
|
||||||
|
*/
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates the addon
|
||||||
|
* @param terminal The terminal the addon is being loaded in.
|
||||||
|
*/
|
||||||
|
public activate(terminal: Terminal): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes the addon.
|
||||||
|
*/
|
||||||
|
public dispose(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes the terminal to the dimensions of its containing element.
|
||||||
|
*/
|
||||||
|
public fit(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the proposed dimensions that will be used for a fit.
|
||||||
|
*/
|
||||||
|
public proposeDimensions(): ITerminalDimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reprepresents the dimensions of a terminal.
|
||||||
|
*/
|
||||||
|
export interface ITerminalDimensions {
|
||||||
|
/**
|
||||||
|
* The number of rows in the terminal.
|
||||||
|
*/
|
||||||
|
rows: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of columns in the terminal.
|
||||||
|
*/
|
||||||
|
cols: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
lib
|
||||||
|
node_modules
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
**/*.api.js
|
||||||
|
**/*.api.ts
|
||||||
|
tsconfig.json
|
||||||
|
.yarnrc
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "@xtermjs/addon-search",
|
||||||
|
"version": "0.1.0-beta4",
|
||||||
|
"author": {
|
||||||
|
"name": "The xterm.js authors",
|
||||||
|
"url": "https://xtermjs.org/"
|
||||||
|
},
|
||||||
|
"main": "lib/SearchAddon.js",
|
||||||
|
"types": "typings/search.d.ts",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"prepublishOnly": "../../../node_modules/.bin/tsc -p src"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"xterm": "^3.14.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
-8
@@ -3,16 +3,57 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces';
|
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||||
import { IDisposable } from 'xterm';
|
|
||||||
|
export interface ISearchOptions {
|
||||||
|
regex?: boolean;
|
||||||
|
wholeWord?: boolean;
|
||||||
|
caseSensitive?: boolean;
|
||||||
|
incremental?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISearchResult {
|
||||||
|
term: string;
|
||||||
|
col: number;
|
||||||
|
row: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is temporary, link to xtem when new version is published
|
||||||
|
interface INewTerminal extends Terminal {
|
||||||
|
buffer: IBuffer;
|
||||||
|
select(column: number, row: number, length: number): void;
|
||||||
|
getSelectionPosition(): ISelectionPosition | undefined;
|
||||||
|
}
|
||||||
|
interface IBuffer {
|
||||||
|
readonly cursorY: number;
|
||||||
|
readonly cursorX: number;
|
||||||
|
readonly viewportY: number;
|
||||||
|
readonly baseY: number;
|
||||||
|
readonly length: number;
|
||||||
|
getLine(y: number): IBufferLine | undefined;
|
||||||
|
}
|
||||||
|
interface IBufferLine {
|
||||||
|
readonly isWrapped: boolean;
|
||||||
|
getCell(x: number): IBufferCell;
|
||||||
|
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;
|
||||||
|
}
|
||||||
|
interface IBufferCell {
|
||||||
|
readonly char: string;
|
||||||
|
readonly width: number;
|
||||||
|
}
|
||||||
|
interface ISelectionPosition {
|
||||||
|
startColumn: number;
|
||||||
|
startRow: number;
|
||||||
|
endColumn: number;
|
||||||
|
endRow: number;
|
||||||
|
}
|
||||||
|
|
||||||
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
|
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
|
||||||
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
|
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
|
||||||
|
|
||||||
/**
|
export class SearchAddon implements ITerminalAddon {
|
||||||
* A class that knows how to search the terminal and how to display the results.
|
private _terminal: INewTerminal;
|
||||||
*/
|
|
||||||
export class SearchHelper implements ISearchHelper {
|
|
||||||
/**
|
/**
|
||||||
* translateBufferLineToStringWithWrap is a fairly expensive call.
|
* translateBufferLineToStringWithWrap is a fairly expensive call.
|
||||||
* We memoize the calls into an array that has a time based ttl.
|
* We memoize the calls into an array that has a time based ttl.
|
||||||
@@ -23,10 +64,12 @@ export class SearchHelper implements ISearchHelper {
|
|||||||
private _cursorMoveListener: IDisposable | undefined;
|
private _cursorMoveListener: IDisposable | undefined;
|
||||||
private _resizeListener: IDisposable | undefined;
|
private _resizeListener: IDisposable | undefined;
|
||||||
|
|
||||||
constructor(private _terminal: ISearchAddonTerminal) {
|
public activate(terminal: Terminal): void {
|
||||||
this._destroyLinesCache = this._destroyLinesCache.bind(this);
|
this._terminal = <any>terminal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public dispose(): void {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the next instance of the term, then scroll to and select it. If it
|
* Find the next instance of the term, then scroll to and select it. If it
|
||||||
* doesn't exist, do nothing.
|
* doesn't exist, do nothing.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es6",
|
||||||
|
],
|
||||||
|
"rootDir": ".",
|
||||||
|
"outDir": "../lib",
|
||||||
|
"sourceMap": true,
|
||||||
|
"removeComments": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./**/*",
|
||||||
|
"../../../../typings/xterm.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm';
|
||||||
|
|
||||||
|
declare module '@xtermjs/addon-search' {
|
||||||
|
/**
|
||||||
|
* Options for a search.
|
||||||
|
*/
|
||||||
|
export interface ISearchOptions {
|
||||||
|
/**
|
||||||
|
* Whether the search term is a regex.
|
||||||
|
*/
|
||||||
|
regex?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to search for a whole word, the result is only valid if it's
|
||||||
|
* suppounded in "non-word" characters such as `_`, `(`, `)` or space.
|
||||||
|
*/
|
||||||
|
wholeWord?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the search is case sensitive.
|
||||||
|
*/
|
||||||
|
caseSensitive?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to do an indcremental search, this will expand the selection if it
|
||||||
|
* still matches the term the user typed. Note that this only affects
|
||||||
|
* `findNext`, not `findPrevious`.
|
||||||
|
*/
|
||||||
|
incremental?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An xterm.js addon that provides search functionality.
|
||||||
|
*/
|
||||||
|
export class SearchAddon implements ITerminalAddon {
|
||||||
|
/**
|
||||||
|
* Activates the addon
|
||||||
|
* @param terminal The terminal the addon is being loaded in.
|
||||||
|
*/
|
||||||
|
public activate(terminal: Terminal): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes the addon.
|
||||||
|
*/
|
||||||
|
public dispose(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search forwards for the next result that matches the search term and
|
||||||
|
* options.
|
||||||
|
* @param term The search term.
|
||||||
|
* @param searchOptions The options for the search.
|
||||||
|
*/
|
||||||
|
public findNext(term: string, searchOptions?: ISearchOptions): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search backwards for the previous result that matches the search term and
|
||||||
|
* options.
|
||||||
|
* @param term The search term.
|
||||||
|
* @param searchOptions The options for the search.
|
||||||
|
*/
|
||||||
|
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
lib
|
||||||
|
node_modules
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
**/*.api.js
|
||||||
|
**/*.api.ts
|
||||||
|
tsconfig.json
|
||||||
|
.yarnrc
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
## xterm-addon-web-links
|
||||||
|
|
||||||
|
[](https://dev.azure.com/xtermjs/xterm-addon-web-links/_build/latest?definitionId=5&branchName=master)
|
||||||
|
|
||||||
|
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enabled web links. This addon requires xterm.js 3.14+.
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save xterm-addon-web-links
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Terminal } from 'xterm';
|
||||||
|
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||||
|
|
||||||
|
const terminal = new Terminal();
|
||||||
|
terminal.loadAddon(new WebLinksAddon());
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also specify a custom handler and options, see the [API](https://github.com/xtermjs/xterm-addon-web-links/blob/master/typings/web-links.d.ts) for more details.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "@xtermjs/addon-web-links",
|
||||||
|
"version": "0.1.0-beta7",
|
||||||
|
"author": {
|
||||||
|
"name": "The xterm.js authors",
|
||||||
|
"url": "https://xtermjs.org/"
|
||||||
|
},
|
||||||
|
"main": "lib/WebLinksAddon.js",
|
||||||
|
"types": "typings/web-links.d.ts",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"prepublishOnly": "../../../node_modules/.bin/tsc -p src"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"xterm": "^3.14.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* 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 = 800;
|
||||||
|
const height = 600;
|
||||||
|
|
||||||
|
describe('WebLinksAddon', () => {
|
||||||
|
before(async function(): Promise<any> {
|
||||||
|
this.timeout(10000);
|
||||||
|
browser = await puppeteer.launch({
|
||||||
|
headless: process.argv.indexOf('--headless') !== -1,
|
||||||
|
slowMo: 80,
|
||||||
|
args: [`--window-size=${width},${height}`]
|
||||||
|
});
|
||||||
|
page = (await browser.pages())[0];
|
||||||
|
await page.setViewport({ width, height });
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async function(): Promise<any> {
|
||||||
|
this.timeout(5000);
|
||||||
|
await page.goto(APP);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.com', async function(): Promise<any> {
|
||||||
|
this.timeout(20000);
|
||||||
|
await testHostName('foo.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.com.au', async function(): Promise<any> {
|
||||||
|
this.timeout(20000);
|
||||||
|
await testHostName('foo.com.au');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.io', async function(): Promise<any> {
|
||||||
|
this.timeout(20000);
|
||||||
|
await testHostName('foo.io');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function testHostName(hostname: string): Promise<void> {
|
||||||
|
await openTerminal({ rendererType: 'dom' });
|
||||||
|
await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`);
|
||||||
|
await page.evaluate(`
|
||||||
|
window.term.writeln(' http://${hostname} ');
|
||||||
|
window.term.writeln(' http://${hostname}/a~b#c~d?e~f ');
|
||||||
|
window.term.writeln(' http://${hostname}/colon:test ');
|
||||||
|
window.term.writeln(' http://${hostname}/colon:test: ');
|
||||||
|
window.term.writeln('"http://${hostname}/"');
|
||||||
|
window.term.writeln('\\'http://${hostname}/\\'');
|
||||||
|
window.term.writeln('http://${hostname}/subpath/+/id');
|
||||||
|
`);
|
||||||
|
assert.equal(await getLinkAtCell(3, 1), `http://${hostname}`);
|
||||||
|
assert.equal(await getLinkAtCell(3, 2), `http://${hostname}/a~b#c~d?e~f`);
|
||||||
|
assert.equal(await getLinkAtCell(3, 3), `http://${hostname}/colon:test`);
|
||||||
|
assert.equal(await getLinkAtCell(3, 4), `http://${hostname}/colon:test`);
|
||||||
|
assert.equal(await getLinkAtCell(2, 5), `http://${hostname}/`);
|
||||||
|
assert.equal(await getLinkAtCell(2, 6), `http://${hostname}/`);
|
||||||
|
assert.equal(await getLinkAtCell(1, 7), `http://${hostname}/subpath/+/id`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLinkAtCell(col: number, row: number): Promise<string> {
|
||||||
|
const rowSelector = `.xterm-rows > :nth-child(${row})`;
|
||||||
|
await page.hover(`${rowSelector} > :nth-child(${col})`);
|
||||||
|
return await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`);
|
||||||
|
}
|
||||||
+21
-15
@@ -3,7 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Terminal, ILinkMatcherOptions } from 'xterm';
|
import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm';
|
||||||
|
|
||||||
const protocolClause = '(https?:\\/\\/)';
|
const protocolClause = '(https?:\\/\\/)';
|
||||||
const domainCharacterSet = '[\\da-z\\.-]+';
|
const domainCharacterSet = '[\\da-z\\.-]+';
|
||||||
@@ -29,19 +29,25 @@ function handleLink(event: MouseEvent, uri: string): void {
|
|||||||
window.open(uri, '_blank');
|
window.open(uri, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class WebLinksAddon implements ITerminalAddon {
|
||||||
* Initialize the web links addon, registering the link matcher.
|
private _linkMatcherId: number | undefined;
|
||||||
* @param term The terminal to use web links within.
|
private _terminal: Terminal | undefined;
|
||||||
* @param handler A custom handler to use.
|
|
||||||
* @param options Custom options to use, matchIndex will always be ignored.
|
|
||||||
*/
|
|
||||||
export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void {
|
|
||||||
options.matchIndex = 1;
|
|
||||||
term.registerLinkMatcher(strictUrlRegex, handler, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function apply(terminalConstructor: typeof Terminal): void {
|
constructor(
|
||||||
(<any>terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void {
|
private _handler: (event: MouseEvent, uri: string) => void = handleLink,
|
||||||
webLinksInit(this, handler, options);
|
private _options: ILinkMatcherOptions = {}
|
||||||
};
|
) {
|
||||||
|
this._options.matchIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public activate(terminal: Terminal): void {
|
||||||
|
this._terminal = terminal;
|
||||||
|
this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, this._handler, this._options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
if (this._linkMatcherId !== undefined && this._terminal !== undefined) {
|
||||||
|
this._terminal.deregisterLinkMatcher(this._linkMatcherId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,19 +4,16 @@
|
|||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es5"
|
"es2015"
|
||||||
],
|
],
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../../../lib/addons/fit/",
|
"outDir": "../lib",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"declaration": true,
|
"strict": true
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"./**/*",
|
||||||
"../../../typings/xterm.d.ts"
|
"../../../../typings/xterm.d.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import { Terminal, IDisposable, ILinkMatcherOptions } from 'xterm';
|
||||||
|
|
||||||
|
declare module '@xtermjs/addon-web-links' {
|
||||||
|
// TODO: This is temporary, link to xterm when the new version is published
|
||||||
|
export interface ITerminalAddon extends IDisposable {
|
||||||
|
activate(terminal: Terminal): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An xterm.js addon that enables web links.
|
||||||
|
*/
|
||||||
|
export class WebLinksAddon implements ITerminalAddon {
|
||||||
|
/**
|
||||||
|
* Creates a new web links addon.
|
||||||
|
* @param handler The callback when the link is called.
|
||||||
|
* @param options Options for the link matcher.
|
||||||
|
*/
|
||||||
|
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates the addon
|
||||||
|
* @param terminal The terminal the addon is being loaded in.
|
||||||
|
*/
|
||||||
|
public activate(terminal: Terminal): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes the addon.
|
||||||
|
*/
|
||||||
|
public dispose(): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-9
@@ -12,11 +12,10 @@ import { Terminal } from '../out/public/Terminal';
|
|||||||
// Use webpacked version (yarn package)
|
// Use webpacked version (yarn package)
|
||||||
// import { Terminal } from '../lib/xterm';
|
// import { Terminal } from '../lib/xterm';
|
||||||
|
|
||||||
import { AttachAddon } from 'xterm-addon-attach';
|
import { AttachAddon } from '@xtermjs/addon-attach';
|
||||||
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
|
import { FitAddon } from '@xtermjs/addon-fit';
|
||||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
import { SearchAddon, ISearchOptions } from '@xtermjs/addon-search';
|
||||||
|
import { WebLinksAddon } from '@xtermjs/addon-web-links';
|
||||||
import * as fit from '../lib/addons/fit/fit';
|
|
||||||
|
|
||||||
// Pulling in the module's types relies on the <reference> above, it's looks a
|
// Pulling in the module's types relies on the <reference> above, it's looks a
|
||||||
// little weird here as we're importing "this" module
|
// little weird here as we're importing "this" module
|
||||||
@@ -25,12 +24,15 @@ import { Terminal as TerminalType, ITerminalOptions } from 'xterm';
|
|||||||
export interface IWindowWithTerminal extends Window {
|
export interface IWindowWithTerminal extends Window {
|
||||||
term: TerminalType;
|
term: TerminalType;
|
||||||
Terminal?: typeof TerminalType;
|
Terminal?: typeof TerminalType;
|
||||||
|
AttachAddon?: typeof AttachAddon;
|
||||||
|
FitAddon?: typeof FitAddon;
|
||||||
|
SearchAddon?: typeof SearchAddon;
|
||||||
|
WebLinksAddon?: typeof WebLinksAddon;
|
||||||
}
|
}
|
||||||
declare let window: IWindowWithTerminal;
|
declare let window: IWindowWithTerminal;
|
||||||
|
|
||||||
Terminal.applyAddon(fit);
|
|
||||||
|
|
||||||
let term;
|
let term;
|
||||||
|
let fitAddon: FitAddon;
|
||||||
let searchAddon: SearchAddon;
|
let searchAddon: SearchAddon;
|
||||||
let protocol;
|
let protocol;
|
||||||
let socketURL;
|
let socketURL;
|
||||||
@@ -74,6 +76,10 @@ const disposeRecreateButtonHandler = () => {
|
|||||||
|
|
||||||
if (document.location.pathname === '/test') {
|
if (document.location.pathname === '/test') {
|
||||||
window.Terminal = Terminal;
|
window.Terminal = Terminal;
|
||||||
|
window.AttachAddon = AttachAddon;
|
||||||
|
window.FitAddon = FitAddon;
|
||||||
|
window.SearchAddon = SearchAddon;
|
||||||
|
window.WebLinksAddon = WebLinksAddon;
|
||||||
} else {
|
} else {
|
||||||
createTerminal();
|
createTerminal();
|
||||||
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
|
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
|
||||||
@@ -95,6 +101,8 @@ function createTerminal(): void {
|
|||||||
typedTerm.loadAddon(new WebLinksAddon());
|
typedTerm.loadAddon(new WebLinksAddon());
|
||||||
searchAddon = new SearchAddon();
|
searchAddon = new SearchAddon();
|
||||||
typedTerm.loadAddon(searchAddon);
|
typedTerm.loadAddon(searchAddon);
|
||||||
|
fitAddon = new FitAddon();
|
||||||
|
typedTerm.loadAddon(fitAddon);
|
||||||
|
|
||||||
window.term = term; // Expose `term` to window for debugging purposes
|
window.term = term; // Expose `term` to window for debugging purposes
|
||||||
term.onResize((size: { cols: number, rows: number }) => {
|
term.onResize((size: { cols: number, rows: number }) => {
|
||||||
@@ -111,7 +119,7 @@ function createTerminal(): void {
|
|||||||
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
|
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
|
||||||
|
|
||||||
term.open(terminalContainer);
|
term.open(terminalContainer);
|
||||||
term.fit();
|
fitAddon.fit();
|
||||||
term.focus();
|
term.focus();
|
||||||
|
|
||||||
addDomListener(paddingElement, 'change', setPadding);
|
addDomListener(paddingElement, 'change', setPadding);
|
||||||
@@ -303,5 +311,5 @@ function updateTerminalSize(): void {
|
|||||||
const height = (rows * term._core._renderCoordinator.dimensions.actualCellHeight).toString() + 'px';
|
const height = (rows * term._core._renderCoordinator.dimensions.actualCellHeight).toString() + 'px';
|
||||||
terminalContainer.style.width = width;
|
terminalContainer.style.width = width;
|
||||||
terminalContainer.style.height = height;
|
terminalContainer.style.height = height;
|
||||||
term.fit();
|
fitAddon.fit();
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -43,7 +43,11 @@ const clientConfig = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: [path.resolve(__dirname, '..'), 'node_modules'],
|
modules: [
|
||||||
|
'node_modules',
|
||||||
|
path.resolve(__dirname, '..'),
|
||||||
|
path.resolve(__dirname, '../addons')
|
||||||
|
],
|
||||||
extensions: [ '.tsx', '.ts', '.js' ],
|
extensions: [ '.tsx', '.ts', '.js' ],
|
||||||
alias: {
|
alias: {
|
||||||
common: path.resolve('./out/common'),
|
common: path.resolve('./out/common'),
|
||||||
|
|||||||
+2
-2
@@ -2,8 +2,8 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>xterm.js integration test fixture</title>
|
<title>xterm.js integration test fixture</title>
|
||||||
<link rel="stylesheet" href="/src/xterm.css" />
|
<link rel="stylesheet" href="/xterm.css" />
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="/style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="terminal-container"></div>
|
<div id="terminal-container"></div>
|
||||||
|
|||||||
+9
-2
@@ -1,9 +1,16 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es5",
|
"target": "es2015",
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@xtermjs/addon-attach": ["../addons/@xtermjs/addon-attach"],
|
||||||
|
"@xtermjs/addon-fit": ["../addons/@xtermjs/addon-fit"],
|
||||||
|
"@xtermjs/addon-search": ["../addons/@xtermjs/addon-search"],
|
||||||
|
"@xtermjs/addon-web-links": ["../addons/@xtermjs/addon-web-links"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"client.ts",
|
"client.ts",
|
||||||
|
|||||||
+17
-20
@@ -6,6 +6,21 @@
|
|||||||
"types": "typings/xterm.d.ts",
|
"types": "typings/xterm.d.ts",
|
||||||
"repository": "https://github.com/xtermjs/xterm.js",
|
"repository": "https://github.com/xtermjs/xterm.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"prepackage": "npm run build",
|
||||||
|
"package": "webpack",
|
||||||
|
"start": "node demo/start",
|
||||||
|
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts' './addons/**/*.ts'",
|
||||||
|
"test": "npm run test-unit",
|
||||||
|
"posttest": "npm run lint",
|
||||||
|
"test-api": "mocha \"**/*.api.js\"",
|
||||||
|
"test-unit": "node ./bin/test.js",
|
||||||
|
"build": "tsc -b ./tsconfig.all.json",
|
||||||
|
"prepare": "npm run build",
|
||||||
|
"prepublishOnly": "npm run package",
|
||||||
|
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
|
||||||
|
"clean": "rm -rf lib out addons/@xtermjs/*/lib"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^3.4.34",
|
"@types/chai": "^3.4.34",
|
||||||
"@types/glob": "^5.0.35",
|
"@types/glob": "^5.0.35",
|
||||||
@@ -15,6 +30,7 @@
|
|||||||
"@types/puppeteer": "^1.12.4",
|
"@types/puppeteer": "^1.12.4",
|
||||||
"@types/utf8": "^2.1.6",
|
"@types/utf8": "^2.1.6",
|
||||||
"@types/webpack": "^4.4.11",
|
"@types/webpack": "^4.4.11",
|
||||||
|
"@types/ws": "^6.0.1",
|
||||||
"chai": "3.5.0",
|
"chai": "3.5.0",
|
||||||
"express": "4.13.4",
|
"express": "4.13.4",
|
||||||
"express-ws": "2.0.0-rc.1",
|
"express-ws": "2.0.0-rc.1",
|
||||||
@@ -31,25 +47,6 @@
|
|||||||
"utf8": "^3.0.0",
|
"utf8": "^3.0.0",
|
||||||
"webpack": "^4.17.1",
|
"webpack": "^4.17.1",
|
||||||
"webpack-cli": "^3.1.0",
|
"webpack-cli": "^3.1.0",
|
||||||
"xterm-addon-attach": "0.1.0-beta8",
|
"ws": "^7.0.0"
|
||||||
"xterm-addon-search": "0.1.0-beta4",
|
|
||||||
"xterm-addon-web-links": "0.1.0-beta6"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"prepackage": "npm run build",
|
|
||||||
"package": "webpack",
|
|
||||||
"start": "node demo/start",
|
|
||||||
"start-debug": "node --inspect-brk demo/start",
|
|
||||||
"start-zmodem": "node demo/zmodem/app",
|
|
||||||
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts'",
|
|
||||||
"test-unit": "node ./bin/test.js",
|
|
||||||
"test": "npm run test-unit",
|
|
||||||
"posttest": "npm run lint",
|
|
||||||
"test-api": "mocha \"**/*.api.js\"",
|
|
||||||
"build": "tsc -b ./src/tsconfig.all.json",
|
|
||||||
"prepare": "npm run build",
|
|
||||||
"prepublishOnly": "npm run package",
|
|
||||||
"coveralls": "nyc report --reporter=text-lcov | coveralls",
|
|
||||||
"watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*
|
|
||||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Terminal, IDisposable } from 'xterm';
|
|
||||||
|
|
||||||
export interface IAttachAddonTerminal extends Terminal {
|
|
||||||
_core: {
|
|
||||||
register<T extends IDisposable>(d: T): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
__socket?: WebSocket;
|
|
||||||
__attachSocketBuffer?: string;
|
|
||||||
__dataListener?: IDisposable;
|
|
||||||
|
|
||||||
__getMessage?(ev: MessageEvent): void;
|
|
||||||
__flushBuffer?(): void;
|
|
||||||
__pushToBuffer?(data: string): void;
|
|
||||||
__sendData?(data: string): void;
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import * as attach from './attach';
|
|
||||||
|
|
||||||
class MockTerminal {}
|
|
||||||
|
|
||||||
describe('attach addon', () => {
|
|
||||||
describe('apply', () => {
|
|
||||||
it('should do register the `attach` and `detach` methods', () => {
|
|
||||||
attach.apply(<any>MockTerminal);
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.attach, 'function');
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.detach, 'function');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*
|
|
||||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Terminal, IDisposable } from 'xterm';
|
|
||||||
import { IAttachAddonTerminal } from './Interfaces';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches the given terminal to the given socket.
|
|
||||||
*
|
|
||||||
* @param term The terminal to be attached to the given socket.
|
|
||||||
* @param socket The socket to attach the current terminal.
|
|
||||||
* @param bidirectional Whether the terminal should send data to the socket as well.
|
|
||||||
* @param buffered Whether the rendering of incoming data should happen instantly or at a maximum
|
|
||||||
* frequency of 1 rendering per 10ms.
|
|
||||||
*/
|
|
||||||
export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
|
||||||
const addonTerminal = <IAttachAddonTerminal>term;
|
|
||||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
|
||||||
addonTerminal.__socket = socket;
|
|
||||||
|
|
||||||
addonTerminal.__flushBuffer = () => {
|
|
||||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
|
||||||
addonTerminal.__attachSocketBuffer = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
addonTerminal.__pushToBuffer = (data: string) => {
|
|
||||||
if (addonTerminal.__attachSocketBuffer) {
|
|
||||||
addonTerminal.__attachSocketBuffer += data;
|
|
||||||
} else {
|
|
||||||
addonTerminal.__attachSocketBuffer = data;
|
|
||||||
setTimeout(addonTerminal.__flushBuffer, 10);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: This should be typed but there seem to be issues importing the type
|
|
||||||
let myTextDecoder: any;
|
|
||||||
|
|
||||||
addonTerminal.__getMessage = function(ev: MessageEvent): void {
|
|
||||||
let str: string;
|
|
||||||
|
|
||||||
if (typeof ev.data === 'object') {
|
|
||||||
if (!myTextDecoder) {
|
|
||||||
myTextDecoder = new TextDecoder();
|
|
||||||
}
|
|
||||||
if (ev.data instanceof ArrayBuffer) {
|
|
||||||
str = myTextDecoder.decode(ev.data);
|
|
||||||
displayData(str);
|
|
||||||
} else {
|
|
||||||
const fileReader = new FileReader();
|
|
||||||
|
|
||||||
fileReader.addEventListener('load', () => {
|
|
||||||
str = myTextDecoder.decode(fileReader.result);
|
|
||||||
displayData(str);
|
|
||||||
});
|
|
||||||
fileReader.readAsArrayBuffer(ev.data);
|
|
||||||
}
|
|
||||||
} else if (typeof ev.data === 'string') {
|
|
||||||
displayData(ev.data);
|
|
||||||
} else {
|
|
||||||
throw Error(`Cannot handle "${typeof ev.data}" websocket message.`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push data to buffer or write it in the terminal.
|
|
||||||
* This is used as a callback for FileReader.onload.
|
|
||||||
*
|
|
||||||
* @param str String decoded by FileReader.
|
|
||||||
* @param data The data of the EventMessage.
|
|
||||||
*/
|
|
||||||
function displayData(str?: string, data?: string): void {
|
|
||||||
if (buffered) {
|
|
||||||
addonTerminal.__pushToBuffer(str || data);
|
|
||||||
} else {
|
|
||||||
addonTerminal.write(str || data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addonTerminal.__sendData = (data: string) => {
|
|
||||||
if (socket.readyState !== 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
socket.send(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));
|
|
||||||
|
|
||||||
if (bidirectional) {
|
|
||||||
addonTerminal.__dataListener = addonTerminal.onData(addonTerminal.__sendData);
|
|
||||||
addonTerminal._core.register(addonTerminal.__dataListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket)));
|
|
||||||
addonTerminal._core.register(addSocketListener(socket, 'error', () => detach(addonTerminal, socket)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: Event) => any): IDisposable {
|
|
||||||
socket.addEventListener(type, handler);
|
|
||||||
return {
|
|
||||||
dispose: () => {
|
|
||||||
if (!handler) {
|
|
||||||
// Already disposed
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
socket.removeEventListener(type, handler);
|
|
||||||
handler = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detaches the given terminal from the given socket
|
|
||||||
*
|
|
||||||
* @param term The terminal to be detached from the given socket.
|
|
||||||
* @param socket The socket from which to detach the current terminal.
|
|
||||||
*/
|
|
||||||
export function detach(term: Terminal, socket: WebSocket): void {
|
|
||||||
const addonTerminal = <IAttachAddonTerminal>term;
|
|
||||||
addonTerminal.__dataListener.dispose();
|
|
||||||
addonTerminal.__dataListener = undefined;
|
|
||||||
|
|
||||||
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
|
|
||||||
|
|
||||||
if (socket) {
|
|
||||||
socket.removeEventListener('message', addonTerminal.__getMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete addonTerminal.__socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function apply(terminalConstructor: typeof Terminal): void {
|
|
||||||
/**
|
|
||||||
* Attaches the current terminal to the given socket
|
|
||||||
*
|
|
||||||
* @param socket The socket to attach the current terminal.
|
|
||||||
* @param bidirectional Whether the terminal should send data to the socket as well.
|
|
||||||
* @param buffered Whether the rendering of incoming data should happen instantly or at a maximum
|
|
||||||
* frequency of 1 rendering per 10ms.
|
|
||||||
*/
|
|
||||||
(<any>terminalConstructor.prototype).attach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
|
||||||
attach(this, socket, bidirectional, buffered);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detaches the current terminal from the given socket.
|
|
||||||
*
|
|
||||||
* @param socket The socket from which to detach the current terminal.
|
|
||||||
*/
|
|
||||||
(<any>terminalConstructor.prototype).detach = function (socket: WebSocket): void {
|
|
||||||
detach(this, socket);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="../../src/xterm.css" />
|
|
||||||
<link rel="stylesheet" href="../../demo/style.css" />
|
|
||||||
<script src="../../src/xterm.js"></script>
|
|
||||||
<script src="attach.js"></script>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
color: #111;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
color: #444;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, button {
|
|
||||||
line-height: 22px;
|
|
||||||
font-size: 16px;
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
height: 22px;
|
|
||||||
padding-left: 4px;
|
|
||||||
padding-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
height: 28px;
|
|
||||||
background-color: #ccc;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
<h1>
|
|
||||||
xterm.js: socket attach
|
|
||||||
</h1>
|
|
||||||
<p>
|
|
||||||
Attach the terminal to a WebSocket terminal stream with ease. Perfect for attaching to your
|
|
||||||
Docker containers.
|
|
||||||
</p>
|
|
||||||
<h2>
|
|
||||||
Socket information
|
|
||||||
</h2>
|
|
||||||
<form id="socket-form">
|
|
||||||
<input id="socket-url"
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter socket url (e.g. ws://mysock)"
|
|
||||||
autofocus />
|
|
||||||
<button>
|
|
||||||
Attach
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<div id="terminal-container"></div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
var term = new Terminal(),
|
|
||||||
container = document.getElementById('terminal-container'),
|
|
||||||
socketUrl = document.getElementById('socket-url'),
|
|
||||||
socketForm = document.getElementById('socket-form');
|
|
||||||
|
|
||||||
socketForm.addEventListener('submit', function (ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
var url = socketUrl.value,
|
|
||||||
sock = new WebSocket(url);
|
|
||||||
sock.addEventListener('open', function () {
|
|
||||||
term.attach(sock);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
term.open(container);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "xterm.attach",
|
|
||||||
"main": "attach.js",
|
|
||||||
"private": true
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
## fit addon
|
|
||||||
|
|
||||||
The fit addon adjusts the dimensions of the terminal to match best fit its parent element container. `fit` will only work when the element is visible.
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import * as fit from './fit';
|
|
||||||
|
|
||||||
class MockTerminal {}
|
|
||||||
|
|
||||||
describe('fit addon', () => {
|
|
||||||
describe('apply', () => {
|
|
||||||
it('should do register the `proposeGeometry` and `fit` methods', () => {
|
|
||||||
fit.apply(<any>MockTerminal);
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.proposeGeometry, 'function');
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.fit, 'function');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*
|
|
||||||
* Fit terminal columns and rows to the dimensions of its DOM element.
|
|
||||||
*
|
|
||||||
* ## Approach
|
|
||||||
*
|
|
||||||
* Rows: Truncate the division of the terminal parent element height by the
|
|
||||||
* terminal row height.
|
|
||||||
* Columns: Truncate the division of the terminal parent element width by the
|
|
||||||
* terminal character width (apply display: inline at the terminal
|
|
||||||
* row and truncate its width with the current number of columns).
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Terminal } from 'xterm';
|
|
||||||
|
|
||||||
export interface IGeometry {
|
|
||||||
rows: number;
|
|
||||||
cols: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function proposeGeometry(term: Terminal): IGeometry {
|
|
||||||
if (!term.element.parentElement) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const parentElementStyle = window.getComputedStyle(term.element.parentElement);
|
|
||||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
|
||||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
|
||||||
const elementStyle = window.getComputedStyle(term.element);
|
|
||||||
const elementPadding = {
|
|
||||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
|
||||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
|
||||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
|
||||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
|
||||||
};
|
|
||||||
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
|
||||||
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
|
||||||
const availableHeight = parentElementHeight - elementPaddingVer;
|
|
||||||
const availableWidth = parentElementWidth - elementPaddingHor - (<any>term)._core.viewport.scrollBarWidth;
|
|
||||||
const geometry = {
|
|
||||||
cols: Math.floor(availableWidth / (<any>term)._core._renderCoordinator.dimensions.actualCellWidth),
|
|
||||||
rows: Math.floor(availableHeight / (<any>term)._core._renderCoordinator.dimensions.actualCellHeight)
|
|
||||||
};
|
|
||||||
return geometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fit(term: Terminal): void {
|
|
||||||
const geometry = proposeGeometry(term);
|
|
||||||
if (geometry) {
|
|
||||||
// Force a full render
|
|
||||||
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
|
|
||||||
(<any>term)._core._renderCoordinator.clear();
|
|
||||||
term.resize(geometry.cols, geometry.rows);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function apply(terminalConstructor: typeof Terminal): void {
|
|
||||||
(<any>terminalConstructor.prototype).proposeGeometry = function (): IGeometry {
|
|
||||||
return proposeGeometry(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
(<any>terminalConstructor.prototype).fit = function (): void {
|
|
||||||
fit(this);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "xterm.fit",
|
|
||||||
"main": "fit.js",
|
|
||||||
"private": true
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
.xterm.fullscreen {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
z-index: 255;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import * as fullscreen from './fullscreen';
|
|
||||||
|
|
||||||
class MockTerminal {}
|
|
||||||
|
|
||||||
describe('fullscreen addon', () => {
|
|
||||||
describe('apply', () => {
|
|
||||||
it('should do register the `toggleFullscreen` method', () => {
|
|
||||||
fullscreen.apply(<any>MockTerminal);
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.toggleFullScreen, 'function');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Terminal } from 'xterm';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the given terminal's fullscreen mode.
|
|
||||||
* @param term The terminal to toggle full screen mode
|
|
||||||
* @param fullscreen Toggle fullscreen on (true) or off (false)
|
|
||||||
*/
|
|
||||||
export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
|
|
||||||
let fn: (...tokens: string[]) => void;
|
|
||||||
|
|
||||||
if (typeof fullscreen === 'undefined') {
|
|
||||||
fn = (term.element.classList.contains('fullscreen')) ?
|
|
||||||
term.element.classList.remove : term.element.classList.add;
|
|
||||||
} else if (!fullscreen) {
|
|
||||||
fn = term.element.classList.remove;
|
|
||||||
} else {
|
|
||||||
fn = term.element.classList.add;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn = fn.bind(term.element.classList);
|
|
||||||
fn('fullscreen');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function apply(terminalConstructor: typeof Terminal): void {
|
|
||||||
(<any>terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {
|
|
||||||
toggleFullScreen(this, fullscreen);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "xterm.fullscreen",
|
|
||||||
"main": "fullscreen.js",
|
|
||||||
"private": true
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"lib": [
|
|
||||||
"dom",
|
|
||||||
"es5"
|
|
||||||
],
|
|
||||||
"rootDir": ".",
|
|
||||||
"outDir": "../../../lib/addons/fullscreen/",
|
|
||||||
"sourceMap": true,
|
|
||||||
"removeComments": true,
|
|
||||||
"declaration": true,
|
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"**/*.ts",
|
|
||||||
"../../../typings/xterm.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Terminal } from 'xterm';
|
|
||||||
|
|
||||||
export interface ISearchAddonTerminal extends Terminal {
|
|
||||||
__searchHelper?: ISearchHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISearchHelper {
|
|
||||||
findNext(term: string, searchOptions: ISearchOptions): boolean;
|
|
||||||
findPrevious(term: string, searchOptions: ISearchOptions): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISearchOptions {
|
|
||||||
regex?: boolean;
|
|
||||||
wholeWord?: boolean;
|
|
||||||
caseSensitive?: boolean;
|
|
||||||
/**
|
|
||||||
* Use this when you want the selection to expand if it still matches as the
|
|
||||||
* user types. Note that this only affects findNext.
|
|
||||||
*/
|
|
||||||
incremental?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISearchResult {
|
|
||||||
term: string;
|
|
||||||
col: number;
|
|
||||||
row: number;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "xterm.search",
|
|
||||||
"main": "search.js",
|
|
||||||
"private": true
|
|
||||||
}
|
|
||||||
@@ -1,358 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
declare var require: any;
|
|
||||||
|
|
||||||
import { assert, expect } from 'chai';
|
|
||||||
import * as search from './search';
|
|
||||||
import { SearchHelper } from './SearchHelper';
|
|
||||||
import { ISearchOptions, ISearchResult } from './Interfaces';
|
|
||||||
|
|
||||||
class MockTerminalPlain {}
|
|
||||||
|
|
||||||
class MockTerminal {
|
|
||||||
private _core: any;
|
|
||||||
public searchHelper: TestSearchHelper;
|
|
||||||
public cols: number;
|
|
||||||
constructor(options: any) {
|
|
||||||
this._core = new (require('../../../out/Terminal')).Terminal(options);
|
|
||||||
this.searchHelper = new TestSearchHelper(this as any);
|
|
||||||
this.cols = options.cols;
|
|
||||||
}
|
|
||||||
get core(): any {
|
|
||||||
return this._core;
|
|
||||||
}
|
|
||||||
get buffer(): IBuffer {
|
|
||||||
// TODO: This is a hacky workaround until we use puppeteer for addon tests
|
|
||||||
const buffer = this._core.buffer;
|
|
||||||
return {
|
|
||||||
cursorY: buffer.y,
|
|
||||||
cursorX: buffer.x,
|
|
||||||
viewportY: buffer.ydisp,
|
|
||||||
baseY: buffer.ybase,
|
|
||||||
length: buffer.length,
|
|
||||||
getLine(y: number): IBufferLine {
|
|
||||||
return {
|
|
||||||
isWrapped: buffer.lines.get(y) ? buffer.lines.get(y).isWrapped : false,
|
|
||||||
getCell(x: number): IBufferCell {
|
|
||||||
return {
|
|
||||||
char: buffer.lines.get(y).get(x)[1/*CHAR_DATA_CHAR_INDEX*/],
|
|
||||||
width: buffer.lines.get(y).get(x)[2/*CHAR_DATA_WIDTH_INDEX*/]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {
|
|
||||||
return buffer.translateBufferLineToString(y, trimRight);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pushWriteData(): void {
|
|
||||||
this._core._innerWrite();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IBuffer {
|
|
||||||
readonly cursorY: number;
|
|
||||||
readonly cursorX: number;
|
|
||||||
readonly viewportY: number;
|
|
||||||
readonly baseY: number;
|
|
||||||
readonly length: number;
|
|
||||||
getLine(y: number): IBufferLine | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IBufferLine {
|
|
||||||
readonly isWrapped: boolean;
|
|
||||||
getCell(x: number): IBufferCell;
|
|
||||||
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IBufferCell {
|
|
||||||
readonly char: string;
|
|
||||||
readonly width: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestSearchHelper extends SearchHelper {
|
|
||||||
public findInLine(term: string, rowNumber: number, searchOptions?: ISearchOptions): ISearchResult {
|
|
||||||
return this._findInLine(term, rowNumber, 0, searchOptions);
|
|
||||||
}
|
|
||||||
public findFromIndex(term: string, row: number, col: number, searchOptions?: ISearchOptions, isReverseSearch?: boolean): ISearchResult {
|
|
||||||
return this._findInLine(term, row, col, searchOptions, isReverseSearch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('search addon', () => {
|
|
||||||
describe('apply', () => {
|
|
||||||
it('should register findNext and findPrevious', () => {
|
|
||||||
search.apply(<any>MockTerminalPlain);
|
|
||||||
assert.equal(typeof (<any>MockTerminalPlain).prototype.findNext, 'function');
|
|
||||||
assert.equal(typeof (<any>MockTerminalPlain).prototype.findPrevious, 'function');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('find', () => {
|
|
||||||
it('Searchhelper - should find correct position', () => {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 3});
|
|
||||||
term.core.write('Hello World\r\ntest\n123....hello');
|
|
||||||
term.pushWriteData();
|
|
||||||
const hello0 = term.searchHelper.findInLine('Hello', 0);
|
|
||||||
const hello1 = term.searchHelper.findInLine('Hello', 1);
|
|
||||||
const hello2 = term.searchHelper.findInLine('Hello', 2);
|
|
||||||
expect(hello0).eql({col: 0, row: 0, term: 'Hello'});
|
|
||||||
expect(hello1).eql(undefined);
|
|
||||||
expect(hello2).eql({col: 11, row: 2, term: 'Hello'});
|
|
||||||
});
|
|
||||||
it('should find search term accross line wrap', () => {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 10, rows: 5});
|
|
||||||
term.core.write('texttextHellotext\r\n');
|
|
||||||
term.core.write('texttexttextHellotext goodbye');
|
|
||||||
term.pushWriteData();
|
|
||||||
/*
|
|
||||||
texttextHe
|
|
||||||
llotext
|
|
||||||
texttextte
|
|
||||||
xtHellotex
|
|
||||||
t (these spaces included intentionally)
|
|
||||||
goodbye
|
|
||||||
*/
|
|
||||||
|
|
||||||
const hello0 = term.searchHelper.findInLine('Hello', 0);
|
|
||||||
const hello1 = term.searchHelper.findInLine('Hello', 1);
|
|
||||||
const hello2 = term.searchHelper.findInLine('Hello', 2);
|
|
||||||
const hello3 = term.searchHelper.findInLine('Hello', 3);
|
|
||||||
const llo = term.searchHelper.findInLine('llo', 1);
|
|
||||||
const goodbye = term.searchHelper.findInLine('goodbye', 2);
|
|
||||||
expect(hello0).eql({col: 8, row: 0, term: 'Hello'});
|
|
||||||
expect(hello1).eql(undefined);
|
|
||||||
expect(hello2).eql({col: 2, row: 3, term: 'Hello'});
|
|
||||||
expect(hello3).eql(undefined);
|
|
||||||
expect(llo).eql(undefined);
|
|
||||||
expect(goodbye).eql({col: 0, row: 5, term: 'goodbye'});
|
|
||||||
term.core.resize(9, 5);
|
|
||||||
const hello0Resize = term.searchHelper.findInLine('Hello', 0);
|
|
||||||
expect(hello0Resize).eql({col: 8, row: 0, term: 'Hello'});
|
|
||||||
});
|
|
||||||
it('should respect search regex', () => {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 10, rows: 4});
|
|
||||||
term.core.write('abcdefghijklmnopqrstuvwxyz\r\n~/dev ');
|
|
||||||
/*
|
|
||||||
abcdefghij
|
|
||||||
klmnopqrst
|
|
||||||
uvwxyz
|
|
||||||
~/dev
|
|
||||||
*/
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: true,
|
|
||||||
wholeWord: false,
|
|
||||||
caseSensitive: false
|
|
||||||
};
|
|
||||||
const hello0 = term.searchHelper.findInLine('dee*', 0, searchOptions);
|
|
||||||
const hello1 = term.searchHelper.findInLine('jkk*', 0, searchOptions);
|
|
||||||
const hello2 = term.searchHelper.findInLine('mnn*', 1, searchOptions);
|
|
||||||
const tilda0 = term.searchHelper.findInLine('^~', 3, searchOptions);
|
|
||||||
const tilda1 = term.searchHelper.findInLine('^[~]', 3, searchOptions);
|
|
||||||
const tilda2 = term.searchHelper.findInLine('^\\~', 3, searchOptions);
|
|
||||||
expect(hello0).eql({col: 3, row: 0, term: 'de'});
|
|
||||||
expect(hello1).eql({col: 9, row: 0, term: 'jk'});
|
|
||||||
expect(hello2).eql(undefined);
|
|
||||||
expect(tilda0).eql({col: 0, row: 3, term: '~'});
|
|
||||||
expect(tilda1).eql({col: 0, row: 3, term: '~'});
|
|
||||||
expect(tilda2).eql({col: 0, row: 3, term: '~'});
|
|
||||||
});
|
|
||||||
it('should not select empty lines', () => {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 3});
|
|
||||||
const line = term.searchHelper.findInLine('^.*$', 0, { regex: true });
|
|
||||||
expect(line).eql(undefined);
|
|
||||||
});
|
|
||||||
it('should respect case sensitive', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 4});
|
|
||||||
term.core.write('Hello World\r\n123....hello\r\nmoreTestHello');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: false,
|
|
||||||
wholeWord: false,
|
|
||||||
caseSensitive: true
|
|
||||||
};
|
|
||||||
const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions);
|
|
||||||
const hello1 = term.searchHelper.findInLine('Hello', 1, searchOptions);
|
|
||||||
const hello2 = term.searchHelper.findInLine('Hello', 2, searchOptions);
|
|
||||||
expect(hello0).eql({col: 0, row: 0, term: 'Hello'});
|
|
||||||
expect(hello1).eql(undefined);
|
|
||||||
expect(hello2).eql({col: 8, row: 2, term: 'Hello'});
|
|
||||||
});
|
|
||||||
it('should respect case sensitive + regex', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 4});
|
|
||||||
term.core.write('hellohello\r\nHelloHello');
|
|
||||||
term.pushWriteData();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* hellohello
|
|
||||||
* HelloHello
|
|
||||||
*/
|
|
||||||
|
|
||||||
const searchOptions = {
|
|
||||||
regex: true,
|
|
||||||
wholeWord: false,
|
|
||||||
caseSensitive: true
|
|
||||||
};
|
|
||||||
const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions);
|
|
||||||
const hello1 = term.searchHelper.findInLine('Hello$', 0, searchOptions);
|
|
||||||
const hello2 = term.searchHelper.findInLine('Hello', 1, searchOptions);
|
|
||||||
const hello3 = term.searchHelper.findInLine('Hello$', 1, searchOptions);
|
|
||||||
expect(hello0).eql(undefined);
|
|
||||||
expect(hello1).eql(undefined);
|
|
||||||
expect(hello2).eql({col: 0, row: 1, term: 'Hello'});
|
|
||||||
expect(hello3).eql({col: 5, row: 1, term: 'Hello'});
|
|
||||||
});
|
|
||||||
it('should respect whole-word search option', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('Hello World\r\nWorld Hello\r\nWorldHelloWorld\r\nHelloWorld\r\nWorldHello');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: false,
|
|
||||||
wholeWord: true,
|
|
||||||
caseSensitive: false
|
|
||||||
};
|
|
||||||
const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions);
|
|
||||||
const hello1 = term.searchHelper.findInLine('Hello', 1, searchOptions);
|
|
||||||
const hello2 = term.searchHelper.findInLine('Hello', 2, searchOptions);
|
|
||||||
const hello3 = term.searchHelper.findInLine('Hello', 3, searchOptions);
|
|
||||||
const hello4 = term.searchHelper.findInLine('Hello', 4, searchOptions);
|
|
||||||
expect(hello0).eql({col: 0, row: 0, term: 'Hello'});
|
|
||||||
expect(hello1).eql({col: 6, row: 1, term: 'Hello'});
|
|
||||||
expect(hello2).eql(undefined);
|
|
||||||
expect(hello3).eql(undefined);
|
|
||||||
expect(hello4).eql(undefined);
|
|
||||||
});
|
|
||||||
it('should respect whole-word + case sensitive search options', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('Hello World\r\nHelloWorld');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: false,
|
|
||||||
wholeWord: true,
|
|
||||||
caseSensitive: true
|
|
||||||
};
|
|
||||||
const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions);
|
|
||||||
const hello1 = term.searchHelper.findInLine('hello', 0, searchOptions);
|
|
||||||
const hello2 = term.searchHelper.findInLine('Hello', 1, searchOptions);
|
|
||||||
const hello3 = term.searchHelper.findInLine('hello', 1, searchOptions);
|
|
||||||
expect(hello0).eql({col: 0, row: 0, term: 'Hello'});
|
|
||||||
expect(hello1).eql(undefined);
|
|
||||||
expect(hello2).eql(undefined);
|
|
||||||
expect(hello3).eql(undefined);
|
|
||||||
});
|
|
||||||
it('should respect whole-word + regex search options', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('Hello World Hello\r\nHelloWorldHello');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: true,
|
|
||||||
wholeWord: true,
|
|
||||||
caseSensitive: false
|
|
||||||
};
|
|
||||||
const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions);
|
|
||||||
const hello1 = term.searchHelper.findInLine('Hello$', 0, searchOptions);
|
|
||||||
const hello2 = term.searchHelper.findInLine('Hello', 1, searchOptions);
|
|
||||||
const hello3 = term.searchHelper.findInLine('Hello$', 1, searchOptions);
|
|
||||||
expect(hello0).eql({col: 0, row: 0, term: 'hello'});
|
|
||||||
expect(hello1).eql({col: 12, row: 0, term: 'hello'});
|
|
||||||
expect(hello2).eql(undefined);
|
|
||||||
expect(hello3).eql(undefined);
|
|
||||||
});
|
|
||||||
it('should respect all search options', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('Hello World Hello\r\nHelloWorldHello');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: true,
|
|
||||||
wholeWord: true,
|
|
||||||
caseSensitive: true
|
|
||||||
};
|
|
||||||
const hello0 = term.searchHelper.findInLine('Hello', 0, searchOptions);
|
|
||||||
const hello1 = term.searchHelper.findInLine('Hello$', 0, searchOptions);
|
|
||||||
const hello2 = term.searchHelper.findInLine('hello', 0, searchOptions);
|
|
||||||
const hello3 = term.searchHelper.findInLine('hello$', 0, searchOptions);
|
|
||||||
const hello4 = term.searchHelper.findInLine('hello', 1, searchOptions);
|
|
||||||
const hello5 = term.searchHelper.findInLine('hello$', 1, searchOptions);
|
|
||||||
expect(hello0).eql({col: 0, row: 0, term: 'Hello'});
|
|
||||||
expect(hello1).eql({col: 12, row: 0, term: 'Hello'});
|
|
||||||
expect(hello2).eql(undefined);
|
|
||||||
expect(hello3).eql(undefined);
|
|
||||||
expect(hello4).eql(undefined);
|
|
||||||
expect(hello5).eql(undefined);
|
|
||||||
});
|
|
||||||
it('should find multiple matches in line', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('helloooo helloooo\r\naaaAAaaAAA');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: false,
|
|
||||||
wholeWord: false,
|
|
||||||
caseSensitive: false
|
|
||||||
};
|
|
||||||
const find0 = term.searchHelper.findFromIndex('hello', 0, 0, searchOptions);
|
|
||||||
const find1 = term.searchHelper.findFromIndex('hello', 0, find0.col + find0.term.length, searchOptions);
|
|
||||||
const find2 = term.searchHelper.findFromIndex('aaaa', 1, 0, searchOptions);
|
|
||||||
const find3 = term.searchHelper.findFromIndex('aaaa', 1, find2.col + find2.term.length, searchOptions);
|
|
||||||
const find4 = term.searchHelper.findFromIndex('aaaa', 1, find3.col + find3.term.length, searchOptions);
|
|
||||||
expect(find0).eql({col: 0, row: 0, term: 'hello'});
|
|
||||||
expect(find1).eql({col: 9, row: 0, term: 'hello'});
|
|
||||||
expect(find2).eql({col: 0, row: 1, term: 'aaaa'});
|
|
||||||
expect(find3).eql({col: 4, row: 1, term: 'aaaa'});
|
|
||||||
expect(find4).eql(undefined);
|
|
||||||
});
|
|
||||||
it('should find multiple matches in line - reverse search', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('it is what it is');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: false,
|
|
||||||
wholeWord: false,
|
|
||||||
caseSensitive: false
|
|
||||||
};
|
|
||||||
const isReverseSearch = true;
|
|
||||||
const find0 = term.searchHelper.findFromIndex('is', 0, 16, searchOptions, isReverseSearch);
|
|
||||||
const find1 = term.searchHelper.findFromIndex('is', 0, find0.col, searchOptions, isReverseSearch);
|
|
||||||
const find2 = term.searchHelper.findFromIndex('it', 0, 16, searchOptions, isReverseSearch);
|
|
||||||
const find3 = term.searchHelper.findFromIndex('it', 0, find2.col, searchOptions, isReverseSearch);
|
|
||||||
expect(find0).eql({col: 14, row: 0, term: 'is'});
|
|
||||||
expect(find1).eql({col: 3, row: 0, term: 'is'});
|
|
||||||
expect(find2).eql({col: 11, row: 0, term: 'it'});
|
|
||||||
expect(find3).eql({col: 0, row: 0, term: 'it'});
|
|
||||||
});
|
|
||||||
it('should find multiple matches in line - reverse search with regex', function(): void {
|
|
||||||
search.apply(<any>MockTerminal);
|
|
||||||
const term = new MockTerminal({cols: 20, rows: 5});
|
|
||||||
term.core.write('zzzABCzzzzABCABC');
|
|
||||||
term.pushWriteData();
|
|
||||||
const searchOptions = {
|
|
||||||
regex: true,
|
|
||||||
wholeWord: false,
|
|
||||||
caseSensitive: true
|
|
||||||
};
|
|
||||||
const isReverseSearch = true;
|
|
||||||
const find0 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, 16, searchOptions, isReverseSearch);
|
|
||||||
const find1 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find0.col, searchOptions, isReverseSearch);
|
|
||||||
const find2 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find1.col, searchOptions, isReverseSearch);
|
|
||||||
const find3 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find2.col, searchOptions, isReverseSearch);
|
|
||||||
expect(find0).eql({col: 13, row: 0, term: 'ABC'});
|
|
||||||
expect(find1).eql({col: 10, row: 0, term: 'ABC'});
|
|
||||||
expect(find2).eql({col: 3, row: 0, term: 'ABC'});
|
|
||||||
expect(find3).eql(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { SearchHelper } from './SearchHelper';
|
|
||||||
import { Terminal } from 'xterm';
|
|
||||||
import { ISearchAddonTerminal, ISearchOptions } from './Interfaces';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the next instance of the term, then scroll to and select it. If it
|
|
||||||
* doesn't exist, do nothing.
|
|
||||||
* @param term The search term.
|
|
||||||
* @param searchOptions Search options
|
|
||||||
* @return Whether a result was found.
|
|
||||||
*/
|
|
||||||
export function findNext(terminal: Terminal, term: string, searchOptions: ISearchOptions = {}): boolean {
|
|
||||||
const addonTerminal = <ISearchAddonTerminal>terminal;
|
|
||||||
if (!addonTerminal.__searchHelper) {
|
|
||||||
addonTerminal.__searchHelper = new SearchHelper(addonTerminal);
|
|
||||||
}
|
|
||||||
return addonTerminal.__searchHelper.findNext(term, searchOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the previous instance of the term, then scroll to and select it. If it
|
|
||||||
* doesn't exist, do nothing.
|
|
||||||
* @param term The search term.
|
|
||||||
* @param searchOptions Search options
|
|
||||||
* @return Whether a result was found.
|
|
||||||
*/
|
|
||||||
export function findPrevious(terminal: Terminal, term: string, searchOptions: ISearchOptions): boolean {
|
|
||||||
const addonTerminal = <ISearchAddonTerminal>terminal;
|
|
||||||
if (!addonTerminal.__searchHelper) {
|
|
||||||
addonTerminal.__searchHelper = new SearchHelper(addonTerminal);
|
|
||||||
}
|
|
||||||
return addonTerminal.__searchHelper.findPrevious(term, searchOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function apply(terminalConstructor: typeof Terminal): void {
|
|
||||||
(<any>terminalConstructor.prototype).findNext = function(term: string, searchOptions: ISearchOptions): boolean {
|
|
||||||
return findNext(this, term, searchOptions);
|
|
||||||
};
|
|
||||||
|
|
||||||
(<any>terminalConstructor.prototype).findPrevious = function(term: string, searchOptions: ISearchOptions): boolean {
|
|
||||||
return findPrevious(this, term, searchOptions);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"lib": [
|
|
||||||
"dom",
|
|
||||||
"es5"
|
|
||||||
],
|
|
||||||
"rootDir": ".",
|
|
||||||
"outDir": "../../../lib/addons/search/",
|
|
||||||
"sourceMap": true,
|
|
||||||
"removeComments": true,
|
|
||||||
"declaration": true,
|
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"**/*.ts",
|
|
||||||
"../../../typings/xterm.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*
|
|
||||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Terminal, IDisposable } from 'xterm';
|
|
||||||
|
|
||||||
export interface ITerminadoAddonTerminal extends Terminal {
|
|
||||||
_core: {
|
|
||||||
register<T extends IDisposable>(d: T): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
__socket?: WebSocket;
|
|
||||||
__attachSocketBuffer?: string;
|
|
||||||
__dataListener?: IDisposable;
|
|
||||||
|
|
||||||
__getMessage?(ev: MessageEvent): void;
|
|
||||||
__flushBuffer?(): void;
|
|
||||||
__pushToBuffer?(data: string): void;
|
|
||||||
__sendData?(data: string): void;
|
|
||||||
__setSize?(size: {rows: number, cols: number}): void;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "xterm.terminado",
|
|
||||||
"main": "terminado.js",
|
|
||||||
"private": true
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import * as terminado from './terminado';
|
|
||||||
|
|
||||||
class MockTerminal {}
|
|
||||||
|
|
||||||
describe('terminado addon', () => {
|
|
||||||
describe('apply', () => {
|
|
||||||
it('should do register the `terminadoAttach` and `terminadoDetach` methods', () => {
|
|
||||||
terminado.apply(<any>MockTerminal);
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.terminadoAttach, 'function');
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.terminadoDetach, 'function');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*
|
|
||||||
* This module provides methods for attaching a terminal to a terminado
|
|
||||||
* WebSocket stream.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Terminal } from 'xterm';
|
|
||||||
import { ITerminadoAddonTerminal } from './Interfaces';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches the given terminal to the given socket.
|
|
||||||
*
|
|
||||||
* @param term The terminal to be attached to the given socket.
|
|
||||||
* @param socket The socket to attach the current terminal.
|
|
||||||
* @param bidirectional Whether the terminal should send data to the socket as well.
|
|
||||||
* @param buffered Whether the rendering of incoming data should happen instantly or at a maximum
|
|
||||||
* frequency of 1 rendering per 10ms.
|
|
||||||
*/
|
|
||||||
export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
|
||||||
const addonTerminal = <ITerminadoAddonTerminal>term;
|
|
||||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
|
||||||
addonTerminal.__socket = socket;
|
|
||||||
|
|
||||||
addonTerminal.__flushBuffer = () => {
|
|
||||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
|
||||||
addonTerminal.__attachSocketBuffer = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
addonTerminal.__pushToBuffer = (data: string) => {
|
|
||||||
if (addonTerminal.__attachSocketBuffer) {
|
|
||||||
addonTerminal.__attachSocketBuffer += data;
|
|
||||||
} else {
|
|
||||||
addonTerminal.__attachSocketBuffer = data;
|
|
||||||
setTimeout(addonTerminal.__flushBuffer, 10);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
addonTerminal.__getMessage = (ev: MessageEvent) => {
|
|
||||||
const data = JSON.parse(ev.data);
|
|
||||||
if (data[0] === 'stdout') {
|
|
||||||
if (buffered) {
|
|
||||||
addonTerminal.__pushToBuffer(data[1]);
|
|
||||||
} else {
|
|
||||||
addonTerminal.write(data[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
addonTerminal.__sendData = (data: string) => {
|
|
||||||
socket.send(JSON.stringify(['stdin', data]));
|
|
||||||
};
|
|
||||||
|
|
||||||
addonTerminal.__setSize = (size: {rows: number, cols: number}) => {
|
|
||||||
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.addEventListener('message', addonTerminal.__getMessage);
|
|
||||||
|
|
||||||
if (bidirectional) {
|
|
||||||
addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData));
|
|
||||||
}
|
|
||||||
addonTerminal._core.register(addonTerminal.onResize(addonTerminal.__setSize));
|
|
||||||
|
|
||||||
socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));
|
|
||||||
socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detaches the given terminal from the given socket
|
|
||||||
*
|
|
||||||
* @param term The terminal to be detached from the given socket.
|
|
||||||
* @param socket The socket from which to detach the current terminal.
|
|
||||||
*/
|
|
||||||
export function terminadoDetach(term: Terminal, socket: WebSocket): void {
|
|
||||||
const addonTerminal = <ITerminadoAddonTerminal>term;
|
|
||||||
addonTerminal.__dataListener.dispose();
|
|
||||||
addonTerminal.__dataListener = undefined;
|
|
||||||
|
|
||||||
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
|
|
||||||
|
|
||||||
if (socket) {
|
|
||||||
socket.removeEventListener('message', addonTerminal.__getMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete addonTerminal.__socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function apply(terminalConstructor: typeof Terminal): void {
|
|
||||||
/**
|
|
||||||
* Attaches the current terminal to the given socket
|
|
||||||
*
|
|
||||||
* @param socket - The socket to attach the current terminal.
|
|
||||||
* @param bidirectional - Whether the terminal should send data to the socket as well.
|
|
||||||
* @param buffered - Whether the rendering of incoming data should happen instantly or at a
|
|
||||||
* maximum frequency of 1 rendering per 10ms.
|
|
||||||
*/
|
|
||||||
(<any>terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
|
|
||||||
return terminadoAttach(this, socket, bidirectional, buffered);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detaches the current terminal from the given socket.
|
|
||||||
*
|
|
||||||
* @param socket The socket from which to detach the current terminal.
|
|
||||||
*/
|
|
||||||
(<any>terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {
|
|
||||||
return terminadoDetach(this, socket);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"lib": [
|
|
||||||
"es5"
|
|
||||||
],
|
|
||||||
"rootDir": ".",
|
|
||||||
"outDir": "../../../lib/addons/terminado/",
|
|
||||||
"sourceMap": true,
|
|
||||||
"removeComments": true,
|
|
||||||
"declaration": true,
|
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"**/*.ts",
|
|
||||||
"../../../typings/xterm.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "xterm.weblinks",
|
|
||||||
"main": "weblinks.js",
|
|
||||||
"private": true
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"lib": [
|
|
||||||
"dom",
|
|
||||||
"es5",
|
|
||||||
],
|
|
||||||
"rootDir": ".",
|
|
||||||
"outDir": "../../../lib/addons/webLinks/",
|
|
||||||
"sourceMap": true,
|
|
||||||
"removeComments": true,
|
|
||||||
"declaration": true,
|
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"**/*.ts",
|
|
||||||
"../../../typings/xterm.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import * as webLinks from './webLinks';
|
|
||||||
|
|
||||||
class MockTerminal {
|
|
||||||
public regex: RegExp;
|
|
||||||
public handler: (event: MouseEvent, uri: string) => void;
|
|
||||||
public options?: any;
|
|
||||||
|
|
||||||
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: any): number {
|
|
||||||
this.regex = regex;
|
|
||||||
this.handler = handler;
|
|
||||||
this.options = options;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('webLinks addon', () => {
|
|
||||||
describe('apply', () => {
|
|
||||||
it('should do register the `webLinksInit` method', () => {
|
|
||||||
webLinks.apply(<any>MockTerminal);
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.webLinksInit, 'function');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should allow simple URI path', () => {
|
|
||||||
it('foo.com', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://foo.com ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('bar.io', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://bar.io ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://bar.io');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should allow ~ character in URI path', () => {
|
|
||||||
it('foo.com', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://foo.com/a~b#c~d?e~f ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/a~b#c~d?e~f');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('bar.io', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://bar.io/a~b#c~d?e~f ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://bar.io/a~b#c~d?e~f');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should allow : character in URI path', () => {
|
|
||||||
it('foo.com', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://foo.com/colon:test ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/colon:test');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('bar.io', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://bar.io/colon:test ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://bar.io/colon:test');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should not allow : character at the end of a URI path', () => {
|
|
||||||
it('foo.com', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://foo.com/colon:test: ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/colon:test');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('bar.io', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = ' http://bar.io/colon:test: ';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://bar.io/colon:test');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should not allow " character at the end of a URI enclosed with ""', () => {
|
|
||||||
it('foo.com', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = '"http://foo.com/"';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('bar.io', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = '"http://bar.io/"';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://bar.io/');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should not allow \' character at the end of a URI enclosed with \'\'', () => {
|
|
||||||
it('foo.com', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = '\'http://foo.com/\'';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('bar.io', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = '\'http://bar.io/\'';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://bar.io/');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should allow + character in URI path', () => {
|
|
||||||
it('foo.com', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = 'http://foo.com/subpath/+/id';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://foo.com/subpath/+/id');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('bar.io', () => {
|
|
||||||
const term = new MockTerminal();
|
|
||||||
webLinks.webLinksInit(<any>term);
|
|
||||||
|
|
||||||
const row = 'http://bar.io/subpath/+/id';
|
|
||||||
|
|
||||||
const match = row.match(term.regex);
|
|
||||||
const uri = match[term.options.matchIndex];
|
|
||||||
|
|
||||||
assert.equal(uri, 'http://bar.io/subpath/+/id');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "xterm.zmodem",
|
|
||||||
"main": "zmodem.js",
|
|
||||||
"private": true
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"lib": [
|
|
||||||
"es5"
|
|
||||||
],
|
|
||||||
"rootDir": ".",
|
|
||||||
"outDir": "../../../lib/addons/zmodem/",
|
|
||||||
"sourceMap": true,
|
|
||||||
"removeComments": true,
|
|
||||||
"declaration": true,
|
|
||||||
"types": [
|
|
||||||
"../../node_modules/@types/mocha"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"**/*.ts",
|
|
||||||
"../../../typings/xterm.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import * as zmodem from './zmodem';
|
|
||||||
|
|
||||||
class MockTerminal {}
|
|
||||||
|
|
||||||
describe('zmodem addon', () => {
|
|
||||||
describe('apply', () => {
|
|
||||||
it('should do register the `zmodemAttach` method and `zmodemBrowser` attribute', () => {
|
|
||||||
zmodem.apply(<any>MockTerminal);
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.zmodemAttach, 'function');
|
|
||||||
assert.equal(typeof (<any>MockTerminal).prototype.zmodemBrowser, 'object');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Terminal } from 'xterm';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Allow xterm.js to handle ZMODEM uploads and downloads.
|
|
||||||
*
|
|
||||||
* This addon is a wrapper around zmodem.js. It adds the following to the
|
|
||||||
* Terminal class:
|
|
||||||
*
|
|
||||||
* - function `zmodemAttach(<WebSocket>, <Object>)` - creates a Zmodem.Sentry
|
|
||||||
* on the passed WebSocket object. The Object passed is optional and
|
|
||||||
* can contain:
|
|
||||||
* - noTerminalWriteOutsideSession: Suppress writes from the Sentry
|
|
||||||
* object to the Terminal while there is no active Session. This
|
|
||||||
* is necessary for compatibility with, for example, the
|
|
||||||
* `attach.js` addon.
|
|
||||||
*
|
|
||||||
* - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback.
|
|
||||||
* Passes the zmodem.js Detection object.
|
|
||||||
*
|
|
||||||
* - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback.
|
|
||||||
*
|
|
||||||
* You’ll need to provide logic to handle uploads and downloads.
|
|
||||||
* See zmodem.js’s documentation for more details.
|
|
||||||
*
|
|
||||||
* **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have
|
|
||||||
* used the `attach` or `terminado` addons, you’ll need to suspend their
|
|
||||||
* operation for the duration of the ZMODEM session. (The demo does this
|
|
||||||
* via `detach()` and a re-`attach()`.)
|
|
||||||
*/
|
|
||||||
|
|
||||||
let zmodem: any;
|
|
||||||
|
|
||||||
export interface IZmodemOptions {
|
|
||||||
noTerminalWriteOutsideSession?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {
|
|
||||||
const term = this;
|
|
||||||
const senderFunc = (octets: ArrayLike<number>) => ws.send(new Uint8Array(octets));
|
|
||||||
|
|
||||||
let zsentry: any;
|
|
||||||
|
|
||||||
function shouldWrite(): boolean {
|
|
||||||
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
zsentry = new zmodem.Sentry({
|
|
||||||
to_terminal: (octets: ArrayLike<number>) => {
|
|
||||||
if (shouldWrite()) {
|
|
||||||
term.write(
|
|
||||||
String.fromCharCode.apply(String, octets)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sender: senderFunc,
|
|
||||||
on_retract: () => (<any>term).emit('zmodemRetract'),
|
|
||||||
on_detect: (detection: any) => (<any>term).emit('zmodemDetect', detection)
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleWSMessage(evt: MessageEvent): void {
|
|
||||||
|
|
||||||
// In testing with xterm.js’s demo the first message was
|
|
||||||
// always text even if the rest were binary. While that
|
|
||||||
// may be specific to xterm.js’s demo, ultimately we
|
|
||||||
// should reject anything that isn’t binary.
|
|
||||||
if (typeof evt.data === 'string') {
|
|
||||||
if (shouldWrite()) {
|
|
||||||
term.write(evt.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
zsentry.consume(evt.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.binaryType = 'arraybuffer';
|
|
||||||
ws.addEventListener('message', handleWSMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function apply(terminalConstructor: typeof Terminal): void {
|
|
||||||
zmodem = (typeof window === 'object') ? (<any>window).Zmodem : {Browser: null}; // Nullify browser for tests
|
|
||||||
|
|
||||||
(<any>terminalConstructor.prototype).zmodemAttach = zmodemAttach;
|
|
||||||
(<any>terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
import { Terminal } from './Terminal';
|
|
||||||
import * as attach from '../addons/attach/attach';
|
|
||||||
|
|
||||||
describe('Terminal', () => {
|
|
||||||
it('should apply addons with Terminal.applyAddon', () => {
|
|
||||||
Terminal.applyAddon(attach);
|
|
||||||
// Test that addon was applied successfully, adding attach to Terminal's
|
|
||||||
// prototype.
|
|
||||||
assert.equal(typeof (<any>Terminal).prototype.attach, 'function');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"files": [],
|
|
||||||
"include": [],
|
|
||||||
"references": [
|
|
||||||
{ "path": "." },
|
|
||||||
{ "path": "./addons/attach" },
|
|
||||||
{ "path": "./addons/fit" },
|
|
||||||
{ "path": "./addons/fullscreen" },
|
|
||||||
{ "path": "./addons/search" },
|
|
||||||
{ "path": "./addons/terminado" },
|
|
||||||
{ "path": "./addons/webLinks" },
|
|
||||||
{ "path": "./addons/zmodem" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./src" },
|
||||||
|
{ "path": "./addons/@xtermjs/addon-attach/src" },
|
||||||
|
{ "path": "./addons/@xtermjs/addon-fit/src" },
|
||||||
|
{ "path": "./addons/@xtermjs/addon-search/src" },
|
||||||
|
{ "path": "./addons/@xtermjs/addon-web-links/src" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -108,6 +108,14 @@
|
|||||||
"@types/uglify-js" "*"
|
"@types/uglify-js" "*"
|
||||||
source-map "^0.6.0"
|
source-map "^0.6.0"
|
||||||
|
|
||||||
|
"@types/ws@^6.0.1":
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.1.tgz#ca7a3f3756aa12f62a0a62145ed14c6db25d5a28"
|
||||||
|
integrity sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==
|
||||||
|
dependencies:
|
||||||
|
"@types/events" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@webassemblyjs/ast@1.5.13":
|
"@webassemblyjs/ast@1.5.13":
|
||||||
version "1.5.13"
|
version "1.5.13"
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25"
|
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25"
|
||||||
@@ -472,7 +480,7 @@ async-each@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
|
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
|
||||||
integrity sha1-GdOGodntxufByF04iu28xW0zYC0=
|
integrity sha1-GdOGodntxufByF04iu28xW0zYC0=
|
||||||
|
|
||||||
async-limiter@~1.0.0:
|
async-limiter@^1.0.0, async-limiter@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||||
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
|
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
|
||||||
@@ -4536,6 +4544,13 @@ ws@^6.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
async-limiter "~1.0.0"
|
async-limiter "~1.0.0"
|
||||||
|
|
||||||
|
ws@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.0.0.tgz#79351cbc3f784b3c20d0821baf4b4ff809ffbf51"
|
||||||
|
integrity sha512-cknCal4k0EAOrh1SHHPPWWh4qm93g1IuGGGwBjWkXmCG7LsDtL8w9w+YVfaF+KSVwiHQKDIMsSLBVftKf9d1pg==
|
||||||
|
dependencies:
|
||||||
|
async-limiter "^1.0.0"
|
||||||
|
|
||||||
xml-name-validator@^3.0.0:
|
xml-name-validator@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||||
@@ -4551,21 +4566,6 @@ xtend@^4.0.0, xtend@~4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||||
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
||||||
|
|
||||||
xterm-addon-attach@0.1.0-beta8:
|
|
||||||
version "0.1.0-beta8"
|
|
||||||
resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta8.tgz#e469ed9d6ab7e535d0a9ffae23ef4f2efe58163b"
|
|
||||||
integrity sha512-HtQuwqnvcR+SwI9/JbBMd//Il+oEeo3rWrIucLLKHT8sB+OAOkdhmo5KIM/hhnovjI040WJ+tTHkDgPFwIJtmw==
|
|
||||||
|
|
||||||
xterm-addon-search@0.1.0-beta4:
|
|
||||||
version "0.1.0-beta4"
|
|
||||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.1.0-beta4.tgz#c73fe058c87f07eaae31baaa92976e927438a396"
|
|
||||||
integrity sha512-tJgZ1VTRd/DOFUhSFZzybRF8SR1LCEXRYkw/mHzGV5Ba3zhqVdSkN/0J9sjOpX6u21buee2OmTiCMZxq80zfJg==
|
|
||||||
|
|
||||||
xterm-addon-web-links@0.1.0-beta6:
|
|
||||||
version "0.1.0-beta6"
|
|
||||||
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta6.tgz#9b4e862be8928ef455a667745bea479665db6c6b"
|
|
||||||
integrity sha512-tkVU5wCfBFjXwfOvcbMHoLoMDANztkwSREiKyu2R059kEF+sP67Z33HzxVCXUWFuCmutcx40xR2O0BK68gXZlg==
|
|
||||||
|
|
||||||
"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0:
|
"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
||||||
|
|||||||
Reference in New Issue
Block a user