Skip to content

Commit 48801f9

Browse files
committed
extract command fields into one state
1 parent 5c28032 commit 48801f9

4 files changed

Lines changed: 146 additions & 81 deletions

File tree

com.microsoft.copilot.eclipse.terminal.api/src/com/microsoft/copilot/eclipse/terminal/api/TerminalCommandProcessor.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,27 +125,28 @@ public static CompletionCheckResult tryCompleteWithMarker(StringBuilder output,
125125
return CompletionCheckResult.incomplete();
126126
}
127127

128-
if (skipCompletion) {
128+
// Keep command output plus the next prompt, but remove the command-finished marker itself, including exit code.
129+
String completedOutput = output.substring(0, commandFinishMarkerRange.startIndex)
130+
+ output.substring(commandFinishMarkerRange.endIndex, promptEndMarkerRange.endIndex);
131+
if (shouldSkipCompletion(completedOutput, activeCommand, skipCompletion)) {
129132
// This region belongs to a command that was interrupted by Ctrl+C. Drop it so it cannot complete the next
130133
// foreground command that may already be listening on the same terminal output buffer.
131134
output.delete(0, promptEndMarkerRange.endIndex);
132135
return CompletionCheckResult.skipped();
133136
}
134-
135-
// Keep command output plus the next prompt, but remove the command-finished marker itself, including exit code.
136-
String completedOutput = output.substring(0, commandFinishMarkerRange.startIndex)
137-
+ output.substring(commandFinishMarkerRange.endIndex, promptEndMarkerRange.endIndex);
138137
return CompletionCheckResult.completed(cleanCommandOutput(completedOutput, activeCommand));
139138
}
140139

141140
/**
142141
* Attempts to complete a command by detecting a shell prompt.
143142
*
144143
* @param output terminal output buffer
144+
* @param activeCommand command currently being executed
145145
* @param skipCompletion whether to skip the next completed command region
146146
* @return the completion check result
147147
*/
148-
public static CompletionCheckResult tryCompleteWithPrompt(StringBuilder output, boolean skipCompletion) {
148+
public static CompletionCheckResult tryCompleteWithPrompt(StringBuilder output, String activeCommand,
149+
boolean skipCompletion) {
149150
String terminalOutput = output.toString().trim();
150151
int lastNewLineIndex = terminalOutput.lastIndexOf('\n');
151152
if (lastNewLineIndex <= 0) {
@@ -163,7 +164,7 @@ public static CompletionCheckResult tryCompleteWithPrompt(StringBuilder output,
163164
return CompletionCheckResult.incomplete();
164165
}
165166

166-
if (skipCompletion) {
167+
if (shouldSkipCompletion(terminalOutput, activeCommand, skipCompletion)) {
167168
output.setLength(0);
168169
return CompletionCheckResult.skipped();
169170
}
@@ -182,6 +183,17 @@ public static CompletionCheckResult tryCompleteWithPrompt(StringBuilder output,
182183
return CompletionCheckResult.completed(contentWithoutLastPrompt.substring(promptStartIndex).trim());
183184
}
184185

186+
private static boolean shouldSkipCompletion(String completedRegion, String activeCommand, boolean skipCompletion) {
187+
if (!skipCompletion) {
188+
return false;
189+
}
190+
String normalizedCommand = normalizeLineEndings(activeCommand == null ? "" : activeCommand).trim();
191+
if (normalizedCommand.isBlank()) {
192+
return true;
193+
}
194+
return !normalizeLineEndings(completedRegion).contains(normalizedCommand);
195+
}
196+
185197
private static String removeBracketedPasteMarkers(String output) {
186198
return output.replace(BRACKETED_PASTE_START, "").replace(BRACKETED_PASTE_END, "");
187199
}

com.microsoft.copilot.eclipse.ui.terminal.tm/src/com/microsoft/copilot/eclipse/ui/terminal/tm/RunInTerminalTool.java

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)