From 264dcabd95dd26546d24a636cba4d40e0028c723 Mon Sep 17 00:00:00 2001 From: jonaraphael Date: Wed, 23 Jul 2025 17:11:30 -0400 Subject: [PATCH 1/3] feat: implement shift-click range selection --- src/extension.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index fc86d1b..2d9bfdd 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -704,8 +704,33 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { table.addEventListener('mousedown', e => { if(e.target.tagName !== 'TD' && e.target.tagName !== 'TH') return; - if(editingCell){ if(e.target !== editingCell) editingCell.blur(); else return; } else clearSelection(); const target = e.target; + + // ──────── NEW: Shift+Click range selection ──────── + if ( + e.shiftKey && + anchorCell && + currentSelection.length === 1 && + !editingCell && + target.getAttribute('data-row') !== null && + target.getAttribute('data-col') !== null && + anchorCell.getAttribute('data-row') !== null && + anchorCell.getAttribute('data-col') !== null && + target.getAttribute('data-col') !== '-1' && + anchorCell.getAttribute('data-col') !== '-1' + ) { + e.preventDefault(); + selectionMode = 'cell'; + startCell = anchorCell; + endCell = target; + isSelecting = true; + selectRange(getCellCoords(startCell), getCellCoords(endCell)); + target.focus(); + return; + } + + if(editingCell){ if(e.target !== editingCell) editingCell.blur(); else return; } else clearSelection(); + /* ──────── NEW: select-all via top-left header cell ──────── */ if ( target.tagName === 'TH' && // header cell From 81bcf55502e591a2dcaf583291e8ac404eabcee0 Mon Sep 17 00:00:00 2001 From: jonaraphael Date: Wed, 23 Jul 2025 23:20:26 -0400 Subject: [PATCH 2/3] Improve shift-click range selection --- src/extension.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 2d9bfdd..9f133fb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -710,7 +710,6 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { if ( e.shiftKey && anchorCell && - currentSelection.length === 1 && !editingCell && target.getAttribute('data-row') !== null && target.getAttribute('data-col') !== null && @@ -720,12 +719,11 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { anchorCell.getAttribute('data-col') !== '-1' ) { e.preventDefault(); - selectionMode = 'cell'; - startCell = anchorCell; - endCell = target; - isSelecting = true; - selectRange(getCellCoords(startCell), getCellCoords(endCell)); - target.focus(); + selectRange( + getCellCoords(anchorCell), + getCellCoords(target) + ); + anchorCell.focus(); return; } From d77d12cc75b280d89449b0f203af0c7f6562625c Mon Sep 17 00:00:00 2001 From: jonaraphael Date: Wed, 23 Jul 2025 23:31:45 -0400 Subject: [PATCH 3/3] Enable shift+arrow range selection --- src/extension.ts | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 9f133fb..45cc3ca 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -602,7 +602,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { document.body.setAttribute('tabindex', '0'); document.body.focus(); const vscode = acquireVsCodeApi(); let lastContextIsHeader = false; // remembers whether we right-clicked a - let isUpdating = false, isSelecting = false, anchorCell = null, currentSelection = []; + let isUpdating = false, isSelecting = false, anchorCell = null, rangeEndCell = null, currentSelection = []; let startCell = null, endCell = null, selectionMode = "cell"; let editingCell = null, originalCellValue = ""; const table = document.querySelector('table'); @@ -723,6 +723,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { getCellCoords(anchorCell), getCellCoords(target) ); + rangeEndCell = target; anchorCell.focus(); return; } @@ -745,7 +746,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { /* ──────── END NEW BLOCK ──────── */ selectionMode = (target.tagName === 'TH') ? "column" : (target.getAttribute('data-col') === '-1' ? "row" : "cell"); - startCell = target; endCell = target; isSelecting = true; e.preventDefault(); + startCell = target; endCell = target; rangeEndCell = target; isSelecting = true; e.preventDefault(); target.focus(); }); table.addEventListener('mousemove', e => { @@ -754,6 +755,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { if(selectionMode === "cell"){ if(target.tagName === 'TD' || target.tagName === 'TH'){ endCell = target; + rangeEndCell = target; selectRange(getCellCoords(startCell), getCellCoords(endCell)); } } else if(selectionMode === "column"){ @@ -762,6 +764,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { target = table.querySelector('thead th[data-col="'+col+'"]') || target; } endCell = target; + rangeEndCell = target; const startCol = parseInt(startCell.getAttribute('data-col')); const endCol = parseInt(endCell.getAttribute('data-col')); selectFullColumnRange(startCol, endCol); @@ -771,6 +774,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { target = table.querySelector('td[data-col="-1"][data-row="'+row+'"]') || target; } endCell = target; + rangeEndCell = target; const startRow = parseInt(startCell.getAttribute('data-row')); const endRow = parseInt(endCell.getAttribute('data-row')); selectFullRowRange(startRow, endRow); @@ -781,16 +785,20 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { isSelecting = false; if(selectionMode === "cell"){ if(startCell === endCell){ - clearSelection(); startCell.classList.add('selected'); currentSelection.push(startCell); anchorCell = startCell; - } else { anchorCell = startCell; } + clearSelection(); + startCell.classList.add('selected'); + currentSelection.push(startCell); + } + anchorCell = startCell; + rangeEndCell = endCell; } else if(selectionMode === "column"){ const startCol = parseInt(startCell.getAttribute('data-col')); const endCol = parseInt(endCell.getAttribute('data-col')); - selectFullColumnRange(startCol, endCol); anchorCell = startCell; + selectFullColumnRange(startCol, endCol); anchorCell = startCell; rangeEndCell = endCell; } else if(selectionMode === "row"){ const startRow = parseInt(startCell.getAttribute('data-row')); const endRow = parseInt(endCell.getAttribute('data-row')); - selectFullRowRange(startRow, endRow); anchorCell = startCell; + selectFullRowRange(startRow, endRow); anchorCell = startCell; rangeEndCell = endCell; } }); const selectRange = (start, end) => { @@ -910,6 +918,28 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { } /* ──────── ARROW KEY NAVIGATION ──────── */ + if (!editingCell && anchorCell && e.shiftKey && ['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(e.key)) { + const { row, col } = getCellCoords(rangeEndCell || anchorCell); + let targetRow = row, targetCol = col; + switch(e.key){ + case 'ArrowUp': targetRow = row - 1; break; + case 'ArrowDown': targetRow = row + 1; break; + case 'ArrowLeft': targetCol = col - 1; break; + case 'ArrowRight':targetCol = col + 1; break; + } + if(targetRow < 0 || targetCol < 0) return; + const tag = (hasHeader && targetRow === 0 ? 'th' : 'td'); + const nextCell = table.querySelector(\`\${tag}[data-row="\${targetRow}"][data-col="\${targetCol}"]\`); + if(nextCell){ + e.preventDefault(); + rangeEndCell = nextCell; + selectRange(getCellCoords(anchorCell), getCellCoords(rangeEndCell)); + anchorCell.focus({preventScroll:true}); + rangeEndCell.scrollIntoView({ block:'nearest', inline:'nearest', behavior:'smooth' }); + } + return; + } + if (!editingCell && anchorCell && ['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(e.key)) { const { row, col } = getCellCoords(anchorCell); let targetRow = row, targetCol = col; @@ -928,6 +958,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider { nextCell.classList.add('selected'); currentSelection.push(nextCell); anchorCell = nextCell; + rangeEndCell = nextCell; nextCell.focus({preventScroll:true}); nextCell.scrollIntoView({ block:'nearest', inline:'nearest', behavior:'smooth' }); }