@@ -77,6 +77,34 @@ export const success = <T>(ctx: Context, value: T): Success<T> => {
7777 } ;
7878} ;
7979
80+ const LINE_CACHE_LIMIT = 8 ;
81+ // Cache per input string. `null` means the string has no '\n' (single line).
82+ const lineStartsCache = new Map < string , number [ ] | null > ( ) ;
83+
84+ const getLineStarts = ( text : string ) : number [ ] | null => {
85+ if ( lineStartsCache . has ( text ) ) {
86+ const cached = lineStartsCache . get ( text ) ?? null ;
87+ // Basic LRU bump: preserve small cache without unbounded growth.
88+ lineStartsCache . delete ( text ) ;
89+ lineStartsCache . set ( text , cached ) ;
90+ return cached ;
91+ }
92+
93+ const starts : number [ ] = [ 0 ] ;
94+ for ( let i = 0 ; i < text . length ; i ++ ) {
95+ if ( text . charCodeAt ( i ) === 10 /* '\n' */ ) starts . push ( i + 1 ) ;
96+ }
97+
98+ const value = starts . length === 1 ? null : starts ;
99+ lineStartsCache . set ( text , value ) ;
100+ if ( lineStartsCache . size > LINE_CACHE_LIMIT ) {
101+ const oldest = lineStartsCache . keys ( ) . next ( ) . value as string | undefined ;
102+ if ( oldest !== undefined ) lineStartsCache . delete ( oldest ) ;
103+ }
104+
105+ return value ;
106+ } ;
107+
80108/**
81109 * Compute line and column from context
82110 */
@@ -88,14 +116,23 @@ export const getLocation = (ctx: Context): { line: number; column: number } => {
88116 if ( index < 0 ) index = 0 ;
89117 if ( index > textLength ) index = textLength ;
90118
91- const parsedCtx = text . slice ( 0 , index ) ;
92- const parsedLines = parsedCtx . split ( "\n" ) ;
93- const line = parsedLines . length ;
119+ if ( index === 0 ) return { line : 1 , column : 1 } ;
94120
95- // `split` always returns at least one element, but keep a safe fallback.
96- const lastLine = parsedLines [ parsedLines . length - 1 ] ?? "" ;
97- const column = lastLine . length + 1 ;
121+ const starts = getLineStarts ( text ) ;
122+ if ( starts === null ) return { line : 1 , column : index + 1 } ;
123+
124+ // upper bound: number of line starts <= index => 1-based line number
125+ let lo = 0 ;
126+ let hi = starts . length ;
127+ while ( lo < hi ) {
128+ const mid = ( lo + hi ) >> 1 ;
129+ if ( starts [ mid ] ! <= index ) lo = mid + 1 ;
130+ else hi = mid ;
131+ }
98132
133+ const line = lo ;
134+ const lineStart = starts [ line - 1 ] ?? 0 ;
135+ const column = index - lineStart + 1 ;
99136 return { line, column } ;
100137} ;
101138
0 commit comments