Merge remote-tracking branch 'origin/master' into pr/noamyogev84/1763
This commit is contained in:
@@ -13,11 +13,11 @@
|
||||
"declaration": true,
|
||||
"preserveWatchOutput": true,
|
||||
"types": [
|
||||
"../../node_modules/@types/mocha",
|
||||
"../.."
|
||||
"../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
|
||||
fn = term.element.classList.add;
|
||||
}
|
||||
|
||||
fn = fn.bind(term.element.classList);
|
||||
fn('fullscreen');
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
"declaration": true,
|
||||
"preserveWatchOutput": true,
|
||||
"types": [
|
||||
"../../node_modules/@types/mocha",
|
||||
"../.."
|
||||
"../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ export interface ISearchOptions {
|
||||
regex?: boolean;
|
||||
wholeWord?: boolean;
|
||||
caseSensitive?: boolean;
|
||||
/** Assume caller implements 'search as you type' where findNext gets called when search input changes */
|
||||
incremental?: boolean;
|
||||
}
|
||||
|
||||
export interface ISearchIndex {
|
||||
|
||||
@@ -4,15 +4,24 @@
|
||||
*/
|
||||
|
||||
import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult, ISearchIndex } from './Interfaces';
|
||||
const nonWordCharacters = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
|
||||
|
||||
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;
|
||||
|
||||
constructor(private _terminal: ISearchAddonTerminal) {
|
||||
// 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
|
||||
this._destroyLinesCache = this._destroyLinesCache.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,31 +32,40 @@ export class SearchHelper implements ISearchHelper {
|
||||
* @return Whether a result was found.
|
||||
*/
|
||||
public findNext(term: string, searchOptions?: ISearchOptions): boolean {
|
||||
const selectionManager = this._terminal._core.selectionManager;
|
||||
const {incremental} = searchOptions;
|
||||
let result: ISearchResult;
|
||||
|
||||
if (!term || term.length === 0) {
|
||||
selectionManager.clearSelection();
|
||||
return false;
|
||||
}
|
||||
|
||||
let result: ISearchResult;
|
||||
let startRow = this._terminal._core.buffer.ydisp;
|
||||
let startCol: number = 0;
|
||||
if (this._terminal._core.selectionManager.selectionEnd) {
|
||||
let startRow = this._terminal._core.buffer.ydisp;
|
||||
|
||||
if (selectionManager.selectionEnd) {
|
||||
// Start from the selection end if there is a selection
|
||||
// For incremental search, use existing row
|
||||
if (this._terminal.getSelection().length !== 0) {
|
||||
startRow = this._terminal._core.selectionManager.selectionEnd[1];
|
||||
startRow = incremental ? selectionManager.selectionStart[1] : selectionManager.selectionEnd[1];
|
||||
// TODO: Fix for incremental
|
||||
startCol = this._terminal._core.selectionManager.selectionEnd[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Search from ydisp + 1 to end
|
||||
for (let y = startRow; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
|
||||
result = this._findInLine(term, {row: y, col: startCol}, searchOptions);
|
||||
this._initLinesCache();
|
||||
|
||||
// Search from startRow to end
|
||||
for (let y = incremental ? startRow : startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
|
||||
result = this._findInLine(term, { row: y, col: startCol }, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
startCol = 0;
|
||||
}
|
||||
|
||||
// Search from the top to the current ydisp
|
||||
// Search from the top to the startRow
|
||||
if (!result) {
|
||||
for (let y = 0; y < startRow; y++) {
|
||||
startCol = 0;
|
||||
@@ -70,24 +88,31 @@ export class SearchHelper implements ISearchHelper {
|
||||
* @return Whether a result was found.
|
||||
*/
|
||||
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean {
|
||||
const selectionManager = this._terminal._core.selectionManager;
|
||||
const {incremental} = searchOptions;
|
||||
let result: ISearchResult;
|
||||
|
||||
if (!term || term.length === 0) {
|
||||
selectionManager.clearSelection();
|
||||
return false;
|
||||
}
|
||||
|
||||
const isReverseSearch = true;
|
||||
let result: ISearchResult;
|
||||
let startRow = this._terminal._core.buffer.ydisp;
|
||||
let startCol: number = this._terminal._core.buffer.lines.get(startRow).length;
|
||||
|
||||
if (this._terminal._core.selectionManager.selectionStart) {
|
||||
// Start from the selection end if there is a selection
|
||||
if (selectionManager.selectionStart) {
|
||||
// Start from the selection start if there is a selection
|
||||
if (this._terminal.getSelection().length !== 0) {
|
||||
startRow = this._terminal._core.selectionManager.selectionStart[1];
|
||||
startCol = this._terminal._core.selectionManager.selectionStart[0];
|
||||
startRow = selectionManager.selectionStart[1];
|
||||
startCol = selectionManager.selectionStart[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Search from ydisp + 1 to end
|
||||
for (let y = startRow; y >= 0; y--) {
|
||||
this._initLinesCache();
|
||||
|
||||
// Search from startRow to top
|
||||
for (let y = incremental ? startRow : startRow - 1; y >= 0; y--) {
|
||||
result = this._findInLine(term, {row: y, col: startCol}, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
@@ -95,7 +120,7 @@ export class SearchHelper implements ISearchHelper {
|
||||
startCol = y > 0 ? this._terminal._core.buffer.lines.get(y - 1).length : 0;
|
||||
}
|
||||
|
||||
// Search from the top to the current ydisp
|
||||
// Search from the bottom to startRow
|
||||
if (!result) {
|
||||
const searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1;
|
||||
for (let y = searchFrom; y > startRow; y--) {
|
||||
@@ -111,6 +136,28 @@ export class SearchHelper implements ISearchHelper {
|
||||
return this._selectResult(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a line cache with a ttl
|
||||
*/
|
||||
private _initLinesCache(): void {
|
||||
if (!this._linesCache) {
|
||||
this._linesCache = new Array(this._terminal._core.buffer.length);
|
||||
this._terminal.on('cursormove', this._destroyLinesCache);
|
||||
}
|
||||
|
||||
window.clearTimeout(this._linesCacheTimeoutId);
|
||||
this._linesCacheTimeoutId = window.setTimeout(() => this._destroyLinesCache(), LINES_CACHE_TIME_TO_LIVE);
|
||||
}
|
||||
|
||||
private _destroyLinesCache(): void {
|
||||
this._linesCache = null;
|
||||
this._terminal.off('cursormove', this._destroyLinesCache);
|
||||
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
|
||||
@@ -118,8 +165,8 @@ export class SearchHelper implements ISearchHelper {
|
||||
* @param term the substring that starts at searchIndex
|
||||
*/
|
||||
private _isWholeWord(searchIndex: number, line: string, term: string): boolean {
|
||||
return (((searchIndex === 0) || (nonWordCharacters.indexOf(line[searchIndex - 1]) !== -1)) &&
|
||||
(((searchIndex + term.length) === line.length) || (nonWordCharacters.indexOf(line[searchIndex + term.length]) !== -1)));
|
||||
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)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +184,14 @@ export class SearchHelper implements ISearchHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
const stringLine = this.translateBufferLineToStringWithWrap(searchIndex.row, true);
|
||||
let stringLine = this._linesCache ? this._linesCache[searchIndex.row] : void 0;
|
||||
if (stringLine === void 0) {
|
||||
stringLine = this.translateBufferLineToStringWithWrap(searchIndex.row, true);
|
||||
if (this._linesCache) {
|
||||
this._linesCache[searchIndex.row] = stringLine;
|
||||
}
|
||||
}
|
||||
|
||||
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
|
||||
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
||||
|
||||
|
||||
@@ -121,8 +121,6 @@ describe('search addon', () => {
|
||||
it('should not select empty lines', () => {
|
||||
search.apply(<any>MockTerminal);
|
||||
const term = new MockTerminal({cols: 20, rows: 3});
|
||||
term.core.write(' ');
|
||||
term.pushWriteData();
|
||||
const line = term.searchHelper.findInLine('^.*$', 0, { regex: true });
|
||||
expect(line).eql(undefined);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es5"
|
||||
],
|
||||
"rootDir": ".",
|
||||
@@ -12,11 +13,11 @@
|
||||
"declaration": true,
|
||||
"preserveWatchOutput": true,
|
||||
"types": [
|
||||
"../../node_modules/@types/mocha",
|
||||
"../.."
|
||||
"../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
"declaration": true,
|
||||
"preserveWatchOutput": true,
|
||||
"types": [
|
||||
"../../node_modules/@types/mocha",
|
||||
"../.."
|
||||
"../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
"declaration": true,
|
||||
"preserveWatchOutput": true,
|
||||
"types": [
|
||||
"../../node_modules/@types/mocha",
|
||||
"../.."
|
||||
"../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
"declaration": true,
|
||||
"preserveWatchOutput": true,
|
||||
"types": [
|
||||
"../../node_modules/@types/mocha",
|
||||
"../.."
|
||||
"../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import { Terminal } from 'xterm';
|
||||
import { IWinptyCompatAddonTerminal } from './Interfaces';
|
||||
|
||||
const CHAR_DATA_CODE_INDEX = 3;
|
||||
const NULL_CELL_CODE = 32;
|
||||
const NULL_CELL_CODE = 0;
|
||||
const WHITESPACE_CELL_CODE = 32;
|
||||
|
||||
export function winptyCompatInit(terminal: Terminal): void {
|
||||
const addonTerminal = <IWinptyCompatAddonTerminal>terminal;
|
||||
@@ -32,7 +33,7 @@ export function winptyCompatInit(terminal: Terminal): void {
|
||||
const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);
|
||||
const lastChar = line.get(addonTerminal.cols - 1);
|
||||
|
||||
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE) {
|
||||
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) {
|
||||
const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);
|
||||
nextLine.isWrapped = true;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
"declaration": true,
|
||||
"preserveWatchOutput": true,
|
||||
"types": [
|
||||
"../../node_modules/@types/mocha",
|
||||
"../.."
|
||||
"../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user