From 8d12881a1cd9f8f88160f6b7686c9db446a92dbc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 12 Jun 2017 16:27:13 -0700 Subject: [PATCH] Very basic find implementation Part of #553 --- src/Interfaces.ts | 3 ++ src/SearchHelper.ts | 75 +++++++++++++++++++++++++++++++++++++++++ src/SelectionManager.ts | 9 +++++ src/xterm.js | 3 ++ 4 files changed, 90 insertions(+) create mode 100644 src/SearchHelper.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 4b857674..5016b4f6 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -21,6 +21,7 @@ export interface ITerminal { element: HTMLElement; rowContainer: HTMLElement; selectionContainer: HTMLElement; + selectionManager: ISelectionManager; charMeasure: ICharMeasure; textarea: HTMLTextAreaElement; ybase: number; @@ -51,6 +52,8 @@ export interface ITerminal { export interface ISelectionManager { selectionText: string; + + setSelection(row: number, col: number, length: number); } export interface ICharMeasure { diff --git a/src/SearchHelper.ts b/src/SearchHelper.ts new file mode 100644 index 00000000..f5021076 --- /dev/null +++ b/src/SearchHelper.ts @@ -0,0 +1,75 @@ +import { ITerminal } from './Interfaces'; + +export class SearchHelper { + constructor(private _terminal: ITerminal) { + + } + + /** + * Find the next instance of the term, then scroll to and select it. If it + * doesn't exist, do nothing. + * @param term The term to search for. + */ + public findNext(term: string): void { + if (!term || term.length === 0) { + return; + } + // TODO: Return number of results? + + for (let currentLine = this._terminal.ydisp; currentLine < this._terminal.ybase + this._terminal.rows; currentLine++) { + const bufferLine = this._terminal.lines.get(currentLine); + const stringLine = this._translateBufferLineToString(bufferLine, true); + const searchIndex = stringLine.indexOf(term); + if (searchIndex >= 0) { + console.log('found term on line: ' + currentLine); + console.log(stringLine); + this._terminal.selectionManager.setSelection(searchIndex, currentLine, term.length); + break; + } + } + } + + // TODO: Consolidate with SelectionManager function + private _translateBufferLineToString(line: any, trimRight: boolean, startCol: number = 0, endCol: number = null): string { + // TODO: This function should live in a buffer or buffer line class + + // TODO: Move these constants elsewhere, they belong in a buffer or buffer + // data/line class. + const LINE_DATA_CHAR_INDEX = 1; + const LINE_DATA_WIDTH_INDEX = 2; + // Get full line + let lineString = ''; + let widthAdjustedStartCol = startCol; + let widthAdjustedEndCol = endCol; + for (let i = 0; i < line.length; i++) { + const char = line[i]; + lineString += char[LINE_DATA_CHAR_INDEX]; + // Adjust start and end cols for wide characters if they affect their + // column indexes + if (char[LINE_DATA_WIDTH_INDEX] === 0) { + if (startCol >= i) { + widthAdjustedStartCol--; + } + if (endCol >= i) { + widthAdjustedEndCol--; + } + } + } + + // Calculate the final end col by trimming whitespace on the right of the + // line if needed. + let finalEndCol = widthAdjustedEndCol || line.length; + if (trimRight) { + const rightWhitespaceIndex = lineString.search(/\s+$/); + if (rightWhitespaceIndex !== -1) { + finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex); + } + // Return the empty string if only trimmed whitespace is selected + if (finalEndCol <= widthAdjustedStartCol) { + return ''; + } + } + + return lineString.substring(widthAdjustedStartCol, finalEndCol); + } +} diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 906d5b01..3c3b839f 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -551,6 +551,15 @@ export class SelectionManager extends EventEmitter { return charIndex; } + public setSelection(col: number, row: number, length: number): void { + this._model.clearSelection(); + this._removeMouseDownListeners(); + console.log('setSelection', arguments); + this._model.selectionStart = [col, row]; + this._model.selectionStartLength = length; + this.refresh(); + } + /** * Selects the word at the coordinates specified. Words are defined as all * non-whitespace characters. diff --git a/src/xterm.js b/src/xterm.js index a476fd7e..789a7adf 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -21,6 +21,7 @@ import { Parser } from './Parser'; import { Renderer } from './Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; +import { SearchHelper } from './SearchHelper'; import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './utils/Browser'; import * as Mouse from './utils/Mouse'; @@ -223,6 +224,7 @@ function Terminal(options) { this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; this.linkifier = this.linkifier || new Linkifier(); + this.searchHelper = this.searchHelper || null; // user input states this.writeBuffer = []; @@ -706,6 +708,7 @@ Terminal.prototype.open = function(parent, focus) { this.selectionManager.on('refresh', data => this.renderer.refreshSelection(data.start, data.end)); this.on('scroll', () => this.selectionManager.refresh()); this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh()); + this.searchHelper = new SearchHelper(this);; // Setup loop that draws to screen this.refresh(0, this.rows - 1);