mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Remove all old addons except fit (no replacement yet)
This commit is contained in:
@@ -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,19 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es6",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../../../lib/addons/attach/",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -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,344 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces';
|
||||
import { IDisposable } from 'xterm';
|
||||
|
||||
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
|
||||
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
|
||||
|
||||
/**
|
||||
* A class that knows how to search the terminal and how to display the results.
|
||||
*/
|
||||
export class SearchHelper implements ISearchHelper {
|
||||
/**
|
||||
* translateBufferLineToStringWithWrap is a fairly expensive call.
|
||||
* We memoize the calls into an array that has a time based ttl.
|
||||
* _linesCache is also invalidated when the terminal cursor moves.
|
||||
*/
|
||||
private _linesCache: string[] = null;
|
||||
private _linesCacheTimeoutId = 0;
|
||||
private _cursorMoveListener: IDisposable | undefined;
|
||||
private _resizeListener: IDisposable | undefined;
|
||||
|
||||
constructor(private _terminal: ISearchAddonTerminal) {
|
||||
this._destroyLinesCache = this._destroyLinesCache.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public findNext(term: string, searchOptions?: ISearchOptions): boolean {
|
||||
const {incremental} = searchOptions;
|
||||
let result: ISearchResult;
|
||||
|
||||
if (!term || term.length === 0) {
|
||||
this._terminal.clearSelection();
|
||||
return false;
|
||||
}
|
||||
|
||||
let startCol: number = 0;
|
||||
let startRow = this._terminal.buffer.viewportY;
|
||||
|
||||
if (this._terminal.hasSelection()) {
|
||||
// Start from the selection end if there is a selection
|
||||
// For incremental search, use existing row
|
||||
const currentSelection = this._terminal.getSelectionPosition();
|
||||
startRow = incremental ? currentSelection.startRow : currentSelection.endRow;
|
||||
startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn;
|
||||
}
|
||||
|
||||
this._initLinesCache();
|
||||
|
||||
// A row that has isWrapped = false
|
||||
let findingRow = startRow;
|
||||
// index of beginning column that _findInLine need to scan.
|
||||
let cumulativeCols = startCol;
|
||||
// If startRow is wrapped row, scan for unwrapped row above.
|
||||
// So we can start matching on wrapped line from long unwrapped line.
|
||||
while (this._terminal.buffer.getLine(findingRow).isWrapped) {
|
||||
findingRow--;
|
||||
cumulativeCols += this._terminal.cols;
|
||||
}
|
||||
|
||||
// Search startRow
|
||||
result = this._findInLine(term, findingRow, cumulativeCols, searchOptions);
|
||||
|
||||
// Search from startRow + 1 to end
|
||||
if (!result) {
|
||||
|
||||
for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {
|
||||
|
||||
// If the current line is wrapped line, increase index of column to ignore the previous scan
|
||||
// Otherwise, reset beginning column index to zero with set new unwrapped line index
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search from the top to the startRow (search the whole startRow again in
|
||||
// case startCol > 0)
|
||||
if (!result) {
|
||||
for (let y = 0; y < findingRow; y++) {
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set selection and scroll if a result was found
|
||||
return this._selectResult(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean {
|
||||
let result: ISearchResult;
|
||||
|
||||
if (!term || term.length === 0) {
|
||||
this._terminal.clearSelection();
|
||||
return false;
|
||||
}
|
||||
|
||||
const isReverseSearch = true;
|
||||
let startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1;
|
||||
let startCol = this._terminal.cols;
|
||||
|
||||
if (this._terminal.hasSelection()) {
|
||||
// Start from the selection start if there is a selection
|
||||
const currentSelection = this._terminal.getSelectionPosition();
|
||||
startRow = currentSelection.startRow;
|
||||
startCol = currentSelection.startColumn;
|
||||
}
|
||||
|
||||
this._initLinesCache();
|
||||
|
||||
// Search startRow
|
||||
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
|
||||
|
||||
// Search from startRow - 1 to top
|
||||
if (!result) {
|
||||
// If the line is wrapped line, increase number of columns that is needed to be scanned
|
||||
// Se we can scan on wrapped line from unwrapped line
|
||||
let cumulativeCols = this._terminal.cols;
|
||||
if (this._terminal.buffer.getLine(startRow).isWrapped) {
|
||||
cumulativeCols += startCol;
|
||||
}
|
||||
for (let y = startRow - 1; y >= 0; y--) {
|
||||
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
// If the current line is wrapped line, increase scanning range,
|
||||
// preparing for scanning on unwrapped line
|
||||
if (this._terminal.buffer.getLine(y).isWrapped) {
|
||||
cumulativeCols += this._terminal.cols;
|
||||
} else {
|
||||
cumulativeCols = this._terminal.cols;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search from the bottom to startRow (search the whole startRow again in
|
||||
// case startCol > 0)
|
||||
if (!result) {
|
||||
const searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1;
|
||||
let cumulativeCols = this._terminal.cols;
|
||||
for (let y = searchFrom; y >= startRow; y--) {
|
||||
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
if (this._terminal.buffer.getLine(y).isWrapped) {
|
||||
cumulativeCols += this._terminal.cols;
|
||||
} else {
|
||||
cumulativeCols = this._terminal.cols;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set selection and scroll if a result was found
|
||||
return this._selectResult(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a line cache with a ttl
|
||||
*/
|
||||
private _initLinesCache(): void {
|
||||
if (!this._linesCache) {
|
||||
this._linesCache = new Array(this._terminal.buffer.length);
|
||||
this._cursorMoveListener = this._terminal.onCursorMove(() => this._destroyLinesCache());
|
||||
this._resizeListener = this._terminal.onResize(() => this._destroyLinesCache());
|
||||
}
|
||||
|
||||
window.clearTimeout(this._linesCacheTimeoutId);
|
||||
this._linesCacheTimeoutId = window.setTimeout(() => this._destroyLinesCache(), LINES_CACHE_TIME_TO_LIVE);
|
||||
}
|
||||
|
||||
private _destroyLinesCache(): void {
|
||||
this._linesCache = null;
|
||||
if (this._cursorMoveListener) {
|
||||
this._cursorMoveListener.dispose();
|
||||
this._cursorMoveListener = undefined;
|
||||
}
|
||||
if (this._resizeListener) {
|
||||
this._resizeListener.dispose();
|
||||
this._resizeListener = undefined;
|
||||
}
|
||||
if (this._linesCacheTimeoutId) {
|
||||
window.clearTimeout(this._linesCacheTimeoutId);
|
||||
this._linesCacheTimeoutId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A found substring is a whole word if it doesn't have an alphanumeric character directly adjacent to it.
|
||||
* @param searchIndex starting indext of the potential whole word substring
|
||||
* @param line entire string in which the potential whole word was found
|
||||
* @param term the substring that starts at searchIndex
|
||||
*/
|
||||
private _isWholeWord(searchIndex: number, line: string, term: string): boolean {
|
||||
return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) &&
|
||||
(((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches a line for a search term. Takes the provided terminal line and searches the text line, which may contain
|
||||
* subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that
|
||||
* started on an earlier line then it is skipped since it will be properly searched when the terminal line that the
|
||||
* text starts on is searched.
|
||||
* @param term The search term.
|
||||
* @param row The line to start the search from.
|
||||
* @param col The column to start the search from.
|
||||
* @param searchOptions Search options.
|
||||
* @return The search result if it was found.
|
||||
*/
|
||||
protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult {
|
||||
|
||||
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
|
||||
if (this._terminal.buffer.getLine(row).isWrapped) {
|
||||
return;
|
||||
}
|
||||
let stringLine = this._linesCache ? this._linesCache[row] : void 0;
|
||||
if (stringLine === void 0) {
|
||||
stringLine = this.translateBufferLineToStringWithWrap(row, true);
|
||||
if (this._linesCache) {
|
||||
this._linesCache[row] = stringLine;
|
||||
}
|
||||
}
|
||||
|
||||
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
|
||||
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
||||
|
||||
let resultIndex = -1;
|
||||
if (searchOptions.regex) {
|
||||
const searchRegex = RegExp(searchTerm, 'g');
|
||||
let foundTerm: RegExpExecArray;
|
||||
if (isReverseSearch) {
|
||||
// This loop will get the resultIndex of the _last_ regex match in the range 0..col
|
||||
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {
|
||||
resultIndex = searchRegex.lastIndex - foundTerm[0].length;
|
||||
term = foundTerm[0];
|
||||
searchRegex.lastIndex -= (term.length - 1);
|
||||
}
|
||||
} else {
|
||||
foundTerm = searchRegex.exec(searchStringLine.slice(col));
|
||||
if (foundTerm && foundTerm[0].length > 0) {
|
||||
resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length);
|
||||
term = foundTerm[0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isReverseSearch) {
|
||||
if (col - searchTerm.length >= 0) {
|
||||
resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length);
|
||||
}
|
||||
} else {
|
||||
resultIndex = searchStringLine.indexOf(searchTerm, col);
|
||||
}
|
||||
}
|
||||
|
||||
if (resultIndex >= 0) {
|
||||
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
|
||||
if (resultIndex >= this._terminal.cols) {
|
||||
row += Math.floor(resultIndex / this._terminal.cols);
|
||||
resultIndex = resultIndex % this._terminal.cols;
|
||||
}
|
||||
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const line = this._terminal.buffer.getLine(row);
|
||||
|
||||
for (let i = 0; i < resultIndex; i++) {
|
||||
const cell = line.getCell(i);
|
||||
// Adjust the searchIndex to normalize emoji into single chars
|
||||
const char = cell.char;
|
||||
if (char.length > 1) {
|
||||
resultIndex -= char.length - 1;
|
||||
}
|
||||
// Adjust the searchIndex for empty characters following wide unicode
|
||||
// chars (eg. CJK)
|
||||
const charWidth = cell.width;
|
||||
if (charWidth === 0) {
|
||||
resultIndex++;
|
||||
}
|
||||
}
|
||||
return {
|
||||
term,
|
||||
col: resultIndex,
|
||||
row
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Translates a buffer line to a string, including subsequent lines if they are wraps.
|
||||
* Wide characters will count as two columns in the resulting string. This
|
||||
* function is useful for getting the actual text underneath the raw selection
|
||||
* position.
|
||||
* @param line The line being translated.
|
||||
* @param trimRight Whether to trim whitespace to the right.
|
||||
*/
|
||||
public translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): string {
|
||||
let lineString = '';
|
||||
let lineWrapsToNext: boolean;
|
||||
|
||||
do {
|
||||
const nextLine = this._terminal.buffer.getLine(lineIndex + 1);
|
||||
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
||||
lineString += this._terminal.buffer.getLine(lineIndex).translateToString(!lineWrapsToNext && trimRight).substring(0, this._terminal.cols);
|
||||
lineIndex++;
|
||||
} while (lineWrapsToNext);
|
||||
|
||||
return lineString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects and scrolls to a result.
|
||||
* @param result The result to select.
|
||||
* @return Whethera result was selected.
|
||||
*/
|
||||
private _selectResult(result: ISearchResult): boolean {
|
||||
if (!result) {
|
||||
this._terminal.clearSelection();
|
||||
return false;
|
||||
}
|
||||
this._terminal.select(result.col, result.row, result.term.length);
|
||||
this._terminal.scrollLines(result.row - this._terminal.buffer.viewportY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user