@@ -67,9 +67,7 @@ public class RunInTerminalTool implements IRunInTerminalTool {
6767
6868 // Output and command state
6969 private StringBuilder sb ;
70- private CompletableFuture <String > resultFuture ;
71- private volatile String activeCommand ;
72- private volatile boolean useMarker ;
70+ private volatile ForegroundCommand foregroundCommand ;
7371 private volatile boolean skipNextCompletionAfterInterrupt ;
7472
7573 /**
@@ -91,22 +89,24 @@ public CompletableFuture<String> executeCommand(String command, boolean isBackgr
9189 interruptCurrentCommand (COMMAND_INTERRUPTED_MESSAGE , true );
9290 }
9391
94- resultFuture = new CompletableFuture <>();
95- CompletableFuture <String > commandFuture = resultFuture ;
96- useMarker = hasShellIntegrationMarker ();
97- activeCommand = isBackground ? null : command ;
98-
99- if (!useMarker ) {
100- // Retain only the last line (prompt) in the output buffer
101- if (!sb .isEmpty ()) {
102- int lastLineStart = sb .lastIndexOf (StringUtils .LF );
103- if (lastLineStart > 0 ) {
104- sb .delete (0 , lastLineStart );
92+ CompletableFuture <String > commandFuture = new CompletableFuture <>();
93+ ForegroundCommand commandState = isBackground ? null
94+ : new ForegroundCommand (commandFuture , command , hasShellIntegrationMarker ());
95+
96+ if (commandState != null ) {
97+ foregroundCommand = commandState ;
98+ if (!commandState .useMarker ()) {
99+ // Retain only the last line (prompt) in the output buffer
100+ if (!sb .isEmpty ()) {
101+ int lastLineStart = sb .lastIndexOf (StringUtils .LF );
102+ if (lastLineStart > 0 ) {
103+ sb .delete (0 , lastLineStart );
104+ }
105105 }
106+ } else {
107+ // For marker-based detection, clear the buffer
108+ sb .setLength (0 );
106109 }
107- } else {
108- // For marker-based detection, clear the buffer
109- sb .setLength (0 );
110110 }
111111
112112 String executionId = UUID .randomUUID ().toString ();
@@ -121,7 +121,7 @@ public CompletableFuture<String> executeCommand(String command, boolean isBackgr
121121
122122 ITerminalService service = TerminalServiceFactory .getService ();
123123 if (service == null ) {
124- activeCommand = null ;
124+ clearForegroundCommand ( commandState ) ;
125125 return CompletableFuture .completedFuture ("Failed to open terminal console due to terminal service is null." );
126126 }
127127
@@ -132,7 +132,7 @@ public CompletableFuture<String> executeCommand(String command, boolean isBackgr
132132 }
133133 ITerminalViewControl terminalViewControl = finalizeTerminalSetup (executionId , isBackground );
134134 if (terminalViewControl == null ) {
135- activeCommand = null ;
135+ clearForegroundCommand ( commandState ) ;
136136 commandFuture .complete ("Terminal view control cannot be setup for RunInTerminalTool." );
137137 return ;
138138 }
@@ -143,7 +143,7 @@ public CompletableFuture<String> executeCommand(String command, boolean isBackgr
143143 }
144144 sendCommand (terminalViewControl , finalCommand );
145145 } else {
146- activeCommand = null ;
146+ clearForegroundCommand ( commandState ) ;
147147 commandFuture .complete ("Failed to open terminal console: " + status .getException ());
148148 }
149149 });
@@ -224,30 +224,37 @@ public void cancelCurrentCommand() {
224224
225225 private void interruptCurrentCommand (String completionMessage , boolean skipInterruptedCompletion ) {
226226 ITerminalViewControl terminalViewControl = null ;
227- CompletableFuture < String > commandFuture = null ;
227+ ForegroundCommand commandState = null ;
228228 synchronized (lock ) {
229229 if (!hasRunningForegroundCommand ()) {
230230 if (!skipInterruptedCompletion ) {
231231 skipNextCompletionAfterInterrupt = false ;
232232 }
233233 return ;
234234 }
235- activeCommand = null ;
235+ commandState = foregroundCommand ;
236+ foregroundCommand = null ;
236237 skipNextCompletionAfterInterrupt = skipInterruptedCompletion ;
237238 terminalViewControl = persistentTerminalViewControl ;
238- commandFuture = resultFuture ;
239239 }
240240
241241 if (terminalViewControl != null ) {
242242 sendInterrupt (terminalViewControl );
243243 }
244- if (commandFuture != null && !commandFuture .isDone ()) {
245- commandFuture .complete (completionMessage );
244+ if (commandState != null && !commandState . future () .isDone ()) {
245+ commandState . future () .complete (completionMessage );
246246 }
247247 }
248248
249249 private boolean hasRunningForegroundCommand () {
250- return StringUtils .isNotBlank (activeCommand ) && resultFuture != null && !resultFuture .isDone ();
250+ ForegroundCommand commandState = foregroundCommand ;
251+ return commandState != null && !commandState .future ().isDone ();
252+ }
253+
254+ private void clearForegroundCommand (ForegroundCommand commandState ) {
255+ if (commandState != null && foregroundCommand == commandState ) {
256+ foregroundCommand = null ;
257+ }
251258 }
252259
253260 private void sendInterrupt (ITerminalViewControl terminalViewControl ) {
@@ -354,30 +361,35 @@ private ITerminalServiceOutputStreamMonitorListener buildOutputStreamMonitorList
354361 output .append (content );
355362
356363 // Detect completion based on platform strategy
357- if (!isBackground && resultFuture != null && !resultFuture .isDone ()) {
364+ ForegroundCommand commandState = foregroundCommand ;
365+ if (!isBackground && commandState != null && !commandState .future ().isDone ()) {
358366 CompletionCheckResult completionResult ;
359367 do {
360- completionResult = useMarker
361- ? TerminalCommandProcessor .tryCompleteWithMarker (output , activeCommand , skipNextCompletionAfterInterrupt )
362- : TerminalCommandProcessor .tryCompleteWithPrompt (output , skipNextCompletionAfterInterrupt );
363- handleCompletionResult (completionResult );
368+ completionResult = commandState .useMarker ()
369+ ? TerminalCommandProcessor .tryCompleteWithMarker (output , commandState .command (),
370+ skipNextCompletionAfterInterrupt )
371+ : TerminalCommandProcessor .tryCompleteWithPrompt (output , commandState .command (),
372+ skipNextCompletionAfterInterrupt );
373+ handleCompletionResult (commandState , completionResult );
364374 } while (completionResult .state () == CompletionCheckState .SKIPPED
365- && resultFuture != null && !resultFuture .isDone ());
375+ && foregroundCommand == commandState && !commandState . future () .isDone ());
366376 }
367377 };
368378 }
369379
370- private void handleCompletionResult (CompletionCheckResult completionResult ) {
380+ private void handleCompletionResult (ForegroundCommand commandState , CompletionCheckResult completionResult ) {
371381 if (completionResult .state () == CompletionCheckState .INCOMPLETE ) {
372382 return ;
373383 }
374384 if (completionResult .state () == CompletionCheckState .SKIPPED ) {
375385 skipNextCompletionAfterInterrupt = false ;
376386 return ;
377387 }
378- activeCommand = null ;
379- if (resultFuture != null && !resultFuture .isDone ()) {
380- resultFuture .complete (completionResult .output ());
388+ if (foregroundCommand == commandState ) {
389+ foregroundCommand = null ;
390+ }
391+ if (!commandState .future ().isDone ()) {
392+ commandState .future ().complete (completionResult .output ());
381393 }
382394 }
383395
@@ -424,6 +436,9 @@ private String buildBackgroundTerminalTitle(String executionId) {
424436 return BACKGROUND_TERMINAL_PREFIX + executionId ;
425437 }
426438
439+ private record ForegroundCommand (CompletableFuture <String > future , String command , boolean useMarker ) {
440+ }
441+
427442 /**
428443 * Get active workbench page without UiUtils dependency.
429444 */
0 commit comments