@@ -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" ;
@@ -123,6 +130,7 @@ export const PromptInput = React.memo(function PromptInput({
123130 const [ draftBeforeHistory , setDraftBeforeHistory ] = useState < string | null > ( null ) ;
124131 const [ hasTerminalFocus , setHasTerminalFocus ] = useState ( true ) ;
125132 const lastCtrlDAt = React . useRef < number > ( 0 ) ;
133+ const undoRedoRef = React . useRef ( createPromptUndoRedoState ( ) ) ;
126134
127135 const slashItems = React . useMemo ( ( ) => buildSlashCommands ( skills ) , [ skills ] ) ;
128136 const slashToken = getCurrentSlashToken ( buffer ) ;
@@ -237,6 +245,7 @@ export const PromptInput = React.memo(function PromptInput({
237245 setStatusMessage ( "Interrupting…" ) ;
238246 } else if ( ! isEmpty ( buffer ) ) {
239247 setBuffer ( EMPTY_BUFFER ) ;
248+ clearUndoRedoStacks ( ) ;
240249 } else {
241250 setStatusMessage ( "press ctrl+d to exit" ) ;
242251 }
@@ -476,6 +485,14 @@ export const PromptInput = React.memo(function PromptInput({
476485 updateBuffer ( ( s ) => insertText ( s , "\n" ) ) ;
477486 return ;
478487 }
488+ if ( key . ctrl && key . shift && input === "-" ) {
489+ redo ( ) ;
490+ return ;
491+ }
492+ if ( key . ctrl && input === "-" ) {
493+ undo ( ) ;
494+ return ;
495+ }
479496 if ( input . startsWith ( "\u001B" ) ) {
480497 // Unhandled escape sequence (e.g. function keys); ignore to avoid inserting garbage.
481498 return ;
@@ -491,14 +508,40 @@ export const PromptInput = React.memo(function PromptInput({
491508 { isActive : ! disabled }
492509 ) ;
493510
511+ function undo ( ) : void {
512+ const previous = undoPromptEdit ( undoRedoRef . current , buffer ) ;
513+ if ( ! previous ) {
514+ return ;
515+ }
516+ exitHistoryBrowsing ( ) ;
517+ setBuffer ( previous ) ;
518+ }
519+
520+ function redo ( ) : void {
521+ const next = redoPromptEdit ( undoRedoRef . current , buffer ) ;
522+ if ( ! next ) {
523+ return ;
524+ }
525+ exitHistoryBrowsing ( ) ;
526+ setBuffer ( next ) ;
527+ }
528+
529+ function clearUndoRedoStacks ( ) : void {
530+ clearPromptUndoRedoState ( undoRedoRef . current ) ;
531+ }
532+
494533 function exitHistoryBrowsing ( ) : void {
495534 setHistoryCursor ( - 1 ) ;
496535 setDraftBeforeHistory ( null ) ;
497536 }
498537
499538 function updateBuffer ( updater : ( state : PromptBufferState ) => PromptBufferState ) : void {
500539 exitHistoryBrowsing ( ) ;
501- setBuffer ( updater ) ;
540+ setBuffer ( ( current ) => {
541+ const next = updater ( current ) ;
542+ recordPromptEdit ( undoRedoRef . current , current , next ) ;
543+ return next ;
544+ } ) ;
502545 }
503546
504547 function navigateHistory ( direction : - 1 | 1 ) : void {
@@ -552,6 +595,7 @@ export const PromptInput = React.memo(function PromptInput({
552595 if ( item . kind === "new" ) {
553596 onSubmit ( { text : "" , imageUrls : [ ] , command : "new" } ) ;
554597 setBuffer ( EMPTY_BUFFER ) ;
598+ clearUndoRedoStacks ( ) ;
555599 setImageUrls ( [ ] ) ;
556600 setSelectedSkills ( [ ] ) ;
557601 setShowSkillsDropdown ( false ) ;
@@ -560,6 +604,7 @@ export const PromptInput = React.memo(function PromptInput({
560604 if ( item . kind === "init" ) {
561605 onSubmit ( buildInitPromptSubmission ( selectedSkills ) ) ;
562606 setBuffer ( EMPTY_BUFFER ) ;
607+ clearUndoRedoStacks ( ) ;
563608 setImageUrls ( [ ] ) ;
564609 setSelectedSkills ( [ ] ) ;
565610 setShowSkillsDropdown ( false ) ;
@@ -568,6 +613,7 @@ export const PromptInput = React.memo(function PromptInput({
568613 if ( item . kind === "resume" ) {
569614 onSubmit ( { text : "" , imageUrls : [ ] , command : "resume" } ) ;
570615 setBuffer ( EMPTY_BUFFER ) ;
616+ clearUndoRedoStacks ( ) ;
571617 setImageUrls ( [ ] ) ;
572618 setSelectedSkills ( [ ] ) ;
573619 setShowSkillsDropdown ( false ) ;
@@ -576,6 +622,7 @@ export const PromptInput = React.memo(function PromptInput({
576622 if ( item . kind === "mcp" ) {
577623 onSubmit ( { text : "/mcp" , imageUrls : [ ] , command : "mcp" } ) ;
578624 setBuffer ( EMPTY_BUFFER ) ;
625+ clearUndoRedoStacks ( ) ;
579626 setImageUrls ( [ ] ) ;
580627 setSelectedSkills ( [ ] ) ;
581628 setShowSkillsDropdown ( false ) ;
@@ -584,6 +631,7 @@ export const PromptInput = React.memo(function PromptInput({
584631 if ( item . kind === "exit" ) {
585632 onSubmit ( { text : "/exit" , imageUrls : [ ] , command : "exit" } ) ;
586633 setBuffer ( EMPTY_BUFFER ) ;
634+ clearUndoRedoStacks ( ) ;
587635 return ;
588636 }
589637 }
@@ -613,6 +661,7 @@ export const PromptInput = React.memo(function PromptInput({
613661 selectedSkills,
614662 } ) ;
615663 setBuffer ( EMPTY_BUFFER ) ;
664+ clearUndoRedoStacks ( ) ;
616665 setImageUrls ( [ ] ) ;
617666 setSelectedSkills ( [ ] ) ;
618667 setShowSkillsDropdown ( false ) ;
@@ -629,6 +678,7 @@ export const PromptInput = React.memo(function PromptInput({
629678 function clearSlashToken ( ) : void {
630679 exitHistoryBrowsing ( ) ;
631680 setBuffer ( ( state ) => removeCurrentSlashToken ( state ) ) ;
681+ clearUndoRedoStacks ( ) ;
632682 }
633683
634684 function openModelDropdown ( ) : void {
0 commit comments