@@ -2950,6 +2950,11 @@ function applyHighlightsWithSearchJson(text: string, searchRanges: SearchRange[]
29502950 return result ;
29512951}
29522952
2953+ // Yield to the browser event loop so UI stays responsive during heavy work
2954+ function yieldToUI ( ) : Promise < void > {
2955+ return new Promise ( resolve => requestAnimationFrame ( ( ) => resolve ( ) ) ) ;
2956+ }
2957+
29532958function escapeHtml ( text : string ) : string {
29542959 return sanitizeText ( text )
29552960 . replace ( / & / g, '&' )
@@ -3239,6 +3244,7 @@ function renderMinimapMarkers(): void {
32393244 if ( totalLines === 0 ) return ;
32403245
32413246 const minimapHeight = minimapElement . clientHeight ;
3247+ const fragment = document . createDocumentFragment ( ) ;
32423248
32433249 // Add saved notes range markers (drawn first, behind other markers)
32443250 for ( const range of state . savedRanges ) {
@@ -3249,19 +3255,22 @@ function renderMinimapMarkers(): void {
32493255 marker . style . top = `${ top } px` ;
32503256 marker . style . height = `${ height } px` ;
32513257 marker . title = `Saved: Lines ${ range . startLine + 1 } -${ range . endLine + 1 } ` ;
3252- minimapElement . appendChild ( marker ) ;
3258+ fragment . appendChild ( marker ) ;
32533259 }
32543260
32553261 // Add bookmark markers with colors and tooltips
3256- for ( const bookmark of state . bookmarks ) {
3262+ const maxBookmarkMarkers = 500 ;
3263+ const bookmarkStep = Math . max ( 1 , Math . floor ( state . bookmarks . length / maxBookmarkMarkers ) ) ;
3264+ for ( let i = 0 ; i < state . bookmarks . length ; i += bookmarkStep ) {
3265+ const bookmark = state . bookmarks [ i ] ;
32573266 const marker = document . createElement ( 'div' ) ;
32583267 marker . className = 'minimap-bookmark' ;
32593268 marker . style . top = `${ ( bookmark . lineNumber / totalLines ) * minimapHeight } px` ;
32603269 if ( bookmark . color ) {
32613270 marker . style . backgroundColor = bookmark . color ;
32623271 }
32633272 marker . title = bookmark . label || `Bookmark: Line ${ bookmark . lineNumber + 1 } ` ;
3264- minimapElement . appendChild ( marker ) ;
3273+ fragment . appendChild ( marker ) ;
32653274 }
32663275
32673276 // Add search result markers (limit to prevent performance issues)
@@ -3272,7 +3281,7 @@ function renderMinimapMarkers(): void {
32723281 const marker = document . createElement ( 'div' ) ;
32733282 marker . className = 'minimap-search-marker' ;
32743283 marker . style . top = `${ ( result . lineNumber / totalLines ) * minimapHeight } px` ;
3275- minimapElement . appendChild ( marker ) ;
3284+ fragment . appendChild ( marker ) ;
32763285 }
32773286
32783287 // Add search config markers (colored by config)
@@ -3287,9 +3296,11 @@ function renderMinimapMarkers(): void {
32873296 marker . className = 'minimap-sc-marker' ;
32883297 marker . style . top = `${ ( r . lineNumber / totalLines ) * minimapHeight } px` ;
32893298 marker . style . backgroundColor = config . color ;
3290- minimapElement . appendChild ( marker ) ;
3299+ fragment . appendChild ( marker ) ;
32913300 }
32923301 }
3302+
3303+ minimapElement . appendChild ( fragment ) ;
32933304}
32943305
32953306function handleLogClick ( event : MouseEvent ) : void {
@@ -4375,7 +4386,7 @@ function closeSearchResultsPanel(): void {
43754386
43764387const SEARCH_RESULTS_LIST_CAP = 500 ;
43774388
4378- function renderSearchResultsList ( ) : void {
4389+ async function renderSearchResultsList ( ) : Promise < void > {
43794390 const list = elements . searchResultsList ;
43804391 const results = state . searchResults ;
43814392 const searchPattern = elements . searchInput . value ;
@@ -4391,53 +4402,59 @@ function renderSearchResultsList(): void {
43914402 ? `Showing ${ SEARCH_RESULTS_LIST_CAP } of ${ results . length } `
43924403 : `${ results . length } results` ;
43934404
4394- const fragment = document . createDocumentFragment ( ) ;
4405+ list . innerHTML = '' ;
4406+ const CHUNK_SIZE = 200 ;
43954407
4396- for ( let i = 0 ; i < displayCount ; i ++ ) {
4397- const r = results [ i ] ;
4398- const item = document . createElement ( 'div' ) ;
4399- item . className = 'search-result-item' ;
4400- if ( i === state . currentSearchIndex ) {
4401- item . classList . add ( 'current' ) ;
4402- }
4403- item . dataset . index = String ( i ) ;
4408+ for ( let chunkStart = 0 ; chunkStart < displayCount ; chunkStart += CHUNK_SIZE ) {
4409+ const chunkEnd = Math . min ( chunkStart + CHUNK_SIZE , displayCount ) ;
4410+ const fragment = document . createDocumentFragment ( ) ;
44044411
4405- const lineNum = document . createElement ( 'span' ) ;
4406- lineNum . className = 'search-result-line-num' ;
4407- lineNum . textContent = `${ r . lineNumber + 1 } ` ;
4412+ for ( let i = chunkStart ; i < chunkEnd ; i ++ ) {
4413+ const r = results [ i ] ;
4414+ const item = document . createElement ( 'div' ) ;
4415+ item . className = 'search-result-item' ;
4416+ if ( i === state . currentSearchIndex ) {
4417+ item . classList . add ( 'current' ) ;
4418+ }
4419+ item . dataset . index = String ( i ) ;
4420+
4421+ const lineNum = document . createElement ( 'span' ) ;
4422+ lineNum . className = 'search-result-line-num' ;
4423+ lineNum . textContent = `${ r . lineNumber + 1 } ` ;
4424+
4425+ const text = document . createElement ( 'span' ) ;
4426+ text . className = 'search-result-text' ;
4427+ const lineText = r . lineText || '' ;
4428+ const truncated = lineText . length > 300 ? lineText . substring ( 0 , 300 ) + '...' : lineText ;
4429+ if ( searchPattern && r . column >= 0 && r . length > 0 ) {
4430+ const before = escapeHtml ( truncated . substring ( 0 , r . column ) ) ;
4431+ const match = escapeHtml ( truncated . substring ( r . column , r . column + r . length ) ) ;
4432+ const after = escapeHtml ( truncated . substring ( r . column + r . length ) ) ;
4433+ text . innerHTML = `${ before } <mark>${ match } </mark>${ after } ` ;
4434+ } else {
4435+ text . textContent = truncated ;
4436+ }
44084437
4409- const text = document . createElement ( 'span' ) ;
4410- text . className = 'search-result-text' ;
4411- const lineText = r . lineText || '' ;
4412- const truncated = lineText . length > 300 ? lineText . substring ( 0 , 300 ) + '...' : lineText ;
4413- if ( searchPattern && r . column >= 0 && r . length > 0 ) {
4414- const before = escapeHtml ( truncated . substring ( 0 , r . column ) ) ;
4415- const match = escapeHtml ( truncated . substring ( r . column , r . column + r . length ) ) ;
4416- const after = escapeHtml ( truncated . substring ( r . column + r . length ) ) ;
4417- text . innerHTML = `${ before } <mark>${ match } </mark>${ after } ` ;
4418- } else {
4419- text . textContent = truncated ;
4438+ item . appendChild ( lineNum ) ;
4439+ item . appendChild ( text ) ;
4440+ item . addEventListener ( 'click' , ( ) => {
4441+ goToSearchResult ( i ) ;
4442+ updateSearchResultsCurrent ( ) ;
4443+ scrollSearchResultIntoView ( ) ;
4444+ } ) ;
4445+ fragment . appendChild ( item ) ;
44204446 }
44214447
4422- item . appendChild ( lineNum ) ;
4423- item . appendChild ( text ) ;
4424- item . addEventListener ( 'click' , ( ) => {
4425- goToSearchResult ( i ) ;
4426- updateSearchResultsCurrent ( ) ;
4427- scrollSearchResultIntoView ( ) ;
4428- } ) ;
4429- fragment . appendChild ( item ) ;
4448+ list . appendChild ( fragment ) ;
4449+ if ( chunkEnd < displayCount ) await yieldToUI ( ) ;
44304450 }
44314451
44324452 if ( results . length > SEARCH_RESULTS_LIST_CAP ) {
44334453 const notice = document . createElement ( 'div' ) ;
44344454 notice . className = 'search-results-cap-notice' ;
44354455 notice . textContent = `... and ${ results . length - SEARCH_RESULTS_LIST_CAP } more results` ;
4436- fragment . appendChild ( notice ) ;
4456+ list . appendChild ( notice ) ;
44374457 }
4438-
4439- list . innerHTML = '' ;
4440- list . appendChild ( fragment ) ;
44414458}
44424459
44434460function updateSearchResultsCurrent ( ) : void {
@@ -4637,7 +4654,7 @@ async function deleteSearchConfig(id: string): Promise<void> {
46374654 state . searchConfigResults . delete ( id ) ;
46384655 await window . api . searchConfigDelete ( id ) ;
46394656 renderSearchConfigsChips ( ) ;
4640- renderSearchConfigsResults ( ) ;
4657+ await renderSearchConfigsResults ( ) ;
46414658 renderVisibleLines ( ) ;
46424659}
46434660
@@ -4652,7 +4669,7 @@ async function toggleSearchConfigEnabled(id: string): Promise<void> {
46524669 // Run batch just for this one
46534670 await runSearchConfigsBatch ( ) ;
46544671 } else {
4655- renderSearchConfigsResults ( ) ;
4672+ await renderSearchConfigsResults ( ) ;
46564673 renderVisibleLines ( ) ;
46574674 }
46584675}
@@ -4661,7 +4678,7 @@ async function runSearchConfigsBatch(): Promise<void> {
46614678 const enabledConfigs = state . searchConfigs . filter ( c => c . enabled ) ;
46624679 if ( enabledConfigs . length === 0 ) {
46634680 state . searchConfigResults . clear ( ) ;
4664- renderSearchConfigsResults ( ) ;
4681+ await renderSearchConfigsResults ( ) ;
46654682 renderVisibleLines ( ) ;
46664683 return ;
46674684 }
@@ -4688,8 +4705,11 @@ async function runSearchConfigsBatch(): Promise<void> {
46884705 }
46894706
46904707 renderSearchConfigsChips ( ) ; // Update counts
4691- renderSearchConfigsResults ( ) ;
4708+ await yieldToUI ( ) ;
4709+ await renderSearchConfigsResults ( ) ;
4710+ await yieldToUI ( ) ;
46924711 renderVisibleLines ( ) ;
4712+ renderMinimapMarkers ( ) ;
46934713}
46944714
46954715function renderSearchConfigsChips ( ) : void {
@@ -4698,6 +4718,8 @@ function renderSearchConfigsChips(): void {
46984718 const addBtn = elements . btnAddSearchConfig ;
46994719 container . innerHTML = '' ;
47004720
4721+ const fragment = document . createDocumentFragment ( ) ;
4722+
47014723 for ( const config of state . searchConfigs ) {
47024724 const chip = document . createElement ( 'div' ) ;
47034725 chip . className = `search-config-chip${ config . enabled ? '' : ' disabled' } ` ;
@@ -4747,9 +4769,10 @@ function renderSearchConfigsChips(): void {
47474769 showSearchConfigContextMenu ( e , config ) ;
47484770 } ) ;
47494771
4750- container . appendChild ( chip ) ;
4772+ fragment . appendChild ( chip ) ;
47514773 }
47524774
4775+ container . appendChild ( fragment ) ;
47534776 container . appendChild ( addBtn ) ;
47544777
47554778 // Update badge
@@ -4762,7 +4785,7 @@ function renderSearchConfigsChips(): void {
47624785
47634786const SC_RESULTS_LIST_CAP = 1000 ;
47644787
4765- function renderSearchConfigsResults ( ) : void {
4788+ async function renderSearchConfigsResults ( ) : Promise < void > {
47664789 const list = elements . searchConfigsResults ;
47674790 const enabledConfigs = state . searchConfigs . filter ( c => c . enabled ) ;
47684791
@@ -4794,52 +4817,58 @@ function renderSearchConfigsResults(): void {
47944817 ? `Showing ${ SC_RESULTS_LIST_CAP } of ${ allResults . length } `
47954818 : `${ allResults . length } matches` ;
47964819
4797- const fragment = document . createDocumentFragment ( ) ;
4798-
4799- for ( let i = 0 ; i < displayCount ; i ++ ) {
4800- const r = allResults [ i ] ;
4801- const item = document . createElement ( 'div' ) ;
4802- item . className = 'sc-result-item' ;
4803-
4804- const dot = document . createElement ( 'span' ) ;
4805- dot . className = 'sc-result-dot' ;
4806- dot . style . backgroundColor = r . color ;
4807-
4808- const lineNum = document . createElement ( 'span' ) ;
4809- lineNum . className = 'sc-result-line-num' ;
4810- lineNum . textContent = `${ r . lineNumber + 1 } ` ;
4820+ list . innerHTML = '' ;
4821+ const CHUNK_SIZE = 200 ;
4822+
4823+ for ( let chunkStart = 0 ; chunkStart < displayCount ; chunkStart += CHUNK_SIZE ) {
4824+ const chunkEnd = Math . min ( chunkStart + CHUNK_SIZE , displayCount ) ;
4825+ const fragment = document . createDocumentFragment ( ) ;
4826+
4827+ for ( let i = chunkStart ; i < chunkEnd ; i ++ ) {
4828+ const r = allResults [ i ] ;
4829+ const item = document . createElement ( 'div' ) ;
4830+ item . className = 'sc-result-item' ;
4831+
4832+ const dot = document . createElement ( 'span' ) ;
4833+ dot . className = 'sc-result-dot' ;
4834+ dot . style . backgroundColor = r . color ;
4835+
4836+ const lineNum = document . createElement ( 'span' ) ;
4837+ lineNum . className = 'sc-result-line-num' ;
4838+ lineNum . textContent = `${ r . lineNumber + 1 } ` ;
4839+
4840+ const text = document . createElement ( 'span' ) ;
4841+ text . className = 'sc-result-text' ;
4842+ const lineText = r . lineText || '' ;
4843+ const truncated = lineText . length > 300 ? lineText . substring ( 0 , 300 ) + '...' : lineText ;
4844+ if ( r . column >= 0 && r . length > 0 ) {
4845+ const before = escapeHtml ( truncated . substring ( 0 , r . column ) ) ;
4846+ const match = escapeHtml ( truncated . substring ( r . column , r . column + r . length ) ) ;
4847+ const after = escapeHtml ( truncated . substring ( r . column + r . length ) ) ;
4848+ text . innerHTML = `${ before } <mark style="background:${ r . color } ;color:#000">${ match } </mark>${ after } ` ;
4849+ } else {
4850+ text . textContent = truncated ;
4851+ }
48114852
4812- const text = document . createElement ( 'span' ) ;
4813- text . className = 'sc-result-text' ;
4814- const lineText = r . lineText || '' ;
4815- const truncated = lineText . length > 300 ? lineText . substring ( 0 , 300 ) + '...' : lineText ;
4816- if ( r . column >= 0 && r . length > 0 ) {
4817- const before = escapeHtml ( truncated . substring ( 0 , r . column ) ) ;
4818- const match = escapeHtml ( truncated . substring ( r . column , r . column + r . length ) ) ;
4819- const after = escapeHtml ( truncated . substring ( r . column + r . length ) ) ;
4820- text . innerHTML = `${ before } <mark style="background:${ r . color } ;color:#000">${ match } </mark>${ after } ` ;
4821- } else {
4822- text . textContent = truncated ;
4853+ item . appendChild ( dot ) ;
4854+ item . appendChild ( lineNum ) ;
4855+ item . appendChild ( text ) ;
4856+ item . addEventListener ( 'click' , ( ) => {
4857+ goToLine ( r . lineNumber ) ;
4858+ } ) ;
4859+ fragment . appendChild ( item ) ;
48234860 }
48244861
4825- item . appendChild ( dot ) ;
4826- item . appendChild ( lineNum ) ;
4827- item . appendChild ( text ) ;
4828- item . addEventListener ( 'click' , ( ) => {
4829- goToLine ( r . lineNumber ) ;
4830- } ) ;
4831- fragment . appendChild ( item ) ;
4862+ list . appendChild ( fragment ) ;
4863+ if ( chunkEnd < displayCount ) await yieldToUI ( ) ;
48324864 }
48334865
48344866 if ( allResults . length > SC_RESULTS_LIST_CAP ) {
48354867 const notice = document . createElement ( 'div' ) ;
48364868 notice . className = 'sc-results-cap-notice' ;
48374869 notice . textContent = `Showing first ${ SC_RESULTS_LIST_CAP } of ${ allResults . length } matches` ;
4838- fragment . appendChild ( notice ) ;
4870+ list . appendChild ( notice ) ;
48394871 }
4840-
4841- list . innerHTML = '' ;
4842- list . appendChild ( fragment ) ;
48434872}
48444873
48454874function showSearchConfigContextMenu ( e : MouseEvent , config : SearchConfigDef ) : void {
0 commit comments