File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -123,6 +123,39 @@ describe("@ popup narrows as you type", () => {
123123 } )
124124 } )
125125
126+ test ( "quitting mid-lookup does not write into the disposed shell" , async ( ) => {
127+ await withTestRenderer (
128+ async ( h ) => {
129+ const shell = createAppShell ( h . renderer , {
130+ terminal : { columns : 80 , rows : 24 } ,
131+ wireKeys : false ,
132+ run : "idle" ,
133+ } )
134+ let resolveLookup : ( entries : readonly string [ ] ) => void = ( ) => { }
135+ setMentionSuggestionSource (
136+ shell ,
137+ ( ) =>
138+ new Promise < readonly string [ ] > ( ( resolve ) => {
139+ resolveLookup = resolve
140+ } ) ,
141+ )
142+
143+ shell . prompt . value = "read @"
144+ shell . prompt . cursorOffset = shell . prompt . value . length
145+ const pending = openAtMentionSuggestions ( shell )
146+
147+ // The operator quits before the filesystem lookup answers.
148+ shell . dispose ( )
149+ resolveLookup ( [ "AGENTS.md" , "README.md" ] )
150+
151+ await expect ( pending ) . resolves . toBe ( false )
152+ expect ( shell . overlayKind ) . toBeNull ( )
153+ expect ( isMentionPopupOpen ( shell ) ) . toBe ( false )
154+ } ,
155+ { width : 80 , height : 24 } ,
156+ )
157+ } )
158+
126159 test ( "no match closes the popup and leaves the typed text" , async ( ) => {
127160 await withShell ( async ( shell ) => {
128161 await openAt ( shell , "@" )
Original file line number Diff line number Diff line change @@ -71,6 +71,37 @@ describe("image attachments", () => {
7171 } )
7272 } )
7373
74+ test ( "quitting mid-read does not attach into the disposed shell" , async ( ) => {
75+ await withTestRenderer (
76+ async ( h ) => {
77+ const shell = createAppShell ( h . renderer , {
78+ terminal : { columns : 80 , rows : 24 } ,
79+ wireKeys : true ,
80+ run : "idle" ,
81+ } )
82+ let resolveRead : ( r : { ok : true ; attachment : PendingImageAttachment } ) => void =
83+ ( ) => { }
84+ setPromptImageSource (
85+ shell ,
86+ ( ) =>
87+ new Promise ( ( resolve ) => {
88+ resolveRead = resolve
89+ } ) ,
90+ )
91+
92+ const pending = attachClipboardImage ( shell )
93+
94+ // The operator quits before the clipboard read answers.
95+ shell . dispose ( )
96+ resolveRead ( { ok : true , attachment : CLIP } )
97+
98+ expect ( await pending ) . toBe ( false )
99+ expect ( shell . pendingAttachments ) . toEqual ( [ ] )
100+ } ,
101+ { width : 80 , height : 24 } ,
102+ )
103+ } )
104+
74105 // Raw control bytes, not a synthetic KeyEvent: a binding that never matches
75106 // what the terminal actually writes looks correct in the catalog and fails
76107 // silently in use.
Original file line number Diff line number Diff line change @@ -863,6 +863,9 @@ export async function attachClipboardImage(shell: AppShell): Promise<boolean> {
863863 const source = shellPromptImageSource . get ( shell ) ?? readClipboardImage
864864 setStatusFlash ( shell , "reading clipboard image…" )
865865 const result = await source ( )
866+ // Quitting while the clipboard read is pending tears down the shell's
867+ // renderables; a stale continuation must not mutate them on resume.
868+ if ( shell . disposed ) return false
866869 if ( ! result . ok ) {
867870 setStatusFlash ( shell , `image attach failed: ${ result . reason } ` )
868871 return false
@@ -3931,11 +3934,15 @@ export async function openAtMentionSuggestions(shell: AppShell): Promise<boolean
39313934 await source ( token . dir ) ,
39323935 token . fragment ,
39333936 )
3937+ // Quitting mid-lookup tears down the renderer/TextBuffer this function
3938+ // writes into below; a resolved-but-stale lookup must not touch them.
3939+ if ( shell . disposed ) return false
39343940 // The source caps how many entries it returns per directory, so a large
39353941 // directory can cap out before the interior match appears. Asking it to do
39363942 // its own prefix filter puts that cap after the narrowing instead of before.
39373943 if ( suggestions . length === 0 && token . fragment . length > 0 ) {
39383944 suggestions = await source ( at . prefix )
3945+ if ( shell . disposed ) return false
39393946 }
39403947 if ( mentionGenerations . get ( shell ) !== generation ) return false
39413948
You can’t perform that action at this time.
0 commit comments