@@ -21,6 +21,13 @@ import {
2121 moveUp ,
2222} from "./promptBuffer" ;
2323import type { PromptBufferState } from "./promptBuffer" ;
24+ import {
25+ clearPromptUndoRedoState ,
26+ createPromptUndoRedoState ,
27+ recordPromptEdit ,
28+ redoPromptEdit ,
29+ undoPromptEdit ,
30+ } from "./promptUndoRedo" ;
2431import { buildSlashCommands , filterSlashCommands , findExactSlashCommand } from "./slashCommands" ;
2532import type { SlashCommandItem } from "./slashCommands" ;
2633import { readClipboardImageAsync } from "./clipboard" ;
@@ -122,6 +129,7 @@ export const PromptInput = React.memo(function PromptInput({
122129 const [ draftBeforeHistory , setDraftBeforeHistory ] = useState < string | null > ( null ) ;
123130 const [ hasTerminalFocus , setHasTerminalFocus ] = useState ( true ) ;
124131 const lastCtrlDAt = React . useRef < number > ( 0 ) ;
132+ const undoRedoRef = React . useRef ( createPromptUndoRedoState ( ) ) ;
125133
126134 const slashItems = React . useMemo ( ( ) => buildSlashCommands ( skills ) , [ skills ] ) ;
127135 const slashToken = getCurrentSlashToken ( buffer ) ;
@@ -236,6 +244,7 @@ export const PromptInput = React.memo(function PromptInput({
236244 setStatusMessage ( "Interrupting…" ) ;
237245 } else if ( ! isEmpty ( buffer ) ) {
238246 setBuffer ( EMPTY_BUFFER ) ;
247+ clearUndoRedoStacks ( ) ;
239248 } else {
240249 setStatusMessage ( "press ctrl+d to exit" ) ;
241250 }
@@ -475,6 +484,14 @@ export const PromptInput = React.memo(function PromptInput({
475484 updateBuffer ( ( s ) => insertText ( s , "\n" ) ) ;
476485 return ;
477486 }
487+ if ( key . ctrl && key . shift && input === "-" ) {
488+ redo ( ) ;
489+ return ;
490+ }
491+ if ( key . ctrl && input === "-" ) {
492+ undo ( ) ;
493+ return ;
494+ }
478495 if ( input . startsWith ( "\u001B" ) ) {
479496 // Unhandled escape sequence (e.g. function keys); ignore to avoid inserting garbage.
480497 return ;
@@ -490,14 +507,40 @@ export const PromptInput = React.memo(function PromptInput({
490507 { isActive : ! disabled }
491508 ) ;
492509
510+ function undo ( ) : void {
511+ const previous = undoPromptEdit ( undoRedoRef . current , buffer ) ;
512+ if ( ! previous ) {
513+ return ;
514+ }
515+ exitHistoryBrowsing ( ) ;
516+ setBuffer ( previous ) ;
517+ }
518+
519+ function redo ( ) : void {
520+ const next = redoPromptEdit ( undoRedoRef . current , buffer ) ;
521+ if ( ! next ) {
522+ return ;
523+ }
524+ exitHistoryBrowsing ( ) ;
525+ setBuffer ( next ) ;
526+ }
527+
528+ function clearUndoRedoStacks ( ) : void {
529+ clearPromptUndoRedoState ( undoRedoRef . current ) ;
530+ }
531+
493532 function exitHistoryBrowsing ( ) : void {
494533 setHistoryCursor ( - 1 ) ;
495534 setDraftBeforeHistory ( null ) ;
496535 }
497536
498537 function updateBuffer ( updater : ( state : PromptBufferState ) => PromptBufferState ) : void {
499538 exitHistoryBrowsing ( ) ;
500- setBuffer ( updater ) ;
539+ setBuffer ( ( current ) => {
540+ const next = updater ( current ) ;
541+ recordPromptEdit ( undoRedoRef . current , current , next ) ;
542+ return next ;
543+ } ) ;
501544 }
502545
503546 function navigateHistory ( direction : - 1 | 1 ) : void {
@@ -551,6 +594,7 @@ export const PromptInput = React.memo(function PromptInput({
551594 if ( item . kind === "new" ) {
552595 onSubmit ( { text : "" , imageUrls : [ ] , command : "new" } ) ;
553596 setBuffer ( EMPTY_BUFFER ) ;
597+ clearUndoRedoStacks ( ) ;
554598 setImageUrls ( [ ] ) ;
555599 setSelectedSkills ( [ ] ) ;
556600 setShowSkillsDropdown ( false ) ;
@@ -559,6 +603,7 @@ export const PromptInput = React.memo(function PromptInput({
559603 if ( item . kind === "init" ) {
560604 onSubmit ( buildInitPromptSubmission ( selectedSkills ) ) ;
561605 setBuffer ( EMPTY_BUFFER ) ;
606+ clearUndoRedoStacks ( ) ;
562607 setImageUrls ( [ ] ) ;
563608 setSelectedSkills ( [ ] ) ;
564609 setShowSkillsDropdown ( false ) ;
@@ -567,6 +612,7 @@ export const PromptInput = React.memo(function PromptInput({
567612 if ( item . kind === "resume" ) {
568613 onSubmit ( { text : "" , imageUrls : [ ] , command : "resume" } ) ;
569614 setBuffer ( EMPTY_BUFFER ) ;
615+ clearUndoRedoStacks ( ) ;
570616 setImageUrls ( [ ] ) ;
571617 setSelectedSkills ( [ ] ) ;
572618 setShowSkillsDropdown ( false ) ;
@@ -575,6 +621,7 @@ export const PromptInput = React.memo(function PromptInput({
575621 if ( item . kind === "mcp" ) {
576622 onSubmit ( { text : "/mcp" , imageUrls : [ ] , command : "mcp" } ) ;
577623 setBuffer ( EMPTY_BUFFER ) ;
624+ clearUndoRedoStacks ( ) ;
578625 setImageUrls ( [ ] ) ;
579626 setSelectedSkills ( [ ] ) ;
580627 setShowSkillsDropdown ( false ) ;
@@ -583,6 +630,7 @@ export const PromptInput = React.memo(function PromptInput({
583630 if ( item . kind === "exit" ) {
584631 onSubmit ( { text : "/exit" , imageUrls : [ ] , command : "exit" } ) ;
585632 setBuffer ( EMPTY_BUFFER ) ;
633+ clearUndoRedoStacks ( ) ;
586634 return ;
587635 }
588636 }
@@ -612,6 +660,7 @@ export const PromptInput = React.memo(function PromptInput({
612660 selectedSkills,
613661 } ) ;
614662 setBuffer ( EMPTY_BUFFER ) ;
663+ clearUndoRedoStacks ( ) ;
615664 setImageUrls ( [ ] ) ;
616665 setSelectedSkills ( [ ] ) ;
617666 setShowSkillsDropdown ( false ) ;
@@ -628,6 +677,7 @@ export const PromptInput = React.memo(function PromptInput({
628677 function clearSlashToken ( ) : void {
629678 exitHistoryBrowsing ( ) ;
630679 setBuffer ( ( state ) => removeCurrentSlashToken ( state ) ) ;
680+ clearUndoRedoStacks ( ) ;
631681 }
632682
633683 function openModelDropdown ( ) : void {
0 commit comments