diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 4651f6c7..4a1a6ef0 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, IBufferLine, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm'; +import { Terminal, IBufferLine, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm'; export interface ISearchOptions { regex?: boolean; @@ -40,7 +40,7 @@ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs export class SearchAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - + private _resultDecorations: IDecoration[] = []; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -130,7 +130,7 @@ export class SearchAddon implements ITerminalAddon { } // Set selection and scroll if a result was found - return this._selectResult(result); + return true; } /** @@ -149,69 +149,23 @@ export class SearchAddon implements ITerminalAddon { this._terminal.clearSelection(); return false; } - - const isReverseSearch = true; - let startRow = this._terminal.buffer.active.baseY + this._terminal.rows; - let startCol = this._terminal.cols; - let result: ISearchResult | undefined; - const incremental = searchOptions ? searchOptions.incremental : false; - let currentSelection: ISelectionPosition | undefined; - if (this._terminal.hasSelection()) { - currentSelection = this._terminal.getSelectionPosition()!; - // Start from selection start if there is a selection - startRow = currentSelection.startRow; - startCol = currentSelection.startColumn; - } - - this._initLinesCache(); - const searchPosition: ISearchPosition = { - startRow, - startCol - }; - - if (incremental) { - // Try to expand selection to right first. - result = this._findInLine(term, searchPosition, searchOptions, false); - const isOldResultHighlighted = result && result.row === startRow && result.col === startCol; - if (!isOldResultHighlighted) { - // If selection was not able to be expanded to the right, then try reverse search - if (currentSelection) { - searchPosition.startRow = currentSelection.endRow; - searchPosition.startCol = currentSelection.endColumn; - } - result = this._findInLine(term, searchPosition, searchOptions, true); - } - } else { - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - } - - // Search from startRow - 1 to top - if (!result) { - searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols); - for (let y = startRow - 1; y >= 0; y--) { - searchPosition.startRow = y; - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - if (result) { - break; - } + const results = []; + for (let i = this._terminal.buffer.active.viewportY; i < this._terminal.buffer.active.viewportY + this._terminal.rows; i++) { + const result = this._findInLine(term, { startCol: 0, startRow: i }, searchOptions); + if (result) { + results.push(result); } } - // If we hit the top and didn't search from the very bottom wrap back down - if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) { - for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y >= startRow; y--) { - searchPosition.startRow = y; - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - if (result) { - break; - } + for (const result of results.filter(r => !!r && r.term.length)) { + const resultDecoration = this._showResultDecoration(result); + if (resultDecoration) { + // Add decoration + this._resultDecorations.push(resultDecoration); } } - - // If there is only one result, return true. - if (!result && currentSelection) return true; - - // Set selection and scroll if a result was found - return this._selectResult(result); + console.log(results); + console.log(this._resultDecorations); + return true; } /** @@ -267,7 +221,7 @@ export class SearchAddon implements ITerminalAddon { * @param isReverseSearch Whether the search should start from the right side of the terminal and search to the left. * @return The search result if it was found. */ - protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined { + protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}): ISearchResult | undefined { const terminal = this._terminal!; const row = searchPosition.startRow; const col = searchPosition.startCol; @@ -275,10 +229,6 @@ export class SearchAddon implements ITerminalAddon { // Ignore wrapped lines, only consider on unwrapped line (first row of command string). const firstLine = terminal.buffer.active.getLine(row); if (firstLine?.isWrapped) { - if (isReverseSearch) { - searchPosition.startCol += terminal.cols; - return; - } // This will iterate until we find the line start. // When we find it, we will search using the calculated start column. @@ -302,29 +252,13 @@ export class SearchAddon implements ITerminalAddon { let resultIndex = -1; if (searchOptions.regex) { const searchRegex = RegExp(searchTerm, 'g'); - let foundTerm: RegExpExecArray | null; - if (isReverseSearch) { - // This loop will get the resultIndex of the _last_ regex match in the range 0..offset - while (foundTerm = searchRegex.exec(searchStringLine.slice(0, offset))) { - resultIndex = searchRegex.lastIndex - foundTerm[0].length; - term = foundTerm[0]; - searchRegex.lastIndex -= (term.length - 1); - } - } else { - foundTerm = searchRegex.exec(searchStringLine.slice(offset)); - if (foundTerm && foundTerm[0].length > 0) { - resultIndex = offset + (searchRegex.lastIndex - foundTerm[0].length); - term = foundTerm[0]; - } + const foundTerm = searchRegex.exec(searchStringLine.slice(offset)); + if (foundTerm && foundTerm[0].length > 0) { + resultIndex = offset + (searchRegex.lastIndex - foundTerm[0].length); + term = foundTerm[0]; } } else { - if (isReverseSearch) { - if (offset - searchTerm.length >= 0) { - resultIndex = searchStringLine.lastIndexOf(searchTerm, offset - searchTerm.length); - } - } else { - resultIndex = searchStringLine.indexOf(searchTerm, offset); - } + resultIndex = searchStringLine.indexOf(searchTerm, offset); } if (resultIndex >= 0) { @@ -448,19 +382,19 @@ export class SearchAddon implements ITerminalAddon { * @param result The result to select. * @return Whethera result was selected. */ - private _selectResult(result: ISearchResult | undefined): boolean { + private _showResultDecoration(result: ISearchResult | undefined): IDecoration | undefined { const terminal = this._terminal!; - if (!result) { + if (!result || result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { terminal.clearSelection(); - return false; + return; } - terminal.select(result.col, result.row, result.size); - // If it is not in the viewport then we scroll else it just gets selected - if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { - let scroll = result.row - terminal.buffer.active.viewportY; - scroll -= Math.floor(terminal.rows / 2); - terminal.scrollLines(scroll); + // TODO: + const marker = terminal.registerMarker(undefined, result.col, result.row); + if (!marker) { + return undefined; } - return true; + const findResultDecoration = terminal.registerDecoration({ marker, width: result.size }); + findResultDecoration?.onRender((e) => console.log('rendered', e, result?.term, result?.col)); + return findResultDecoration; } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2cd4daa6..44106354 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -930,7 +930,7 @@ declare module 'xterm' { * @param cursorYOffset The y position offset of the marker from the cursor. * @returns The new marker or undefined. */ - registerMarker(cursorYOffset?: number): IMarker | undefined; + registerMarker(cursorYOffset?: number, col?: number, row?: number): IMarker | undefined; /** * @deprecated use `registerMarker` instead.