@@ -170,6 +170,7 @@ if (csvChunks.length) {
170170 const html = csvChunks . shift ( ) ;
171171 tbody . insertAdjacentHTML ( 'beforeend' , html ) ;
172172 applySizeStateToRenderedCells ( ) ;
173+ window . dispatchEvent ( new Event ( 'csvChunkLoaded' ) ) ;
173174 } finally {
174175 loading = false ;
175176 }
@@ -880,8 +881,13 @@ let findMatches = [];
880881let currentMatchIndex = - 1 ;
881882let findDebounce = null ;
882883let findFocusBeforeOpen = null ;
884+ let findRequestSeq = 0 ;
885+ let latestFindRequestId = 0 ;
886+ const pendingFindRequests = new Map ( ) ;
887+ let findMatchKeySet = new Set ( ) ;
883888
884889const escapeRegexLiteral = value => value . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
890+ const getFindMatchKey = ( row , col ) => `${ row } :${ col } ` ;
885891const isFindWidgetTarget = target => {
886892 const el = getElementTarget ( target ) ;
887893 return ! ! ( el && el . closest ( '#findReplaceWidget' ) ) ;
@@ -924,15 +930,6 @@ const updateFindControls = () => {
924930 replaceOne . disabled = ! hasQuery || ! hasMatches ;
925931 replaceAll . disabled = ! hasQuery || ! hasMatches ;
926932} ;
927- const loadAllChunksForFind = ( ) => {
928- if ( typeof window . __csvLoadNextChunk !== 'function' || ! csvChunks || csvChunks . length === 0 ) {
929- return ;
930- }
931- let guard = 50000 ;
932- while ( csvChunks . length > 0 && guard -- > 0 ) {
933- window . __csvLoadNextChunk ( ) ;
934- }
935- } ;
936933const getFindPattern = ( ) => {
937934 const query = findInput . value ;
938935 if ( ! query ) return null ;
@@ -952,10 +949,40 @@ const buildFindRegex = global => {
952949 return null ;
953950 }
954951} ;
955- const getFindableCells = ( ) => {
956- return Array . from ( table . querySelectorAll ( 'td[data-col], th[data-col]' ) ) . filter ( cell => {
952+ const getRenderedFindCells = ( ) => {
953+ return Array . from ( table . querySelectorAll ( 'td[data-col], th[data-col]' ) ) ;
954+ } ;
955+ const getRenderedCellByCoords = ( row , col ) => {
956+ return table . querySelector ( `td[data-row="${ row } "][data-col="${ col } "], th[data-row="${ row } "][data-col="${ col } "]` ) ;
957+ } ;
958+ const ensureRenderedCellByCoords = ( row , col ) => {
959+ let cell = getRenderedCellByCoords ( row , col ) ;
960+ if ( cell ) {
961+ return cell ;
962+ }
963+ if ( typeof window . __csvLoadNextChunk !== 'function' ) {
964+ return null ;
965+ }
966+ let guard = 50000 ;
967+ while ( ! cell && csvChunks && csvChunks . length > 0 && guard -- > 0 ) {
968+ window . __csvLoadNextChunk ( ) ;
969+ cell = getRenderedCellByCoords ( row , col ) ;
970+ }
971+ return cell ;
972+ } ;
973+ const applyFindHighlightsToRendered = ( ) => {
974+ if ( ! findMatchKeySet . size ) {
975+ return ;
976+ }
977+ getRenderedFindCells ( ) . forEach ( cell => {
978+ const row = parseInt ( cell . getAttribute ( 'data-row' ) || 'NaN' , 10 ) ;
957979 const col = parseInt ( cell . getAttribute ( 'data-col' ) || 'NaN' , 10 ) ;
958- return ! Number . isNaN ( col ) && col >= 0 ;
980+ if ( Number . isNaN ( row ) || Number . isNaN ( col ) || col < 0 ) {
981+ return ;
982+ }
983+ if ( findMatchKeySet . has ( getFindMatchKey ( row , col ) ) ) {
984+ cell . classList . add ( 'highlight' ) ;
985+ }
959986 } ) ;
960987} ;
961988const setActiveFindMatch = ( index , shouldScroll = true ) => {
@@ -968,8 +995,10 @@ const setActiveFindMatch = (index, shouldScroll = true) => {
968995 }
969996 const normalized = ( ( index % findMatches . length ) + findMatches . length ) % findMatches . length ;
970997 currentMatchIndex = normalized ;
971- const cell = findMatches [ currentMatchIndex ] ;
998+ const match = findMatches [ currentMatchIndex ] ;
999+ const cell = match ? ensureRenderedCellByCoords ( match . row , match . col ) : null ;
9721000 if ( cell ) {
1001+ cell . classList . add ( 'highlight' ) ;
9731002 cell . classList . add ( 'active-match' ) ;
9741003 if ( shouldScroll ) {
9751004 cell . scrollIntoView ( { block : 'center' , inline : 'center' , behavior : 'smooth' } ) ;
@@ -981,8 +1010,13 @@ const setActiveFindMatch = (index, shouldScroll = true) => {
9811010const runFind = ( preserveIndex = false ) => {
9821011 const query = findInput . value ;
9831012 const priorIndex = currentMatchIndex ;
1013+ const requestId = ++ findRequestSeq ;
1014+ latestFindRequestId = requestId ;
1015+ pendingFindRequests . set ( requestId , { preserveIndex, priorIndex } ) ;
1016+
9841017 clearFindHighlights ( ) ;
9851018 findMatches = [ ] ;
1019+ findMatchKeySet = new Set ( ) ;
9861020 currentMatchIndex = - 1 ;
9871021 findReplaceState . invalidRegex = false ;
9881022 if ( ! query ) {
@@ -991,32 +1025,18 @@ const runFind = (preserveIndex = false) => {
9911025 return ;
9921026 }
9931027
994- loadAllChunksForFind ( ) ;
995- const regex = buildFindRegex ( false ) ;
996- if ( ! regex ) {
997- findReplaceState . invalidRegex = true ;
998- updateFindStatus ( ) ;
999- updateFindControls ( ) ;
1000- return ;
1001- }
1002-
1003- getFindableCells ( ) . forEach ( cell => {
1004- regex . lastIndex = 0 ;
1005- if ( regex . test ( cell . innerText || '' ) ) {
1006- findMatches . push ( cell ) ;
1007- cell . classList . add ( 'highlight' ) ;
1028+ vscode . postMessage ( {
1029+ type : 'findMatches' ,
1030+ requestId,
1031+ query,
1032+ options : {
1033+ matchCase : findReplaceState . matchCase ,
1034+ wholeWord : findReplaceState . wholeWord ,
1035+ regex : findReplaceState . regex
10081036 }
10091037 } ) ;
1010-
1011- if ( findMatches . length > 0 ) {
1012- const nextIndex = preserveIndex && priorIndex >= 0
1013- ? Math . min ( priorIndex , findMatches . length - 1 )
1014- : 0 ;
1015- setActiveFindMatch ( nextIndex ) ;
1016- } else {
1017- updateFindStatus ( ) ;
1018- updateFindControls ( ) ;
1019- }
1038+ updateFindStatus ( ) ;
1039+ updateFindControls ( ) ;
10201040} ;
10211041const scheduleFind = ( preserveIndex = false ) => {
10221042 if ( findDebounce ) clearTimeout ( findDebounce ) ;
@@ -1049,41 +1069,44 @@ const replaceInText = (text, replaceAllMatches) => {
10491069 }
10501070 return text . replace ( regex , matched => preserveReplacementCase ( replacementText , matched ) ) ;
10511071} ;
1052- const commitCellReplacement = ( cell , value ) => {
1053- cell . textContent = value ;
1054- const { row, col } = getCellCoords ( cell ) ;
1055- vscode . postMessage ( { type : 'editCell' , row, col, value } ) ;
1056- } ;
10571072const replaceCurrentMatch = ( ) => {
10581073 if ( ! findMatches . length || findReplaceState . invalidRegex ) return ;
1059- const cell = findMatches [ currentMatchIndex ] ;
1060- if ( ! cell || ! cell . isConnected ) {
1074+ const match = findMatches [ currentMatchIndex ] ;
1075+ if ( ! match ) {
10611076 runFind ( true ) ;
10621077 return ;
10631078 }
1064- const original = cell . innerText || '' ;
1079+ const original = String ( match . value ?? '' ) ;
10651080 const next = replaceInText ( original , false ) ;
10661081 if ( next === original ) {
10671082 navigateFind ( false ) ;
10681083 return ;
10691084 }
1070- commitCellReplacement ( cell , next ) ;
1085+ const cell = ensureRenderedCellByCoords ( match . row , match . col ) ;
1086+ if ( cell ) {
1087+ cell . textContent = next ;
1088+ }
1089+ vscode . postMessage ( { type : 'editCell' , row : match . row , col : match . col , value : next } ) ;
10711090 runFind ( true ) ;
10721091} ;
10731092const replaceAllMatches = ( ) => {
1074- if ( findReplaceState . invalidRegex || ! findInput . value ) return ;
1075- runFind ( false ) ;
1093+ if ( findReplaceState . invalidRegex || ! findInput . value || ! findMatches . length ) return ;
1094+ const seen = new Set ( ) ;
10761095 if ( ! findMatches . length ) return ;
1077- const unique = [ ...new Set ( findMatches ) ] ;
10781096 const replacements = [ ] ;
1079- unique . forEach ( cell => {
1080- if ( ! cell || ! cell . isConnected ) return ;
1081- const original = cell . innerText || '' ;
1097+ findMatches . forEach ( match => {
1098+ if ( ! match ) return ;
1099+ const key = getFindMatchKey ( match . row , match . col ) ;
1100+ if ( seen . has ( key ) ) return ;
1101+ seen . add ( key ) ;
1102+ const original = String ( match . value ?? '' ) ;
10821103 const next = replaceInText ( original , true ) ;
10831104 if ( next !== original ) {
1084- cell . textContent = next ;
1085- const { row, col } = getCellCoords ( cell ) ;
1086- replacements . push ( { row, col, value : next } ) ;
1105+ const cell = getRenderedCellByCoords ( match . row , match . col ) ;
1106+ if ( cell ) {
1107+ cell . textContent = next ;
1108+ }
1109+ replacements . push ( { row : match . row , col : match . col , value : next } ) ;
10871110 }
10881111 } ) ;
10891112 if ( replacements . length > 0 ) {
@@ -1107,6 +1130,8 @@ const closeFindReplace = () => {
11071130 clearTimeout ( findDebounce ) ;
11081131 findDebounce = null ;
11091132 }
1133+ pendingFindRequests . clear ( ) ;
1134+ latestFindRequestId = 0 ;
11101135 findReplaceState . open = false ;
11111136 findReplaceWidget . classList . remove ( 'open' ) ;
11121137 hideFindOverflowMenu ( ) ;
@@ -1201,6 +1226,19 @@ document.addEventListener('mousedown', e => {
12011226syncFindToggleUi ( ) ;
12021227updateFindStatus ( ) ;
12031228updateFindControls ( ) ;
1229+ window . addEventListener ( 'csvChunkLoaded' , ( ) => {
1230+ if ( ! findReplaceState . open || findMatches . length === 0 ) {
1231+ return ;
1232+ }
1233+ applyFindHighlightsToRendered ( ) ;
1234+ if ( currentMatchIndex >= 0 && currentMatchIndex < findMatches . length ) {
1235+ const active = findMatches [ currentMatchIndex ] ;
1236+ const cell = getRenderedCellByCoords ( active . row , active . col ) ;
1237+ if ( cell ) {
1238+ cell . classList . add ( 'active-match' ) ;
1239+ }
1240+ }
1241+ } ) ;
12041242
12051243// Capture-phase handler to intercept Cmd/Ctrl + Arrow and move to extremes
12061244document . addEventListener ( 'keydown' , e => {
@@ -1660,6 +1698,46 @@ window.addEventListener('message', event => {
16601698 if ( findReplaceState . open && findInput . value ) {
16611699 scheduleFind ( true ) ;
16621700 }
1701+ } else if ( message . type === 'findMatchesResult' ) {
1702+ if ( ! findReplaceState . open ) {
1703+ return ;
1704+ }
1705+ const requestId = Number ( message . requestId ) ;
1706+ const requestState = pendingFindRequests . get ( requestId ) ;
1707+ pendingFindRequests . delete ( requestId ) ;
1708+ if ( ! Number . isInteger ( requestId ) || requestId !== latestFindRequestId ) {
1709+ return ;
1710+ }
1711+
1712+ findReplaceState . invalidRegex = ! ! message . invalidRegex ;
1713+ findMatches = Array . isArray ( message . matches )
1714+ ? message . matches
1715+ . map ( raw => {
1716+ const row = Number ( raw ?. row ) ;
1717+ const col = Number ( raw ?. col ) ;
1718+ if ( ! Number . isInteger ( row ) || row < 0 || ! Number . isInteger ( col ) || col < 0 ) {
1719+ return null ;
1720+ }
1721+ return { row, col, value : String ( raw ?. value ?? '' ) } ;
1722+ } )
1723+ . filter ( Boolean )
1724+ : [ ] ;
1725+ findMatchKeySet = new Set ( findMatches . map ( match => getFindMatchKey ( match . row , match . col ) ) ) ;
1726+
1727+ clearFindHighlights ( ) ;
1728+ applyFindHighlightsToRendered ( ) ;
1729+ if ( findMatches . length > 0 ) {
1730+ const preserveIndex = ! ! requestState ?. preserveIndex ;
1731+ const priorIndex = Number . isInteger ( requestState ?. priorIndex ) ? requestState . priorIndex : - 1 ;
1732+ const nextIndex = preserveIndex && priorIndex >= 0
1733+ ? Math . min ( priorIndex , findMatches . length - 1 )
1734+ : 0 ;
1735+ setActiveFindMatch ( nextIndex ) ;
1736+ } else {
1737+ currentMatchIndex = - 1 ;
1738+ updateFindStatus ( ) ;
1739+ updateFindControls ( ) ;
1740+ }
16631741 }
16641742} ) ;
16651743
0 commit comments