Merge branch 'master' into add-tests-for-events

This commit is contained in:
Daniel Imms
2018-09-08 13:44:57 -07:00
committed by GitHub
41 changed files with 1069 additions and 423 deletions
+86 -62
View File
@@ -1,4 +1,13 @@
import * as Terminal from '../build/xterm';
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*
* This file is the entry point for browserify.
*/
/// <reference path="../typings/xterm.d.ts"/>
import { Terminal } from '../lib/public/Terminal';
import * as attach from '../build/addons/attach/attach';
import * as fit from '../build/addons/fit/fit';
import * as fullscreen from '../build/addons/fullscreen/fullscreen';
@@ -6,6 +15,14 @@ import * as search from '../build/addons/search/search';
import * as webLinks from '../build/addons/webLinks/webLinks';
import * as winptyCompat from '../build/addons/winptyCompat/winptyCompat';
// Pulling in the module's types relies on the <reference> above, it's looks a
// little weird here as we're importing "this" module
import { Terminal as TerminalType } from 'xterm';
export interface IWindowWithTerminal extends Window {
term: TerminalType;
}
declare let window: IWindowWithTerminal;
Terminal.applyAddon(attach);
Terminal.applyAddon(fit);
@@ -15,20 +32,20 @@ Terminal.applyAddon(webLinks);
Terminal.applyAddon(winptyCompat);
var term,
protocol,
socketURL,
socket,
pid;
let term;
let protocol;
let socketURL;
let socket;
let pid;
var terminalContainer = document.getElementById('terminal-container'),
actionElements = {
findNext: document.querySelector('#find-next'),
findPrevious: document.querySelector('#find-previous')
},
paddingElement = document.getElementById('padding');
const terminalContainer = document.getElementById('terminal-container');
const actionElements = {
findNext: <HTMLInputElement>document.querySelector('#find-next'),
findPrevious: <HTMLInputElement>document.querySelector('#find-previous')
};
const paddingElement = <HTMLInputElement>document.getElementById('padding');
function setPadding() {
function setPadding(): void {
term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';
term.fit();
}
@@ -52,20 +69,20 @@ const disposeRecreateButtonHandler = () => {
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
function createTerminal() {
function createTerminal(): void {
// Clean terminal
while (terminalContainer.children.length) {
terminalContainer.removeChild(terminalContainer.children[0]);
}
term = new Terminal({});
window.term = term; // Expose `term` to window for debugging purposes
term.on('resize', function (size) {
term.on('resize', (size: { cols: number, rows: number }) => {
if (!pid) {
return;
}
var cols = size.cols,
rows = size.rows,
url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
const cols = size.cols;
const rows = size.rows;
const url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
fetch(url, {method: 'POST'});
});
@@ -80,32 +97,42 @@ function createTerminal() {
addDomListener(paddingElement, 'change', setPadding);
addDomListener(actionElements.findNext, 'keypress', function (e) {
if (e.key === "Enter") {
addDomListener(actionElements.findNext, 'keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
term.findNext(actionElements.findNext.value);
let searchOptions = {
regex: (document.getElementById('regex') as HTMLInputElement).checked,
wholeWord: false,
caseSensitive: false
};
term.findNext(actionElements.findNext.value, searchOptions);
}
});
addDomListener(actionElements.findPrevious, 'keypress', function (e) {
if (e.key === "Enter") {
addDomListener(actionElements.findPrevious, 'keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
term.findPrevious(actionElements.findPrevious.value);
let searchOptions = {
regex: (document.getElementById('regex') as HTMLInputElement).checked,
wholeWord: false,
caseSensitive: false
};
term.findPrevious(actionElements.findPrevious.value, searchOptions);
}
});
// fit is called within a setTimeout, cols and rows need this.
setTimeout(function () {
setTimeout(() => {
initOptions(term);
document.getElementById(`opt-cols`).value = term.cols;
document.getElementById(`opt-rows`).value = term.rows;
paddingElement.value = 0;
// TODO: Clean this up, opt-cols/rows doesn't exist anymore
(<HTMLInputElement>document.getElementById(`opt-cols`)).value = term.cols;
(<HTMLInputElement>document.getElementById(`opt-rows`)).value = term.rows;
paddingElement.value = '0';
// Set terminal size again to set the specific dimensions on the demo
updateTerminalSize();
fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) {
res.text().then(function (processId) {
fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then((res) => {
res.text().then((processId) => {
pid = processId;
socketURL += processId;
socket = new WebSocket(socketURL);
@@ -117,22 +144,21 @@ function createTerminal() {
}, 0);
}
function runRealTerminal() {
function runRealTerminal(): void {
term.attach(socket);
term._initialized = true;
}
function runFakeTerminal() {
// TODO: Maybe fake terminal should be removed? Not sure it's useful anymore
function runFakeTerminal(): void {
if (term._initialized) {
return;
}
term._initialized = true;
var shellprompt = '$ ';
term.prompt = function () {
term.write('\r\n' + shellprompt);
term.prompt = () => {
term.write('\r\n$ ');
};
term.writeln('Welcome to xterm.js');
@@ -141,14 +167,12 @@ function runFakeTerminal() {
term.writeln('');
term.prompt();
term._core.register(term.addDisposableListener('key', function (key, ev) {
var printable = (
!ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey
);
term._core.register(term.addDisposableListener('key', (key, ev) => {
const printable = !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey;
if (ev.keyCode == 13) {
if (ev.keyCode === 13) {
term.prompt();
} else if (ev.keyCode == 8) {
} else if (ev.keyCode === 8) {
// Do not delete the prompt
if (term.x > 2) {
term.write('\b \b');
@@ -158,13 +182,13 @@ function runFakeTerminal() {
}
}));
term._core.register(term.addDisposableListener('paste', function (data, ev) {
term._core.register(term.addDisposableListener('paste', (data, ev) => {
term.write(data);
}));
}
function initOptions(term) {
var blacklistedOptions = [
function initOptions(term: TerminalType): void {
const blacklistedOptions = [
// Internal only options
'cancelEvents',
'convertEol',
@@ -176,7 +200,7 @@ function initOptions(term) {
// Complex option
'theme'
];
var stringOptions = {
const stringOptions = {
bellSound: null,
bellStyle: ['none', 'sound'],
cursorStyle: ['block', 'underline', 'bar'],
@@ -186,9 +210,9 @@ function initOptions(term) {
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
rendererType: ['dom', 'canvas']
};
var options = Object.keys(term._core.options);
var booleanOptions = [];
var numberOptions = [];
const options = Object.keys((<any>term)._core.options);
const booleanOptions = [];
const numberOptions = [];
options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
switch (typeof term.getOption(o)) {
case 'boolean':
@@ -204,7 +228,7 @@ function initOptions(term) {
}
});
var html = '';
let html = '';
html += '<div class="option-group">';
booleanOptions.forEach(o => {
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.getOption(o) ? 'checked' : ''}/> ${o}</label></div>`;
@@ -218,24 +242,24 @@ function initOptions(term) {
if (stringOptions[o]) {
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${term.getOption(o) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
} else {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.getOption(o)}"/></label></div>`
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.getOption(o)}"/></label></div>`;
}
});
html += '</div>';
var container = document.getElementById('options-container');
const container = document.getElementById('options-container');
container.innerHTML = html;
// Attach listeners
booleanOptions.forEach(o => {
var input = document.getElementById(`opt-${o}`);
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.checked);
term.setOption(o, input.checked);
});
});
numberOptions.forEach(o => {
var input = document.getElementById(`opt-${o}`);
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') {
@@ -246,7 +270,7 @@ function initOptions(term) {
});
});
Object.keys(stringOptions).forEach(o => {
var input = document.getElementById(`opt-${o}`);
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
term.setOption(o, input.value);
@@ -254,16 +278,16 @@ function initOptions(term) {
});
}
function addDomListener(element, type, handler) {
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
element.addEventListener(type, handler);
term._core.register({ dispose: () => element.removeEventListener(type, handler) });
}
function updateTerminalSize() {
var cols = parseInt(document.getElementById(`opt-cols`).value, 10);
var rows = parseInt(document.getElementById(`opt-rows`).value, 10);
var width = (cols * term._core.renderer.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px';
var height = (rows * term._core.renderer.dimensions.actualCellHeight).toString() + 'px';
function updateTerminalSize(): void {
const cols = parseInt((<HTMLInputElement>document.getElementById(`opt-cols`)).value, 10);
const rows = parseInt((<HTMLInputElement>document.getElementById(`opt-rows`)).value, 10);
const width = (cols * term._core.renderer.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px';
const height = (rows * term._core.renderer.dimensions.actualCellHeight).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
term.fit();
+2 -1
View File
@@ -16,6 +16,7 @@
<p>
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
<label>Use regex<input type="checkbox" id="regex"/></label>
</p>
</div>
<div>
@@ -33,6 +34,6 @@
<hr/>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
<script src="dist/bundle.js" defer ></script>
<script src="dist/client-bundle.js" defer ></script>
</body>
</html>
+2 -6
View File
@@ -17,12 +17,8 @@ app.get('/style.css', function(req, res){
res.sendFile(__dirname + '/style.css');
});
app.get('/dist/bundle.js', function(req, res){
res.sendFile(__dirname + '/dist/bundle.js');
});
app.get('/dist/bundle.js.map', function(req, res){
res.sendFile(__dirname + '/dist/bundle.js.map');
app.get('/dist/client-bundle.js', function(req, res){
res.sendFile(__dirname + '/dist/client-bundle.js');
});
app.post('/terminals', function (req, res) {
+49
View File
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*
* This file is the entry point for browserify.
*/
const cp = require('child_process');
const path = require('path');
const webpack = require('webpack');
// Launch server
cp.spawn('node', [path.resolve(__dirname, 'server.js')], { stdio: 'inherit' });
// Build/watch client source
const clientConfig = {
entry: path.resolve(__dirname, 'client.ts'),
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ]
},
output: {
filename: 'client-bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
watch: true
};
const compiler = webpack(clientConfig);
compiler.watch({
// Example watchOptions
aggregateTimeout: 300,
poll: undefined
}, (err, stats) => {
// Print watch/build result here...
console.log(stats.toString({
colors: true
}));
});
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"sourceMap": true
},
"include": [
"client.ts",
"../typings/xterm.d.ts"
]
}
+4
View File
@@ -5,6 +5,10 @@
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [
"dom",
"es6"
],
"noEmit": true
}
}
-12
View File
@@ -14,7 +14,6 @@ const source = require('vinyl-source-stream');
const sourcemaps = require('gulp-sourcemaps');
const ts = require('gulp-typescript');
const util = require('gulp-util');
const webpack = require('webpack-stream');
const buildDir = process.env.BUILD_DIR || 'build';
const tsProject = ts.createProject('tsconfig.json');
@@ -137,17 +136,6 @@ gulp.task('sorcery-addons', ['browserify-addons'], function () {
})
});
gulp.task('webpack', ['build'], function() {
return gulp.src('demo/main.js')
.pipe(webpack(require('./webpack.config.js')))
.pipe(gulp.dest('demo/dist/'));
});
gulp.task('watch-demo', ['webpack'], () => {
gulp.watch(['./demo/*', './lib/**/*'], ['webpack']);
});
gulp.task('build', ['sorcery', 'sorcery-addons']);
gulp.task('test', ['mocha']);
gulp.task('default', ['build']);
+7 -5
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "3.6.0",
"version": "3.7.0",
"main": "lib/public/Terminal.js",
"types": "typings/xterm.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
@@ -12,6 +12,7 @@
"@types/jsdom": "11.0.1",
"@types/mocha": "^2.2.33",
"@types/node": "6.0.108",
"@types/webpack": "^4.4.11",
"browserify": "^13.3.0",
"chai": "3.5.0",
"concurrently": "^3.5.1",
@@ -36,17 +37,18 @@
"nyc": "^11.8.0",
"sorcery": "^0.10.0",
"source-map-loader": "^0.2.3",
"ts-loader": "^4.5.0",
"tslint": "^5.9.1",
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "2.8.3",
"typescript": "3.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"webpack": "^3.10.0",
"webpack-stream": "^4.0.0",
"webpack": "^4.17.1",
"webpack-cli": "^3.1.0",
"zmodem.js": "^0.1.5"
},
"scripts": {
"start": "concurrently --kill-others-on-fail --names \"demo,server\" \"gulp watch-demo\" \"node demo/app\"",
"start": "node demo/start",
"start-zmodem": "node demo/zmodem/app",
"lint": "tslint 'src/**/*.ts'",
"test": "npm-run-all mocha lint",
+1 -1
View File
@@ -5,7 +5,7 @@
import { CircularList } from './common/CircularList';
import { CharData, ITerminal, IBuffer, IBufferLine } from './Types';
import { EventEmitter } from './EventEmitter';
import { EventEmitter } from './common/EventEmitter';
import { IMarker } from 'xterm';
import { BufferLine } from './BufferLine';
+1 -1
View File
@@ -5,7 +5,7 @@
import { ITerminal, IBufferSet } from './Types';
import { Buffer } from './Buffer';
import { EventEmitter } from './EventEmitter';
import { EventEmitter } from './common/EventEmitter';
/**
* The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and
+1 -1
View File
@@ -6,7 +6,7 @@
import { IMouseZoneManager } from './ui/Types';
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferLine } from './Types';
import { MouseZone } from './ui/MouseZoneManager';
import { EventEmitter } from './EventEmitter';
import { EventEmitter } from './common/EventEmitter';
import { CHAR_DATA_ATTR_INDEX } from './Buffer';
/**
+3 -2
View File
@@ -3,11 +3,12 @@
* @license MIT
*/
import { ITerminal, ISelectionManager, IBuffer, CharData, XtermListener, IBufferLine } from './Types';
import { ITerminal, ISelectionManager, IBuffer, CharData, IBufferLine } from './Types';
import { XtermListener } from './common/Types';
import { MouseHelper } from './utils/MouseHelper';
import * as Browser from './shared/utils/Browser';
import { CharMeasure } from './ui/CharMeasure';
import { EventEmitter } from './EventEmitter';
import { EventEmitter } from './common/EventEmitter';
import { SelectionModel } from './SelectionModel';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer';
import { AltClickHandler } from './handlers/AltClickHandler';
+1 -1
View File
@@ -27,7 +27,7 @@ import { IRenderer } from './renderer/Types';
import { BufferSet } from './BufferSet';
import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer';
import { CompositionHelper } from './CompositionHelper';
import { EventEmitter } from './EventEmitter';
import { EventEmitter } from './common/EventEmitter';
import { Viewport } from './Viewport';
import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';
import { C0 } from './common/data/EscapeSequences';
+1 -15
View File
@@ -7,11 +7,10 @@ import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions,
import { IColorSet, IRenderer } from './renderer/Types';
import { IMouseZoneManager } from './ui/Types';
import { ICharset } from './core/Types';
import { ICircularList } from './common/Types';
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
export type XtermListener = (...args: any[]) => void;
export type CharData = [number, string, number, number];
export type LineData = CharData[];
@@ -294,19 +293,6 @@ export interface IBufferSet extends IEventEmitter {
activateAltBuffer(): void;
}
export interface ICircularList<T> extends IEventEmitter {
length: number;
maxLength: number;
get(index: number): T;
set(index: number, value: T): void;
push(value: T): void;
pop(): T;
splice(start: number, deleteCount: number, ...items: T[]): void;
trimStart(count: number): void;
shiftElements(start: number, count: number, offset: number): void;
}
export interface ISelectionManager {
selectionText: string;
selectionStart: [number, number];
+4
View File
@@ -2,6 +2,10 @@
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [
"dom",
"es6",
],
"rootDir": ".",
"outDir": "../../../lib/addons/attach/",
"sourceMap": true,
+4
View File
@@ -2,6 +2,10 @@
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [
"dom",
"es6",
],
"rootDir": ".",
"outDir": "../../../lib/addons/fit/",
"sourceMap": true,
+4
View File
@@ -2,6 +2,10 @@
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [
"dom",
"es6",
],
"rootDir": ".",
"outDir": "../../../lib/addons/fullscreen/",
"sourceMap": true,
+14 -2
View File
@@ -17,6 +17,18 @@ export interface ISearchAddonTerminal extends Terminal {
}
export interface ISearchHelper {
findNext(term: string): boolean;
findPrevious(term: string): boolean;
findNext(term: string, searchOptions: ISearchOptions): boolean;
findPrevious(term: string, searchOptions: ISearchOptions): boolean;
}
export interface ISearchOptions {
regex?: boolean;
wholeWord?: boolean;
caseSensitive?: boolean;
}
export interface ISearchResult {
term: string;
col: number;
row: number;
}
+23 -17
View File
@@ -3,13 +3,7 @@
* @license MIT
*/
import { ISearchHelper, ISearchAddonTerminal } from './Interfaces';
interface ISearchResult {
term: string;
col: number;
row: number;
}
import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces';
/**
* A class that knows how to search the terminal and how to display the results.
@@ -19,16 +13,16 @@ export class SearchHelper implements ISearchHelper {
// TODO: Search for multiple instances on 1 line
// TODO: Don't use the actual selection, instead use a "find selection" so multiple instances can be highlighted
// TODO: Highlight other instances in the viewport
// TODO: Support regex, case sensitivity, etc.
}
/**
* Find the next instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
* @param term Tne search term.
* @param searchOptions Search options.
* @return Whether a result was found.
*/
public findNext(term: string): boolean {
public findNext(term: string, searchOptions?: ISearchOptions): boolean {
if (!term || term.length === 0) {
return false;
}
@@ -43,7 +37,7 @@ export class SearchHelper implements ISearchHelper {
// Search from ydisp + 1 to end
for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
result = this._findInLine(term, y);
result = this._findInLine(term, y, searchOptions);
if (result) {
break;
}
@@ -52,7 +46,7 @@ export class SearchHelper implements ISearchHelper {
// Search from the top to the current ydisp
if (!result) {
for (let y = 0; y < startRow; y++) {
result = this._findInLine(term, y);
result = this._findInLine(term, y, searchOptions);
if (result) {
break;
}
@@ -67,9 +61,10 @@ export class SearchHelper implements ISearchHelper {
* Find the previous instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
* @param term Tne search term.
* @param searchOptions Search options.
* @return Whether a result was found.
*/
public findPrevious(term: string): boolean {
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean {
if (!term || term.length === 0) {
return false;
}
@@ -84,7 +79,7 @@ export class SearchHelper implements ISearchHelper {
// Search from ydisp + 1 to end
for (let y = startRow - 1; y >= 0; y--) {
result = this._findInLine(term, y);
result = this._findInLine(term, y, searchOptions);
if (result) {
break;
}
@@ -93,7 +88,7 @@ export class SearchHelper implements ISearchHelper {
// Search from the top to the current ydisp
if (!result) {
for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
result = this._findInLine(term, y);
result = this._findInLine(term, y, searchOptions);
if (result) {
break;
}
@@ -106,14 +101,25 @@ export class SearchHelper implements ISearchHelper {
/**
* Searches a line for a search term.
* @param term Tne search term.
* @param term The search term.
* @param y The line to search.
* @param searchOptions Search options.
* @return The search result if it was found.
*/
private _findInLine(term: string, y: number): ISearchResult {
protected _findInLine(term: string, y: number, searchOptions: ISearchOptions = {}): ISearchResult {
const lowerStringLine = this._terminal._core.buffer.translateBufferLineToString(y, true).toLowerCase();
const lowerTerm = term.toLowerCase();
let searchIndex = lowerStringLine.indexOf(lowerTerm);
let searchIndex = -1;
if (searchOptions.regex) {
const searchRegex = RegExp(lowerTerm, 'g');
const foundTerm = searchRegex.exec(lowerStringLine);
if (foundTerm) {
searchIndex = searchRegex.lastIndex - foundTerm[0].length;
term = foundTerm[0];
}
} else {
searchIndex = lowerStringLine.indexOf(lowerTerm);
}
if (searchIndex >= 0) {
const line = this._terminal._core.buffer.lines.get(y);
for (let i = 0; i < searchIndex; i++) {
+41 -6
View File
@@ -6,17 +6,17 @@
import { assert, expect } from 'chai';
import * as search from './search';
import { SearchHelper } from './SearchHelper';
import { ISearchHelper } from './Interfaces';
import { ISearchOptions, ISearchResult } from './Interfaces';
class MockTerminalPlain {}
class MockTerminal {
private _core: any;
public searchHelper: ISearchHelper;
public searchHelper: TestSearchHelper;
constructor(options: any) {
this._core = new (require('../../../lib/Terminal').Terminal)(options);
this.searchHelper = new SearchHelper(this as any);
this.searchHelper = new TestSearchHelper(this as any);
}
get core(): any {
return this._core;
@@ -26,6 +26,12 @@ class MockTerminal {
}
}
class TestSearchHelper extends SearchHelper {
public findInLine(term: string, y: number, searchOptions?: ISearchOptions): ISearchResult {
return this._findInLine(term, y, searchOptions);
}
}
describe('search addon', function(): void {
describe('apply', () => {
it('should register findNext and findPrevious', () => {
@@ -39,11 +45,40 @@ describe('search addon', function(): void {
const term = new MockTerminal({cols: 20, rows: 3});
term.core.write('Hello World\r\ntest\n123....hello');
term.pushWriteData();
const hello0 = (term.searchHelper as any)._findInLine('Hello', 0);
const hello1 = (term.searchHelper as any)._findInLine('Hello', 1);
const hello2 = (term.searchHelper as any)._findInLine('Hello', 2);
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 respect search regex', function(): void {
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);
term.searchHelper.findInLine('jkk*', 0, searchOptions);
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'});
// TODO: uncomment this test when line wrap search is checked in expect(hello1).eql({col: 9, row: 0, term: 'jk'});
// TODO: uncomment this test when line wrap search is checked in 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: '~'});
});
});

Some files were not shown because too many files have changed in this diff Show More