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.
Editor mode (experimental) makes any code surface—File or FileDiff—editable in place. Switch from Review (read-only) to Edit, then start typing in the code below and it updates as you edit. Select text to try the custom Selection Action widget.
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;}
Select any text to reveal the gutter icon, then click it to open a custom widget rendered with renderSelectionAction(). Run a transform on the selection—here, wrap a string for translation or shout it in caps—or drive the same wrap from the toolbar, then reset the surface to its original source.
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}
Use editor.setMarkers() to inject inline context into your code for linter, formatting, and more. Includes support for severity-aware underlines and hover popups. Hover over markers with wavy underlines below to see an example.
1234567891011121314151617181920function 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, }}
Open the search panel with Cmd/Ctrl-F on any File or FileDiff to find and replace. The example below shows the search panel pre-filled—press Enter or use its arrows to jump between matches, 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);}
Every edit lands on a structure-aware undo stack. The example below loads with a short refactor already applied across several commits—use the toolbar controls (or Cmd/Ctrl-Z and Shift to redo) to walk back and forth through each change.
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}
Edit mode ships with all the additional shortcuts your users will need out of the box. Use the example File below to try the shortcuts you see in the table. Editing the example File will not update the table.
123456789101112131415161718192021222324252627282930// The data behind the table on the right—this very page maps over it.// Editing here won't rebuild the table, but go ahead: the surface is live.// `keys` are alternatives (joined by /); `modifiers` are held together.// `mod` adds the platform key: Cmd on macOS, Ctrl everywhere else.export const shortcuts = [ // Editing { keys: ['Tab'], action: 'Indent line or selection' }, { keys: ['Tab'], action: 'Outdent line or selection', modifiers: ['Shift'] }, { keys: ['X'], action: 'Cut', mod: true }, { keys: ['C'], action: 'Copy', mod: true }, { keys: ['V'], action: 'Paste', mod: true }, // Selection & cursor { keys: ['←', '→', '↑', '↓'], action: 'Move the cursor' }, { keys: ['←', '→', '↑', '↓'], action: 'Extend the selection', modifiers: ['Shift'] }, { keys: ['←', '→'], action: 'Jump to line start / end', mod: true }, { keys: ['Home', 'End'], action: 'Jump to document start / end', mod: true }, { keys: ['A'], action: 'Select all', mod: true }, { keys: ['Esc'], action: 'Collapse to a single cursor' }, // History { keys: ['Z'], action: 'Undo', mod: true }, { keys: ['Z'], action: 'Redo', modifiers: ['Shift'], mod: true }, // Find { keys: ['F'], action: 'Open the search panel', mod: true }, { keys: ['D'], action: 'Find next match of the selection', mod: true }, { keys: ['Enter'], action: 'Next match (in search panel)' }, { keys: ['Esc'], action: 'Close the search panel' }, // Multiple cursors { keys: ['Click'], action: 'Add a cursor at the click', mod: true },];
| Key | Action |
|---|---|
| Editing | |
| Tab | Indent line or selection |
| ShiftTab | Outdent line or selection |
| CmdX | Cut |
| CmdC | Copy |
| CmdV | Paste |
| Selection & cursor | |
| ←→↑↓ | Move the cursor |
| Shift←→↑↓ | Extend the selection |
| Cmd←→ | Jump to line start / end |
| CmdHomeEnd | Jump to document start / end |
| CmdA | Select all |
| Esc | Collapse to a single cursor |
| History | |
| CmdZ | Undo |
| CmdShiftZ | Redo |
| Find | |
| CmdF | Open the search panel |
| CmdD | Find next match of the selection |
| Enter | Next match (in search panel) |
| Esc | Close the search panel |
| Multiple cursors | |
| CmdClick | Add a cursor at the click |
The demos above cover the headline features. Here's the rest of what edit mode gives you for free.
File, FileDiff, or MultiFileDiff; the new-file side of a diff re-tokenizes as you type.VirtualizedFile / VirtualizedFileDiff; off-screen lines render on demand.contentEditable with role="textbox"; autocorrect, spellcheck, and capitalization off.@pierre/diffs/editor entry point—import it only when editing begins.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.