use-search-highlight.ts
1 import { useContext, useMemo } from 'react' 2 import StdinContext from '../components/StdinContext.js' 3 import type { DOMElement } from '../dom.js' 4 import instances from '../instances.js' 5 import type { MatchPosition } from '../render-to-screen.js' 6 7 /** 8 * Set the search highlight query on the Ink instance. Non-empty → all 9 * visible occurrences are inverted on the next frame (SGR 7, screen-buffer 10 * overlay, same damage machinery as selection). Empty → clears. 11 * 12 * This is a screen-space highlight — it matches the RENDERED text, not the 13 * source message text. Works for anything visible (bash output, file paths, 14 * error messages) regardless of where it came from in the message tree. A 15 * query that matched in source but got truncated/ellipsized in rendering 16 * won't highlight; that's acceptable — we highlight what you see. 17 */ 18 export function useSearchHighlight(): { 19 setQuery: (query: string) => void 20 /** Paint an existing DOM subtree (from the MAIN tree) to a fresh 21 * Screen at its natural height, scan. Element-relative positions 22 * (row 0 = element top). Zero context duplication — the element 23 * IS the one built with all real providers. */ 24 scanElement: (el: DOMElement) => MatchPosition[] 25 /** Position-based CURRENT highlight. Every frame writes yellow at 26 * positions[currentIdx] + rowOffset. The scan-highlight (inverse on 27 * all matches) still runs — this overlays on top. rowOffset tracks 28 * scroll; positions stay stable (message-relative). null clears. */ 29 setPositions: ( 30 state: { 31 positions: MatchPosition[] 32 rowOffset: number 33 currentIdx: number 34 } | null, 35 ) => void 36 } { 37 useContext(StdinContext) // anchor to App subtree for hook rules 38 const ink = instances.get(process.stdout) 39 return useMemo(() => { 40 if (!ink) { 41 return { 42 setQuery: () => {}, 43 scanElement: () => [], 44 setPositions: () => {}, 45 } 46 } 47 return { 48 setQuery: (query: string) => ink.setSearchHighlight(query), 49 scanElement: (el: DOMElement) => ink.scanElementSubtree(el), 50 setPositions: state => ink.setSearchPositions(state), 51 } 52 }, [ink]) 53 }