@@ -146,9 +146,48 @@ class CsvEditorController {
146146 const oldText = this . document . getText ( ) ;
147147 const result = Papa . parse ( oldText , { dynamicTyping : false , delimiter : separator } ) ;
148148 const data = result . data as string [ ] [ ] ;
149- while ( data . length <= row ) data . push ( [ ] ) ;
150- while ( data [ row ] . length <= col ) data [ row ] . push ( '' ) ;
151- data [ row ] [ col ] = value ;
149+ const hadRows = data . length ;
150+ const hadColsAtRow = ( data [ row ] ? data [ row ] . length : 0 ) ;
151+ const wasEditingLastRow = row >= ( data . length - 1 ) ;
152+
153+ const rowExists = row < data . length ;
154+ const colExists = rowExists && col < data [ row ] . length ;
155+
156+ if ( value === '' ) {
157+ if ( ! rowExists ) {
158+ // Do not expand file with an empty virtual row
159+ this . isUpdatingDocument = false ;
160+ return ;
161+ }
162+ if ( ! colExists ) {
163+ // Do not expand row width with an empty virtual cell
164+ this . isUpdatingDocument = false ;
165+ return ;
166+ }
167+ // Existing cell: clear value
168+ data [ row ] [ col ] = '' ;
169+ } else {
170+ // Non-empty value: expand as needed and set
171+ while ( data . length <= row ) data . push ( [ ] ) ;
172+ while ( data [ row ] . length <= col ) data [ row ] . push ( '' ) ;
173+ data [ row ] [ col ] = value ;
174+ }
175+ // If we edited the (previous) last row, trim trailing empty rows recursively
176+ let trimmed = false ;
177+ if ( wasEditingLastRow ) {
178+ const isRowEmpty = ( arr : string [ ] | undefined ) => {
179+ if ( ! arr || arr . length === 0 ) return true ;
180+ for ( let i = 0 ; i < arr . length ; i ++ ) {
181+ if ( ( arr [ i ] ?? '' ) !== '' ) return false ;
182+ }
183+ return true ;
184+ } ;
185+ while ( data . length > 0 && isRowEmpty ( data [ data . length - 1 ] ) ) {
186+ data . pop ( ) ;
187+ trimmed = true ;
188+ }
189+ }
190+
152191 const newCsvText = Papa . unparse ( data , { delimiter : separator } ) ;
153192
154193 const fullRange = new vscode . Range (
@@ -163,6 +202,11 @@ class CsvEditorController {
163202 this . isUpdatingDocument = false ;
164203 console . log ( `CSV: Updated row ${ row + 1 } , column ${ col + 1 } to "${ value } "` ) ;
165204 this . currentWebviewPanel ?. webview . postMessage ( { type : 'updateCell' , row, col, value } ) ;
205+
206+ // Trigger a full re-render if structure may have changed (new row/col created)
207+ if ( trimmed || row >= hadRows || col >= hadColsAtRow ) {
208+ try { this . updateWebviewContent ( ) ; } catch ( e ) { console . error ( 'CSV: refresh failed after structural edit' , e ) ; }
209+ }
166210 }
167211
168212 private async handleSave ( ) {
@@ -394,7 +438,8 @@ class CsvEditorController {
394438 bodyData = data . slice ( offset ) ;
395439 }
396440 const visibleForWidth = headerFlag ? [ headerRow , ...bodyData ] : bodyData ;
397- const numColumns = Math . max ( ...visibleForWidth . map ( row => row . length ) , 0 ) ;
441+ let numColumns = Math . max ( ...visibleForWidth . map ( row => row . length ) , 0 ) ;
442+ if ( numColumns === 0 ) numColumns = 1 ; // ensure at least 1 column for the virtual row
398443
399444 const columnData = Array . from ( { length : numColumns } , ( _ , i ) => bodyData . map ( row => row [ i ] || '' ) ) ;
400445 const columnTypes = columnData . map ( col => this . estimateColumnDataType ( col ) ) ;
@@ -404,16 +449,18 @@ class CsvEditorController {
404449 const CHUNK_SIZE = 1000 ;
405450 const allRows = headerFlag ? bodyData : data . slice ( offset ) ;
406451 const chunks : string [ ] = [ ] ;
452+ const chunked = allRows . length > CHUNK_SIZE ;
407453
408454 if ( allRows . length > CHUNK_SIZE ) {
409455 for ( let i = CHUNK_SIZE ; i < allRows . length ; i += CHUNK_SIZE ) {
410456 const htmlChunk = allRows . slice ( i , i + CHUNK_SIZE ) . map ( ( row , localR ) => {
411457 const startAbs = headerFlag ? offset + 1 : offset ;
412458 const absRow = startAbs + i + localR ;
413- const cells = row . map ( ( cell , cIdx ) => {
414- const safe = this . escapeHtml ( cell ) ;
415- 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>` ;
416- } ) . join ( '' ) ;
459+ let cells = '' ;
460+ for ( let cIdx = 0 ; cIdx < numColumns ; cIdx ++ ) {
461+ const safe = this . escapeHtml ( row [ cIdx ] || '' ) ;
462+ cells += `<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>` ;
463+ }
417464
418465 return `<tr>${
419466 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>` : ''
@@ -438,41 +485,65 @@ class CsvEditorController {
438485 ? `<th tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; background-color: ${ isDark ? '#1e1e1e' : '#ffffff' } ; color: #888;"></th>`
439486 : ''
440487 } `;
441- headerRow . forEach ( ( cell , i ) => {
442- const safe = this . escapeHtml ( cell ) ;
488+ for ( let i = 0 ; i < numColumns ; i ++ ) {
489+ const safe = this . escapeHtml ( headerRow [ i ] || '' ) ;
443490 tableHtml += `<th tabindex="0" style="min-width: ${ Math . min ( columnWidths [ i ] || 0 , 100 ) } ch; max-width: 100ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; background-color: ${ isDark ? '#1e1e1e' : '#ffffff' } ; color: ${ columnColors [ i ] } ; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;" data-row="${ offset } " data-col="${ i } ">${ safe } </th>` ;
444- } ) ;
491+ }
445492 tableHtml += `</tr></thead><tbody>` ;
446493 bodyData . forEach ( ( row , r ) => {
447494 tableHtml += `<tr>${
448495 addSerialIndex
449496 ? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: #888;" data-row="${ offset + 1 + r } " data-col="-1">${ offset + 1 + r } </td>`
450497 : ''
451498 } `;
452- row . forEach ( ( cell , i ) => {
453- const safe = this . escapeHtml ( cell ) ;
499+ for ( let i = 0 ; i < numColumns ; i ++ ) {
500+ const safe = this . escapeHtml ( row [ i ] || '' ) ;
454501 tableHtml += `<td tabindex="0" style="min-width: ${ Math . min ( columnWidths [ i ] || 0 , 100 ) } ch; max-width: 100ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: ${ columnColors [ i ] } ; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;" data-row="${ offset + 1 + r } " data-col="${ i } ">${ safe } </td>` ;
455- } ) ;
502+ }
456503 tableHtml += `</tr>` ;
457504 } ) ;
505+ if ( ! chunked ) {
506+ const virtualAbs = offset + 1 + bodyData . length ;
507+ const idxCell = addSerialIndex ? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: #888;" data-row="${ virtualAbs } " data-col="-1">${ virtualAbs } </td>` : '' ;
508+ const dataCells = Array . from ( { length : numColumns } , ( _ , i ) => `<td tabindex="0" style="min-width: ${ Math . min ( columnWidths [ i ] || 0 , 100 ) } ch; max-width: 100ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: ${ columnColors [ i ] } ; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;" data-row="${ virtualAbs } " data-col="${ i } "></td>` ) . join ( '' ) ;
509+ tableHtml += `<tr>${ idxCell } ${ dataCells } </tr>` ;
510+ }
458511 tableHtml += `</tbody>` ;
459512 } else {
460513 tableHtml += `<tbody>` ;
461- data . slice ( offset ) . forEach ( ( row , r ) => {
514+ const nonHeaderRows = data . slice ( offset ) ;
515+ nonHeaderRows . forEach ( ( row , r ) => {
462516 tableHtml += `<tr>${
463517 addSerialIndex
464518 ? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: #888;" data-row="${ offset + r } " data-col="-1">${ r + 1 } </td>`
465519 : ''
466520 } `;
467- row . forEach ( ( cell , i ) => {
468- const safe = this . escapeHtml ( cell ) ;
521+ for ( let i = 0 ; i < numColumns ; i ++ ) {
522+ const safe = this . escapeHtml ( row [ i ] || '' ) ;
469523 tableHtml += `<td tabindex="0" style="min-width: ${ Math . min ( columnWidths [ i ] || 0 , 100 ) } ch; max-width: 100ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: ${ columnColors [ i ] } ; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;" data-row="${ offset + r } " data-col="${ i } ">${ safe } </td>` ;
470- } ) ;
524+ }
471525 tableHtml += `</tr>` ;
472526 } ) ;
527+ if ( ! chunked ) {
528+ const virtualAbs = offset + nonHeaderRows . length ;
529+ const displayIdx = nonHeaderRows . length + 1 ;
530+ const idxCell = addSerialIndex ? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: #888;" data-row="${ virtualAbs } " data-col="-1">${ displayIdx } </td>` : '' ;
531+ const dataCells = Array . from ( { length : numColumns } , ( _ , i ) => `<td tabindex="0" style="min-width: ${ Math . min ( columnWidths [ i ] || 0 , 100 ) } ch; max-width: 100ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: ${ columnColors [ i ] } ; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;" data-row="${ virtualAbs } " data-col="${ i } "></td>` ) . join ( '' ) ;
532+ tableHtml += `<tr>${ idxCell } ${ dataCells } </tr>` ;
533+ }
473534 tableHtml += `</tbody>` ;
474535 }
475536 tableHtml += `</table>` ;
537+ // If chunked, append a final chunk with the virtual row so it appears at the end
538+ if ( chunked ) {
539+ const startAbs = headerFlag ? offset + 1 : offset ;
540+ const virtualAbs = startAbs + allRows . length ;
541+ const displayIdx = headerFlag ? virtualAbs : ( allRows . length + 1 ) ;
542+ const idxCell = addSerialIndex ? `<td tabindex="0" style="min-width: 4ch; max-width: 4ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: #888;" data-row="${ virtualAbs } " data-col="-1">${ displayIdx } </td>` : '' ;
543+ const dataCells = Array . from ( { length : numColumns } , ( _ , i ) => `<td tabindex="0" style="min-width: ${ Math . min ( columnWidths [ i ] || 0 , 100 ) } ch; max-width: 100ch; border: 1px solid ${ isDark ? '#555' : '#ccc' } ; color: ${ columnColors [ i ] } ; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;" data-row="${ virtualAbs } " data-col="${ i } "></td>` ) . join ( '' ) ;
544+ const vrow = `<tr>${ idxCell } ${ dataCells } </tr>` ;
545+ chunks . push ( vrow ) ;
546+ }
476547
477548 return { tableHtml, chunksJson : JSON . stringify ( chunks ) , colorCss } ;
478549 }
0 commit comments