@@ -122,6 +122,9 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
122122 case 'deleteRow' :
123123 await this . deleteRow ( e . index ) ;
124124 break ;
125+ case 'sortColumn' :
126+ await this . sortColumn ( e . index , e . ascending ) ;
127+ break ;
125128 }
126129 } ) ;
127130
@@ -266,6 +269,58 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
266269 this . isUpdatingDocument = false ;
267270 this . updateWebviewContent ( ) ;
268271 }
272+ /* ───────────── NEW SORT COLUMN METHOD ───────────── */
273+ /**
274+ * Sorts the CSV rows (skipping the header if it is being treated as one)
275+ * by the given column index, ascending ⇧ or descending ⇩.
276+ */
277+ private async sortColumn ( index : number , ascending : boolean ) {
278+ this . isUpdatingDocument = true ;
279+
280+ const config = vscode . workspace . getConfiguration ( 'csv' ) ;
281+ const separator = config . get < string > ( 'separator' , ',' ) ;
282+ const treatHeader = config . get < boolean > ( 'treatFirstRowAsHeader' , true ) ;
283+
284+ const text = this . document . getText ( ) ;
285+ const result = Papa . parse ( text , { dynamicTyping : false , delimiter : separator } ) ;
286+ const rows = result . data as string [ ] [ ] ;
287+
288+ let header : string [ ] = [ ] ;
289+ let body : string [ ] [ ] = rows ;
290+
291+ if ( treatHeader && rows . length ) {
292+ header = rows [ 0 ] ;
293+ body = rows . slice ( 1 ) ;
294+ }
295+
296+ const cmp = ( a : string , b : string ) => {
297+ const na = parseFloat ( a ) , nb = parseFloat ( b ) ;
298+ if ( ! isNaN ( na ) && ! isNaN ( nb ) ) return na - nb ; // numeric compare
299+ return a . localeCompare ( b , undefined , { sensitivity : 'base' } ) ;
300+ } ;
301+
302+ body . sort ( ( r1 , r2 ) => {
303+ const diff = cmp ( r1 [ index ] ?? '' , r2 [ index ] ?? '' ) ;
304+ return ascending ? diff : - diff ;
305+ } ) ;
306+
307+ const newCsv = Papa . unparse ( treatHeader ? [ header , ...body ] : body , { delimiter : separator } ) ;
308+
309+ const fullRange = new vscode . Range (
310+ 0 , 0 ,
311+ this . document . lineCount ,
312+ this . document . lineCount ? this . document . lineAt ( this . document . lineCount - 1 ) . text . length : 0
313+ ) ;
314+
315+ const edit = new vscode . WorkspaceEdit ( ) ;
316+ edit . replace ( this . document . uri , fullRange , newCsv ) ;
317+ await vscode . workspace . applyEdit ( edit ) ;
318+
319+ this . isUpdatingDocument = false ;
320+ this . updateWebviewContent ( ) ;
321+ console . log ( `CSV: Sorted column ${ index + 1 } (${ ascending ? 'A-Z' : 'Z-A' } )` ) ;
322+ }
323+
269324
270325 /* ───────────── NEW ROW METHODS ───────────── */
271326
@@ -513,6 +568,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
513568 document.body.setAttribute('tabindex', '0'); document.body.focus();
514569 window.addEventListener('mousedown', () => document.body.focus());
515570 const vscode = acquireVsCodeApi();
571+ let lastContextIsHeader = false; // remembers whether we right-clicked a <th>
516572 let isUpdating = false, isSelecting = false, anchorCell = null, currentSelection = [];
517573 let startCell = null, endCell = null, selectionMode = "cell";
518574 let editingCell = null, originalCellValue = "";
@@ -566,8 +622,18 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
566622 contextMenu.appendChild(d);
567623 };
568624 let addedRowItems = false;
625+
626+ /* Header-only: SORT functionality */
627+ if (lastContextIsHeader) {
628+ item('Sort: A-Z', () =>
629+ vscode.postMessage({ type: 'sortColumn', index: col, ascending: true }));
630+ item('Sort: Z-A', () =>
631+ vscode.postMessage({ type: 'sortColumn', index: col, ascending: false }));
632+ }
633+
569634 /* Row section */
570635 if (!isNaN(row) && row >= 0) {
636+ if (contextMenu.children.length) divider();
571637 item('Add ROW: above', () => vscode.postMessage({ type: 'insertRow', index: row }));
572638 item('Add ROW: below', () => vscode.postMessage({ type: 'insertRow', index: row + 1 }));
573639 item('Delete ROW', () => vscode.postMessage({ type: 'deleteRow', index: row }));
@@ -581,6 +647,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
581647 item('Add COLUMN: right', () => vscode.postMessage({ type: 'insertColumn', index: col + 1 }));
582648 item('Delete COLUMN', () => vscode.postMessage({ type: 'deleteColumn', index: col }));
583649 }
650+
584651 contextMenu.style.left = x + 'px';
585652 contextMenu.style.top = y + 'px';
586653 contextMenu.style.display = 'block';
@@ -598,6 +665,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
598665 const row = parseInt(rowAttr);
599666 if ((isNaN(col) || col === -1) && (isNaN(row) || row === -1)) return;
600667 e.preventDefault();
668+ lastContextIsHeader = target.tagName === 'TH';
601669 showContextMenu(e.pageX, e.pageY, row, col);
602670 });
603671
0 commit comments