Skip to content

Commit d74a7bd

Browse files
committed
Manage large files by loading 1000 rows at a time
1 parent 951ea85 commit d74a7bd

File tree

2 files changed

+64
-4
lines changed

2 files changed

+64
-4
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
],
3838
"commands": [
3939
{ "command": "csv.toggleExtension", "title": "CSV: Toggle Extension On/Off" },
40-
{ "command": "csv.toggleHeader", "title": "CSV: Toggle Treating First Row as Header" },
41-
{ "command": "csv.toggleSerialIndex", "title": "CSV: Toggle Adding Serial Index Column" },
40+
{ "command": "csv.toggleHeader", "title": "CSV: Toggle First Row as Header" },
41+
{ "command": "csv.toggleSerialIndex", "title": "CSV: Toggle Serial Index Column" },
4242
{ "command": "csv.changeSeparator", "title": "CSV: Change CSV Separator" }
4343
],
4444
"configuration": {

src/extension.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)