Skip to content

feat: add cut cells command (#7423)#8019

Merged
Light2Dark merged 5 commits into
marimo-team:mainfrom
charliegiang:charlie/command-mode-options
May 14, 2026
Merged

feat: add cut cells command (#7423)#8019
Light2Dark merged 5 commits into
marimo-team:mainfrom
charliegiang:charlie/command-mode-options

Conversation

@charliegiang

@charliegiang charliegiang commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

📝 Summary

Added the ability to cut cells in command mode

Partially addresses #7423
nits / super nits encouraged 🙂

🔍 Description of Changes

cut.cells.mp4
  • cutCells function copies cell data (code, name, config) to the clipboard
  • useDeleteManyCellsSilentCallback hook and deleteCellSilent reducer action to delete cells without pushing to the undo stack.
  • createNewCell extended to accept optional name and config parameters so that pasted cells retain their original names and configurations.

📋 Checklist

  • I have read the contributor guidelines.
  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Tests have been added for the changes made.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Pull request title is a good summary of the changes - it will be used in the release notes.
@vercel

vercel Bot commented Jan 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Apr 15, 2026 7:50am

Request Review

@github-actions

github-actions Bot commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

// NOTE: We don't support Cut yet. We can wait for feedback before implementing.
// It is a bit more complex as will need to:
// - include id, outputs, and name
// - delete the existing cell, but don't place on the undo stack

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throughout my testing this actually felt a bit unintuitive. should users be able to undo a cut?

// It is a bit more complex as will need to:
// - include id, outputs, and name
// - delete the existing cell, but don't place on the undo stack
// - don't want to invalidate downstream cells

@charliegiang charliegiang Jan 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is unavoidable in my current implementation.

What might be worth considering though is a "pending" state where we grey out the cell -> paste -> use dropCellOverCell to preserve cellId ? thoughts?

@charliegiang

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@mscolnick

Copy link
Copy Markdown
Contributor

we originally held off on cut since deleting cells can be super destructive (and cause re-runs).

what is the use-case? it is solely to move cells to another place?

@charliegiang

Copy link
Copy Markdown
Contributor Author

we originally held off on cut since deleting cells can be super destructive (and cause re-runs).

what is the use-case? it is solely to move cells to another place?

Yeah i think the main benefit would be for bulk moving cells.

How about something like this?

cut.cells.pending.mp4
  • non-destructive
  • doesn't invalidate downstream cells
  • easy to move cells far apart
  • can move multiple cells at once
@mscolnick

Copy link
Copy Markdown
Contributor

@charliegiang that is a pretty nice interaction. What do you think @manzt @Light2Dark r.e. UX?

@Light2Dark

Light2Dark commented Jan 29, 2026

Copy link
Copy Markdown
Member

That looks pretty good imo @charliegiang, I'm guessing just a styling change to mark the cell as 'cut'. And on refresh or cell runs it would be as normal. I think there should be a way to remove the cut as well (maybe Esc)

@charliegiang

Copy link
Copy Markdown
Contributor Author

That looks pretty good imo @charliegiang, I'm guessing just a styling change to mark the cell as 'cut'. And on refresh or cell runs it would be as normal. I think there should be a way to remove the cut as well (maybe Esc)

Yeah I agree, how's this?

Unmark cells for cut with esc -> esc

unmark.cell.for.cut.mp4

Undo cut -> paste cells

cut.cells.and.undo.mp4
@manzt

manzt commented Jan 30, 2026

Copy link
Copy Markdown
Collaborator

This is a really nice interaction, and I appreciate the thoughtfulness in evolving it to be non-destructive!

Yeah i think the main benefit would be for bulk moving cells.

Since the original use case mentioned is moving cells around, I just wanted to point out that we do have bulk move in "command mode." You can select multiple cells and use move up/down or send to top/bottom.

Screen.Recording.2026-01-30.at.11.38.21.AM.mov

That said, I think cut really shines for moving cells between notebooks or applications, where the clipboard-based approach makes a lot of sense.

Comment thread frontend/src/components/editor/navigation/clipboard.ts
Comment thread frontend/src/components/editor/navigation/clipboard.ts Outdated
Comment thread frontend/src/core/cells/cells.ts Outdated
Comment thread frontend/src/core/cells/cells.ts Outdated
Comment thread frontend/src/components/editor/navigation/clipboard.ts Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had activity in 30 days. It will be closed in 14 days if no further activity occurs. If this PR is still relevant, please leave a comment or push new changes to keep it open. Thank you for your contribution!

@github-actions github-actions Bot added the stale label Mar 25, 2026
@mscolnick mscolnick requested a review from Light2Dark March 30, 2026 19:57
@Light2Dark

Copy link
Copy Markdown
Member

Hi @charliegiang , sorry we did not get around to this after the review. Would you be up to fix the merge conflict and tests?

@github-actions github-actions Bot removed the stale label Mar 31, 2026
@charliegiang

Copy link
Copy Markdown
Contributor Author

@Light2Dark Yep - thanks for the reminder. I'll make time to work on it this week. 👍

@charliegiang charliegiang force-pushed the charlie/command-mode-options branch from a865a0c to 3ef6523 Compare April 14, 2026 01:32
Copilot AI review requested due to automatic review settings April 14, 2026 01:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “cut cells” command to command mode, enabling a cut-then-paste workflow that moves cells (and supports undo) while visually marking cells as pending cut.

Changes:

  • Added cutCells + pending-cut state/service, plus UI styling to indicate “pending cut”.
  • Extended clipboard payload to include cell name and config, and paste now preserves those fields.
  • Added multi-cell move + undo support via MultiColumn.moveCellsRelativeTo, MultiColumn.placeCells, and notebook history entries/labels.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
frontend/src/utils/id-tree.tsx Adds multi-cell move and placement helpers used for cut/paste and undo.
frontend/src/utils/tests/id-tree.test.ts Adds unit tests for multi-cell move behavior.
frontend/src/css/app/Cell.css Adds visual treatment for cells marked as pending cut.
frontend/src/core/hotkeys/hotkeys.ts Introduces the x hotkey for “Cut cell” in command mode.
frontend/src/core/codemirror/cells/extensions.ts Clears pending cut state when editing a cut-marked cell.
frontend/src/core/cells/utils.ts Adds dynamic undo label based on latest history entry type.
frontend/src/core/cells/pending-cut-service.ts New service/atoms/hooks for tracking pending cut cells + clipboard payload.
frontend/src/core/cells/cells.ts Adds history entry typing, move/undo support, and extends createNewCell for name/config.
frontend/src/core/cells/tests/pending-cut-service.test.tsx Adds tests for pending-cut service behavior.
frontend/src/core/cells/tests/cells.test.ts Adds tests for createNewCell name/config and move+undo scenarios.
frontend/src/components/editor/notebook-cell.tsx Applies pending-cut class to cells marked for cut.
frontend/src/components/editor/navigation/navigation.ts Wires cut hotkey and Escape-to-clear-pending-cut behavior.
frontend/src/components/editor/navigation/clipboard.ts Implements cut/paste move logic and clipboard schema including name/config.
frontend/src/components/editor/navigation/tests/navigation.test.ts Adds hotkey tests for cutting single/multiple selected cells.
frontend/src/components/editor/navigation/tests/clipboard.test.ts Adds tests for cut/copy behavior and pending-cut clearing on copy.
frontend/src/components/editor/controls/Controls.tsx Uses the new undo label atom for tooltip content.
frontend/src/components/editor/actions/useNotebookActions.tsx Uses the new undo label atom for the command palette action label.
Comment thread frontend/src/core/cells/cells.ts
Comment thread frontend/src/components/editor/navigation/clipboard.ts Outdated
Comment thread frontend/src/core/cells/pending-cut-service.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comment thread frontend/src/components/editor/cell/code/cell-editor.tsx
Comment thread frontend/src/components/editor/navigation/clipboard.ts
Comment thread frontend/src/core/cells/cells.ts
Comment on lines +740 to +755
if (last.type === "move") {
const { cellIds, placements } = last;
if (cellIds.length === 0 || placements.length !== cellIds.length) {
return { ...state, history: state.history.slice(0, -1) };
}
const toRestore = cellIds.map((id, i) => ({
id,
columnId: placements[i].columnId,
index: placements[i].index,
}));
return {
...state,
cellIds: state.cellIds.placeCells(toRestore),
history: state.history.slice(0, -1),
scrollKey: cellIds[0] ?? null,
};

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the undoDeleteCell move-undo branch, placeCells() will happily re-insert IDs even if those cells no longer exist in state.cellData (e.g., user moves cells, deletes one of the moved cells, undoes the delete which restores a new ID, then undoes the move). This can leave state.cellIds containing cell IDs that have no corresponding cellData/cellRuntime, corrupting state. Before restoring, verify every cellId still exists in state.cellData (or drop the move history entry if any are missing).

Copilot uses AI. Check for mistakes.
Comment thread frontend/src/core/cells/cells.ts

@Light2Dark Light2Dark left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's some type errors to fix

Comment thread frontend/src/components/editor/cell/code/cell-editor.tsx

@Light2Dark Light2Dark left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great!

A nice thing to add is if pasting to an empty cell, we just replace it instead of appending below.

The css change and ^ can be followed-up by us or if you'd like to tackle it.

Comment on lines +77 to +87
&.pending-cut {
opacity: 0.6;
border-style: dashed;
border-color: var(--amber-7);

.output-area,
.cm-gutters,
.cm {
background-color: var(--amber-2);
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of the amber, doesn't look great in light mode. And the dashed border, since it conflicts with the blue border. Maybe this is better?

  &.pending-cut {
    .output-area,
    .cm-gutters,
    .cm {
      background-color: var(--gray-2);
      opacity: 0.55;
    }
  }
Image

@manzt manzt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution. Looking really great! just some minor questions re: potentially vestigial code.

Comment on lines +45 to +69
function buildClipboardPayload(
cells: Array<{ code: string; name?: string; config?: CellConfig }>,
): { clipboardData: ClipboardCellData; plainText: string } {
const clipboardData: ClipboardCellData = {
cells: cells.map((cell) => ({
code: cell.code,
name: cell.name,
config: cell.config,
})),
version: "1.0",
};
const plainText = cells.map((cell) => cell.code).join("\n\n");
return { clipboardData, plainText };
}

async function writeCellsToClipboard(
clipboardData: ClipboardCellData,
plainText: string,
): Promise<void> {
const clipboardItem = new ClipboardItemBuilder()
.add(MARIMO_CELL_MIMETYPE, clipboardData)
.add("text/plain", plainText)
.build();
await navigator.clipboard.write([clipboardItem]);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, could this be encapsulated in a single function, so the call site is just:

await writeCellsToClipboard(cells);

That way we don't leak the internals out or require the caller to map these together.


// Check if we have pending cut cells (internal move)
if (pendingCutState.cellIds.size > 0) {
const pendingCellIds = [...pendingCutState.cellIds];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only see cellIds used from the pendingCutState. This function re-derives everything from pendingCutState.cellIds. I think this is the right design (just keep track of the pending ids (not the data), so we can probably remove pendingCutState.clipboardData?


interface PendingCutState {
cellIds: Set<CellId>;
clipboardData: ClipboardCellData | null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is dead state, can we remove this field?

Comment on lines +637 to +638
cellIds: [firstCellId, "1" as CellId],
targetCellId: "2" as CellId,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we have a helper made for branding types (instead of typecasts):

// frontend/src/__tests__/branded.ts
import { cellId } from "@/__tests__/branded";
Suggested change
cellIds: [firstCellId, "1" as CellId],
targetCellId: "2" as CellId,
cellIds: [firstCellId, cellId("1")],
targetCellId: cellId("2"),
@Light2Dark Light2Dark merged commit 08f05aa into marimo-team:main May 14, 2026
25 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.7-dev17

Light2Dark added a commit that referenced this pull request May 14, 2026
<img width="1055" height="321" alt="image"
src="https://github.com/user-attachments/assets/53ab00ed-4bd5-4fbf-990f-c4029b69541d"
/>

## 📝 Summary

Follow-up to #8019, which merged before a few unresolved review threads
were addressed. This PR addresses the actionable items.

Closes #

## 📋 Pre-Review Checklist

- [ ] For large changes, or changes that affect the public API: this
change was discussed or approved through an issue, on
[Discord](https://marimo.io/discord?ref=pr), or the community
[discussions](https://github.com/marimo-team/marimo/discussions) (Please
provide a link if applicable).
- [x] Any AI generated code has been reviewed line-by-line by the human
PR author, who stands by it.
- [x] Video or media evidence is provided for any visual changes
(optional). <!-- PR is more likely to be merged if evidence is provided
for changes made -->

## ✅ Merge Checklist

- [x] I have read the [contributor
guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md).
- [x] Documentation has been updated where applicable, including
docstrings for API changes.
- [x] Tests have been added for the changes made.

Made with [Cursor](https://cursor.com)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

5 participants