Merge remote-tracking branch 'origin/master' into pr/noamyogev84/1763

This commit is contained in:
Daniel Imms
2018-12-26 15:08:16 -08:00
32 changed files with 554 additions and 759 deletions
+77 -23
View File
@@ -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();