import { CellWidth, cellAtIndex, type Screen, type StylePool, setCellStyleId, } from './screen.js'; /** Invert every visible occurrence of `query` in the screen buffer. */ export function applySearchHighlight( screen: Screen, query: string, stylePool: StylePool, ): boolean { if (!query) return false; const needle = query.toLowerCase(); const needleLen = needle.length; const width = screen.width; const height = screen.height; const { noSelect } = screen; let highlighted = false; for (let row = 0; row < height; row++) { const rowOffset = row * width; let lowerText = ''; const colByCellIndex: number[] = []; const codeUnitToCell: number[] = []; for (let col = 0; col < width; col++) { const idx = rowOffset + col; const cell = cellAtIndex(screen, idx); if ( cell.width === CellWidth.SpacerTail || cell.width === CellWidth.SpacerHead || noSelect[idx] === 1 ) { continue; } const lower = cell.char.toLowerCase(); const cellIndex = colByCellIndex.length; for (let i = 0; i < lower.length; i++) { codeUnitToCell.push(cellIndex); } lowerText += lower; colByCellIndex.push(col); } let pos = lowerText.indexOf(needle); while (pos >= 0) { highlighted = true; const startCell = codeUnitToCell[pos]!; const endCell = codeUnitToCell[pos + needleLen - 1]!; for (let cellIdx = startCell; cellIdx <= endCell; cellIdx++) { const col = colByCellIndex[cellIdx]!; const cell = cellAtIndex(screen, rowOffset + col); setCellStyleId(screen, col, row, stylePool.withInverse(cell.styleId)); } // Advance past the match (not by one) — `aaa` should yield one // highlight starting at index 0, not two starting at 0 and 1, so // a cell isn't double-inverted (which would cancel the invert). pos = lowerText.indexOf(needle, pos + needleLen); } } return highlighted; }