Edit files and diffs
Enable a full-featured yet lightweight editor that lazy-loads when needed on top of any File or FileDiff. All the ergonomics and customization of @pierre/diffs, with everything you need to edit in place.
Live editing
Edit mode (experimental) makes any code surface—File or FileDiff—editable in place. Toggle between a read-only Review and a live Edit, switch the surface between a file and a diff, and render the diff unified or side-by-side split. Start typing in the code below and it updates as you edit.
1234567891011121314151617181920212223242526272829303132export interface DebounceOptions { waitMs: number; trailing?: boolean;}
export function debounce<Args extends unknown[]>( fn: (...args: Args) => void, options: DebounceOptions,) { let timer: ReturnType<typeof setTimeout> | undefined;
const debounced = (...args: Args) => { if (timer != null) { clearTimeout(timer); }
timer = setTimeout(() => { timer = undefined; if (options.trailing !== false) { fn(...args); } }, options.waitMs); };
debounced.cancel = () => { clearTimeout(timer); timer = undefined; };
return debounced;}
Selection actions
Select any text to reveal a floating popover, anchored to the selection and rendered with renderSelectionAction(). Place any number of actions inside—here, an editor-style Add to chat sends the selected snippet to the panel on the right, while a secondary action copies it.
12345678910111213141516171819const greeting = 'Welcome back'const farewell = 'See you soon'const errorText = 'Something went wrong'
type Banner = { title: string; tone: 'info' | 'error' }
function renderBanner(name: string): Banner { const title = greeting + ', ' + name + '!' return { title, tone: 'info' }}
function renderError(): Banner { return { title: errorText, tone: 'error' }}
function renderFooter(year: number) { return farewell + ' · © ' + year}
Annotate code with markers
Use editor.setMarkers() to inject inline context into your code for linter, formatting, and more. Includes support for severity-aware underlines and hover popovers. Hover over markers (shown with wavy, colored underlines) in the example below.
123456789101112131415161718192021// TODO: validate items and taxRate before summingfunction calculateTotal(items, taxRate) { var total = 0 for (var i = 0; i < items.length; i++) { total += items[i].price }
let tax = total * taxRate console.log('subtotal', total)
if (total == 0) { return null }
return { subtotal: total, tax, grandTotal: total + tax, }}
Find and replace
Find strings across files with Cmd/Ctrl-F on any File or FileDiff. Find and replace with Cmd-Opt-F(Mac) or Ctrl-Alt-F(Linux/Windows). The search panel below is open—type a query to highlight matches, jump between them with Enter or its arrows, and toggle case, whole-word, or regex as you go.
12345678910111213141516type User = { id: string; name: string; email: string;};
function formatUser(user: User) { const name = user.name.trim(); const email = user.email.toLowerCase(); return { id: user.id, name, email };}
export function getUsers(users: User[]) { return users.map(formatUser);}
Undo history
Edits land on a structure-aware undo stack out of the box. Walk it with keyboard shortcuts and the toolbar below, or drive it in code with editor.undo(), editor.redo(), and editor.applyEdits(). The example loads with a short refactor already applied across several commits.
12345678910111213141516171819function calculateCart(items) { var total = 0 for (var i = 0; i < items.length; i++) { total = total + items[i].price * items[i].qty }
var discount = 0 if (total > 100) { discount = total * 0.1 }
var shipping = 5 if (total > 50) { shipping = 0 }
return total - discount + shipping}
- Type the signature
- Declare the CartItem type
- Sum items with reduce
- Inline the discount
- Inline the shipping
- Add sales tax
- Round to cents
Keyboard shortcuts
Browse every default key binding and search by shortcut, action, or command. Switch to the editable JSON view to explore the keymap format used to customize editor commands.
| Shortcut | Command | Action |
|---|---|---|
| All platforms · 22 bindings | ||
| Tab | indent | Indent line or selection |
| ShiftTab | outdent | Outdent line or selection |
| Cmd/Ctrl[ | indentLess | Decrease indentation |
| Cmd/Ctrl] | indentMore | Increase indentation |
| Cmd/CtrlZ | undo | Undo |
| Cmd/CtrlShiftZ | redo | Redo |
| Cmd/CtrlA | selectAll | Select all |
| Cmd/CtrlD | findNextMatch | Find next match of the selection |
| Cmd/CtrlF | openSearchPanel | Open search |
| Cmd/CtrlAltF | openSearchReplacePanel | Open search and replace |
| Alt↑ | moveLineUp | Move selected line(s) up |
| Alt↓ | moveLineDown | Move selected line(s) down |
| ShiftAlt↑ | copyLineUp | Copy selected line(s) up |
| ShiftAlt↓ | copyLineDown | Copy selected line(s) down |
| Esc | simplifySelection | Collapse to a single cursor |
| Cmd/CtrlEnter | insertBlankLine | Insert a blank line |
| Cmd/Ctrl/ | toggleComment | Toggle line comment |
| ShiftAltA | toggleBlockComment | Toggle block comment |
| Cmd/CtrlHome | moveCursorToDocStart | Move cursor to document start |
| Cmd/CtrlEnd | moveCursorToDocEnd | Move cursor to document end |
| Cmd/CtrlShiftHome | expandSelectionDocStart | Extend selection to document start |
| Cmd/CtrlShiftEnd | expandSelectionDocEnd | Extend selection to document end |
| macOS · 7 bindings | ||
| CtrlK | deleteHardLineForward | Delete to the end of the line |
| CtrlAltP | moveLineUp | Move selected line(s) up |
| CtrlAltN | moveLineDown | Move selected line(s) down |
| Cmd↑ | moveCursorToDocStart | Move cursor to document start |
| Cmd↓ | moveCursorToDocEnd | Move cursor to document end |
| CmdShift↑ | expandSelectionDocStart | Extend selection to document start |
| CmdShift↓ | expandSelectionDocEnd | Extend selection to document end |
| Linux · 3 bindings | ||
| CtrlY | redo | Redo |
| CtrlAltP | moveLineUp | Move selected line(s) up |
| CtrlAltN | moveLineDown | Move selected line(s) down |
| Windows · 1 binding | ||
| CtrlY | redo | Redo |
And everything else you need…
The demos above cover the headline features. Here's the rest of what edit mode gives you for free.
Editing
- Files & diffs
- Edit a
File,FileDiff,MultiFileDiff, orPatchDiff; the new-file side of a diff re-tokenizes as you type. - Multiple cursors
- Cmd/Ctrl-click adds carets; Alt/Option-drag starts a fresh column selection; edits apply to every range and overlaps merge.
- Smart indentation
- Indent or outdent whole selections, with tab vs. space inferred from each line's existing indentation.
- Bracket matching
- Highlight matching bracket pairs across code as you type with the
matchBracketsoption. - Auto-surround
- Wrap a selection in quotes or brackets by typing the opening character, tunable with the
autoSurroundoption. - Move lines
- Shift lines or selections up and down with
Alt-↑/↓.
Rendering
- Works with CodeView
- Edit virtualized
CodeViewinstances withedit: true. React uses the nearestEditProvider; vanilla usesCodeViewOptions.createEditor. Editors persist as files scroll in and out. - Virtualized files
- Use
VirtualizedFileandVirtualizedFileDiffto edit massive files; off-screen lines render on demand. - Themes & color modes
- Tokens and editor chrome follow the surface theme, re-tokenizing live when you switch themes or toggle light and dark.
- UI adapts to container
- Container queries reflow find & replace panel and marker popovers at narrow widths for a smoother experience, no matter the layout.
- Change & focus events
- React to edits and focus changes with the
onChange,onFocus, andonBlurcallbacks. - Line wrapping
- Carets, selections, and matches render correctly across wrapped visual lines.
Integration & delivery
- Diff annotations
- Editable-side annotations follow structural edits and history, including line merges; read-only old-file-side annotations stay fixed.
- SSR & hydration
- Hydrate from prerendered, already-highlighted HTML with no flash.
- Mobile & a11y
- Native
contentEditablewithrole="textbox"; autocorrect, spellcheck, and capitalization off. - Lazy-loadable
- Standalone
@pierre/diffs/editentry point—import it only when editing begins. - Custom clipboard
- Provide your own
clipboardreader—handy for native copy/paste in Electron apps.
With love from The Pierre Computer Company
Collectively, our team brings over 150 years of expertise designing, building, and scaling the world's largest distributed systems at Cloudflare, Coinbase, Discord, GitHub, Reddit, Stripe, X, and others.