Add Match Row and Delete Row commands
Match Row: selects all rows where the cell in the current column equals the current cell's value. Selection spans first-to-last matching row across all columns. Delete Row (Cmd+Backspace): deletes every row in the current selection, or the single current row if nothing is selected. Ensures at least one empty row always remains. Co-authored-by: Shelley <shelley@exe.dev>
This commit is contained in:
parent
ab2d281aad
commit
0e68de4278
|
|
@ -98,7 +98,7 @@ func TestParseCSVString(t *testing.T) {
|
||||||
func TestGetCommands(t *testing.T) {
|
func TestGetCommands(t *testing.T) {
|
||||||
reg := NewCommandRegistry()
|
reg := NewCommandRegistry()
|
||||||
cmds := reg.GetCommands()
|
cmds := reg.GetCommands()
|
||||||
if len(cmds) != 15 {
|
if len(cmds) != 17 {
|
||||||
t.Errorf("expected 12 commands, got %d", len(cmds))
|
t.Errorf("expected 12 commands, got %d", len(cmds))
|
||||||
}
|
}
|
||||||
// Check that all have IDs
|
// Check that all have IDs
|
||||||
|
|
|
||||||
|
|
@ -33,5 +33,7 @@ func (c *CommandRegistry) GetCommands() []Command {
|
||||||
{ID: "sort-asc", Name: "Sort A-Z", Shortcut: ""},
|
{ID: "sort-asc", Name: "Sort A-Z", Shortcut: ""},
|
||||||
{ID: "sort-desc", Name: "Sort Z-A", Shortcut: ""},
|
{ID: "sort-desc", Name: "Sort Z-A", Shortcut: ""},
|
||||||
{ID: "sort-advanced", Name: "Sort Advanced", Shortcut: ""},
|
{ID: "sort-advanced", Name: "Sort Advanced", Shortcut: ""},
|
||||||
|
{ID: "match-row", Name: "Match Row", Shortcut: ""},
|
||||||
|
{ID: "delete-row", Name: "Delete Row", Shortcut: "Cmd+Backspace"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -491,6 +491,13 @@ document.addEventListener('keydown', (e) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cmd+Backspace deletes rows
|
||||||
|
if (meta && e.key === 'Backspace') {
|
||||||
|
e.preventDefault();
|
||||||
|
executeCommand('delete-row');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Delete/Backspace clears selected cells
|
// Delete/Backspace clears selected cells
|
||||||
if (e.key === 'Delete' || e.key === 'Backspace') {
|
if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -615,6 +622,8 @@ async function loadCommands() {
|
||||||
{ ID: 'sort-asc', Name: 'Sort A-Z', Shortcut: '' },
|
{ ID: 'sort-asc', Name: 'Sort A-Z', Shortcut: '' },
|
||||||
{ ID: 'sort-desc', Name: 'Sort Z-A', Shortcut: '' },
|
{ ID: 'sort-desc', Name: 'Sort Z-A', Shortcut: '' },
|
||||||
{ ID: 'sort-advanced', Name: 'Sort Advanced', Shortcut: '' },
|
{ ID: 'sort-advanced', Name: 'Sort Advanced', Shortcut: '' },
|
||||||
|
{ ID: 'match-row', Name: 'Match Row', Shortcut: '' },
|
||||||
|
{ ID: 'delete-row', Name: 'Delete Row', Shortcut: 'Cmd+Backspace' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -720,6 +729,8 @@ async function executeCommand(id) {
|
||||||
case 'sort-asc': doSort(true); break;
|
case 'sort-asc': doSort(true); break;
|
||||||
case 'sort-desc': doSort(false); break;
|
case 'sort-desc': doSort(false); break;
|
||||||
case 'sort-advanced': openSortAdvanced(); break;
|
case 'sort-advanced': openSortAdvanced(); break;
|
||||||
|
case 'match-row': doMatchRow(); break;
|
||||||
|
case 'delete-row': doDeleteRow(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -933,6 +944,59 @@ function doInsertColRight() {
|
||||||
setStatus('Inserted column right');
|
setStatus('Inserted column right');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== Match & Delete Row =====
|
||||||
|
function doMatchRow() {
|
||||||
|
const col = state.cursor.col;
|
||||||
|
const val = getCellValue(state.cursor.row, col);
|
||||||
|
const matchingRows = [];
|
||||||
|
for (let r = 0; r < state.rows.length; r++) {
|
||||||
|
if ((state.rows[r][col] || '') === val) {
|
||||||
|
matchingRows.push(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchingRows.length === 0) {
|
||||||
|
setStatus('No matching rows');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Select from first to last matching row, spanning all columns
|
||||||
|
const first = matchingRows[0];
|
||||||
|
const last = matchingRows[matchingRows.length - 1];
|
||||||
|
state.cursor = { row: first, col: 0 };
|
||||||
|
state.selection = {
|
||||||
|
startRow: first,
|
||||||
|
startCol: 0,
|
||||||
|
endRow: last,
|
||||||
|
endCol: state.headers.length - 1,
|
||||||
|
};
|
||||||
|
updateSelectionClasses();
|
||||||
|
scrollCursorIntoView();
|
||||||
|
const colName = state.headers[col] || colLabel(col);
|
||||||
|
setStatus(`${matchingRows.length} row(s) matching ${colName} = "${val}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doDeleteRow() {
|
||||||
|
const sel = normalizeSelection();
|
||||||
|
let startRow, endRow;
|
||||||
|
if (sel) {
|
||||||
|
startRow = sel.r1;
|
||||||
|
endRow = sel.r2;
|
||||||
|
} else {
|
||||||
|
startRow = state.cursor.row;
|
||||||
|
endRow = state.cursor.row;
|
||||||
|
}
|
||||||
|
const count = endRow - startRow + 1;
|
||||||
|
state.rows.splice(startRow, count);
|
||||||
|
// Ensure at least one row remains
|
||||||
|
if (state.rows.length === 0) {
|
||||||
|
state.rows.push(new Array(state.headers.length).fill(''));
|
||||||
|
}
|
||||||
|
// Adjust cursor
|
||||||
|
state.cursor.row = Math.min(startRow, state.rows.length - 1);
|
||||||
|
state.selection = null;
|
||||||
|
render();
|
||||||
|
setStatus(`Deleted ${count} row(s)`);
|
||||||
|
}
|
||||||
|
|
||||||
// ===== Sorting =====
|
// ===== Sorting =====
|
||||||
function doSort(ascending) {
|
function doSort(ascending) {
|
||||||
const col = state.cursor.col;
|
const col = state.cursor.col;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue