@@ -23,7 +23,7 @@ export function activate(context: vscode.ExtensionContext) {
2323 toggleBooleanConfig ( 'enabled' , true , 'CSV extension' )
2424 ) ,
2525 vscode . commands . registerCommand ( 'csv.toggleHeader' , ( ) =>
26- toggleBooleanConfig ( 'treatFirstRowAsHeader' , true , 'CSV treating first row as header is now' )
26+ toggleBooleanConfig ( 'treatFirstRowAsHeader' , true , 'CSV first row as header is now' )
2727 ) ,
2828 vscode . commands . registerCommand ( 'csv.toggleSerialIndex' , ( ) =>
2929 toggleBooleanConfig ( 'addSerialIndex' , false , 'CSV serial index is now' )
@@ -361,6 +361,34 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
361361 const columnTypes = columnData . map ( col => this . estimateColumnDataType ( col ) ) ;
362362 const columnColors = columnTypes . map ( ( type , i ) => this . getColumnColor ( type , isDark , i ) ) ;
363363 const columnWidths = this . computeColumnWidths ( data ) ;
364+ /* ──────────── VIRTUAL-SCROLL SUPPORT ──────────── */
365+ const CHUNK_SIZE = 1000 ; // rows per chunk
366+ const allRows = treatHeader ? bodyData : data ;
367+ const chunks : string [ ] = [ ] ;
368+
369+ if ( allRows . length > CHUNK_SIZE ) {
370+ for ( let i = CHUNK_SIZE ; i < allRows . length ; i += CHUNK_SIZE ) {
371+ const htmlChunk = allRows . slice ( i , i + CHUNK_SIZE ) . map ( ( row , localR ) => {
372+ const absRow = treatHeader ? i + localR + 1 : i + localR ;
373+ const cells = row . map ( ( cell , cIdx ) => {
374+ const safe = this . escapeHtml ( cell ) ;
375+ return `<td tabindex="0" style="min-width:${ Math . min ( columnWidths [ cIdx ] || 0 , 100 ) } ch;max-width:100ch;border:1px solid ${ isDark ?'#555' :'#ccc' } ;color:${ columnColors [ cIdx ] } ;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;" data-row="${ absRow } " data-col="${ cIdx } ">${ safe } </td>` ;
376+ } ) . join ( '' ) ;
377+
378+ return `<tr>${
379+ addSerialIndex ? `<td tabindex="0" style="min-width:4ch;max-width:4ch;border:1px solid ${ isDark ?'#555' :'#ccc' } ;color:#888;" data-row="${ absRow } " data-col="-1">${ absRow } </td>` : ''
380+ } ${ cells } </tr>`;
381+ } ) . join ( '' ) ;
382+
383+ chunks . push ( htmlChunk ) ;
384+ }
385+
386+ // keep **only** the first chunk in the initial render
387+ if ( treatHeader ) bodyData . length = CHUNK_SIZE ;
388+ else data . length = CHUNK_SIZE ;
389+ }
390+ /* ────────── END VIRTUAL-SCROLL SUPPORT ────────── */
391+
364392 let tableHtml = `<table>` ;
365393 if ( treatHeader ) {
366394 tableHtml += `<thead><tr>${
@@ -403,7 +431,11 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
403431 tableHtml += `</tbody>` ;
404432 }
405433 tableHtml += `</table>` ;
406- return `<div class="table-container">${ tableHtml } </div>` ;
434+ const chunksJson = JSON . stringify ( chunks ) ;
435+ return `
436+ <script id="__csvChunks" type="application/json">${ chunksJson } </script>
437+ <div class="table-container">${ tableHtml } </div>
438+ ` ;
407439 }
408440
409441 /**
@@ -485,6 +517,34 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
485517 let startCell = null, endCell = null, selectionMode = "cell";
486518 let editingCell = null, originalCellValue = "";
487519 const table = document.querySelector('table');
520+ /* ──────────── VIRTUAL-SCROLL LOADER ──────────── */
521+ const CHUNK_SIZE = 1000;
522+ const chunkScript = document.getElementById('__csvChunks');
523+ let csvChunks = chunkScript ? JSON.parse(chunkScript.textContent) : [];
524+
525+ if (csvChunks.length) {
526+ const scrollContainer = document.querySelector('.table-container');
527+ const tbody = table.tBodies[0];
528+
529+ const loadNextChunk = () => {
530+ if (!csvChunks.length) return;
531+ tbody.insertAdjacentHTML('beforeend', csvChunks.shift());
532+ };
533+
534+ // Use IntersectionObserver on the last row for efficiency
535+ const io = new IntersectionObserver((entries)=>{
536+ if (entries[0].isIntersecting) {
537+ loadNextChunk();
538+ // observe the new last <tr>
539+ io.observe(tbody.querySelector('tr:last-child'));
540+ }
541+ }, { root: scrollContainer, rootMargin: '0px 0px 200px 0px' });
542+
543+ // initial observation
544+ io.observe(tbody.querySelector('tr:last-child'));
545+ }
546+ /* ───────── END VIRTUAL-SCROLL LOADER ───────── */
547+
488548 const hasHeader = document.querySelector('thead') !== null;
489549 const getCellCoords = cell => ({ row: parseInt(cell.getAttribute('data-row')), col: parseInt(cell.getAttribute('data-col')) });
490550 const clearSelection = () => { currentSelection.forEach(c => c.classList.remove('selected')); currentSelection = []; };
0 commit comments