Skip to content

Commit

Permalink
Merge pull request #2183 from Tyriar/search_strict
Browse files Browse the repository at this point in the history
Turn on TS strict mode in search addon
  • Loading branch information
Tyriar authored Jun 5, 2019
2 parents 360bf64 + 2526b2d commit 91ec163
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 78 deletions.
146 changes: 69 additions & 77 deletions addons/xterm-addon-search/src/SearchAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,54 +18,24 @@ export interface ISearchResult {
row: number;
}

// TODO: This is temporary, link to xtem when new version is published
interface INewTerminal extends Terminal {
buffer: IBuffer;
select(column: number, row: number, length: number): void;
getSelectionPosition(): ISelectionPosition | undefined;
}
interface IBuffer {
readonly cursorY: number;
readonly cursorX: number;
readonly viewportY: number;
readonly baseY: number;
readonly length: number;
getLine(y: number): IBufferLine | undefined;
}
interface IBufferLine {
readonly isWrapped: boolean;
getCell(x: number): IBufferCell;
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;
}
interface IBufferCell {
readonly char: string;
readonly width: number;
}
interface ISelectionPosition {
startColumn: number;
startRow: number;
endColumn: number;
endRow: number;
}

const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs

export class SearchAddon implements ITerminalAddon {
private _terminal: INewTerminal;
private _terminal: Terminal | undefined;

/**
* 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 _linesCache: string[] | undefined;
private _linesCacheTimeoutId = 0;
private _cursorMoveListener: IDisposable | undefined;
private _resizeListener: IDisposable | undefined;

public activate(terminal: Terminal): void {
this._terminal = <any>terminal;
this._terminal = terminal;
}

public dispose(): void {}
Expand All @@ -78,8 +48,9 @@ export class SearchAddon implements ITerminalAddon {
* @return Whether a result was found.
*/
public findNext(term: string, searchOptions?: ISearchOptions): boolean {
const {incremental} = searchOptions;
let result: ISearchResult;
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}

if (!term || term.length === 0) {
this._terminal.clearSelection();
Expand All @@ -90,9 +61,10 @@ export class SearchAddon implements ITerminalAddon {
let startRow = this._terminal.buffer.viewportY;

if (this._terminal.hasSelection()) {
const incremental = searchOptions ? searchOptions.incremental : false;
// Start from the selection end if there is a selection
// For incremental search, use existing row
const currentSelection = this._terminal.getSelectionPosition();
const currentSelection = this._terminal.getSelectionPosition()!;
startRow = incremental ? currentSelection.startRow : currentSelection.endRow;
startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn;
}
Expand All @@ -105,13 +77,14 @@ export class SearchAddon implements ITerminalAddon {
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--;
let currentLine = this._terminal.buffer.getLine(findingRow);
while (currentLine && currentLine.isWrapped) {
cumulativeCols += this._terminal.cols;
currentLine = this._terminal.buffer.getLine(--findingRow);
}

// Search startRow
result = this._findInLine(term, findingRow, cumulativeCols, searchOptions);
let result = this._findInLine(term, findingRow, cumulativeCols, searchOptions);

// Search from startRow + 1 to end
if (!result) {
Expand Down Expand Up @@ -150,7 +123,9 @@ export class SearchAddon implements ITerminalAddon {
* @return Whether a result was found.
*/
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean {
let result: ISearchResult;
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}

if (!term || term.length === 0) {
this._terminal.clearSelection();
Expand All @@ -163,22 +138,22 @@ export class SearchAddon implements ITerminalAddon {

if (this._terminal.hasSelection()) {
// Start from the selection start if there is a selection
const currentSelection = this._terminal.getSelectionPosition();
const currentSelection = this._terminal.getSelectionPosition()!;
startRow = currentSelection.startRow;
startCol = currentSelection.startColumn;
}

this._initLinesCache();

// Search startRow
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
let 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) {
if (this._terminal.buffer.getLine(startRow)!.isWrapped) {
cumulativeCols += startCol;
}
for (let y = startRow - 1; y >= 0; y--) {
Expand All @@ -188,7 +163,8 @@ export class SearchAddon implements ITerminalAddon {
}
// If the current line is wrapped line, increase scanning range,
// preparing for scanning on unwrapped line
if (this._terminal.buffer.getLine(y).isWrapped) {
const line = this._terminal.buffer.getLine(y);
if (line && line.isWrapped) {
cumulativeCols += this._terminal.cols;
} else {
cumulativeCols = this._terminal.cols;
Expand All @@ -206,7 +182,8 @@ export class SearchAddon implements ITerminalAddon {
if (result) {
break;
}
if (this._terminal.buffer.getLine(y).isWrapped) {
const line = this._terminal.buffer.getLine(y);
if (line && line.isWrapped) {
cumulativeCols += this._terminal.cols;
} else {
cumulativeCols = this._terminal.cols;
Expand All @@ -222,18 +199,19 @@ export class SearchAddon implements ITerminalAddon {
* Sets up a line cache with a ttl
*/
private _initLinesCache(): void {
const terminal = this._terminal!;
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());
this._linesCache = new Array(terminal.buffer.length);
this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache());
this._resizeListener = 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;
this._linesCache = undefined;
if (this._cursorMoveListener) {
this._cursorMoveListener.dispose();
this._cursorMoveListener = undefined;
Expand Down Expand Up @@ -270,15 +248,17 @@ export class SearchAddon implements ITerminalAddon {
* @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 {
protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
const terminal = this._terminal!;

// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
if (this._terminal.buffer.getLine(row).isWrapped) {
const firstLine = terminal.buffer.getLine(row);
if (firstLine && firstLine.isWrapped) {
return;
}
let stringLine = this._linesCache ? this._linesCache[row] : void 0;
if (stringLine === void 0) {
stringLine = this.translateBufferLineToStringWithWrap(row, true);
stringLine = this._translateBufferLineToStringWithWrap(row, true);
if (this._linesCache) {
this._linesCache[row] = stringLine;
}
Expand All @@ -290,7 +270,7 @@ export class SearchAddon implements ITerminalAddon {
let resultIndex = -1;
if (searchOptions.regex) {
const searchRegex = RegExp(searchTerm, 'g');
let foundTerm: RegExpExecArray;
let foundTerm: RegExpExecArray | null;
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))) {
Expand All @@ -317,28 +297,33 @@ export class SearchAddon implements ITerminalAddon {

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 (resultIndex >= terminal.cols) {
row += Math.floor(resultIndex / terminal.cols);
resultIndex = resultIndex % 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++;
const line = terminal.buffer.getLine(row);

if (line) {
for (let i = 0; i < resultIndex; i++) {
const cell = line.getCell(i);
if (!cell) {
break;
}
// 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 {
Expand All @@ -348,6 +333,7 @@ export class SearchAddon implements ITerminalAddon {
};
}
}

/**
* 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
Expand All @@ -356,14 +342,19 @@ export class SearchAddon implements ITerminalAddon {
* @param line The line being translated.
* @param trimRight Whether to trim whitespace to the right.
*/
public translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): string {
private _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): string {
const terminal = this._terminal!;
let lineString = '';
let lineWrapsToNext: boolean;

do {
const nextLine = this._terminal.buffer.getLine(lineIndex + 1);
const nextLine = terminal.buffer.getLine(lineIndex + 1);
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
lineString += this._terminal.buffer.getLine(lineIndex).translateToString(!lineWrapsToNext && trimRight).substring(0, this._terminal.cols);
const line = terminal.buffer.getLine(lineIndex);
if (!line) {
break;
}
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols);
lineIndex++;
} while (lineWrapsToNext);

Expand All @@ -375,13 +366,14 @@ export class SearchAddon implements ITerminalAddon {
* @param result The result to select.
* @return Whethera result was selected.
*/
private _selectResult(result: ISearchResult): boolean {
private _selectResult(result: ISearchResult | undefined): boolean {
const terminal = this._terminal!;
if (!result) {
this._terminal.clearSelection();
terminal.clearSelection();
return false;
}
this._terminal.select(result.col, result.row, result.term.length);
this._terminal.scrollLines(result.row - this._terminal.buffer.viewportY);
terminal.select(result.col, result.row, result.term.length);
terminal.scrollLines(result.row - terminal.buffer.viewportY);
return true;
}
}
3 changes: 2 additions & 1 deletion addons/xterm-addon-search/src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"rootDir": ".",
"outDir": "../lib",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"strict": true
},
"include": [
"./**/*",
Expand Down

0 comments on commit 91ec163

Please sign in to comment.