Compare commits

...
3 changed files with 79 additions and 15 deletions
@@ -60,7 +60,14 @@ describe("TerminalProcess (Integration Tests)", () => {
const emitSpy = sandbox.spy(process, "emit")
// Run a simple command
await process.run(terminal, "echo test")
const runPromise = process.run(terminal, "echo test")
// If terminal doesn't have shell integration, advance timer
if (!terminal.shellIntegration) {
await sandbox.clock.tickAsync(3000)
}
await runPromise
// Verify that the continue event was emitted
;(emitSpy as sinon.SinonSpy).calledWith("continue").should.be.true()
@@ -76,7 +83,14 @@ describe("TerminalProcess (Integration Tests)", () => {
const emitSpy = sandbox.spy(process, "emit")
// Run a command that produces predictable output
await process.run(terminal, "echo 'Line 1' && echo 'Line 2'")
const runPromise = process.run(terminal, "echo 'Line 1' && echo 'Line 2'")
// If terminal doesn't have shell integration, advance timer
if (!terminal.shellIntegration) {
await sandbox.clock.tickAsync(3000)
}
await runPromise
// Check that the events were emitted
;(emitSpy as sinon.SinonSpy).calledWith("completed").should.be.true()
@@ -92,7 +106,14 @@ describe("TerminalProcess (Integration Tests)", () => {
const emitSpy = sandbox.spy(process, "emit")
// Run a command that lists files
await process.run(terminal, "ls -la")
const runPromise = process.run(terminal, "ls -la")
// If terminal doesn't have shell integration, advance timer
if (!terminal.shellIntegration) {
await sandbox.clock.tickAsync(3000)
}
await runPromise
// Verify that the continue event was emitted
;(emitSpy as sinon.SinonSpy).calledWith("continue").should.be.true()
@@ -130,7 +151,14 @@ describe("TerminalProcess (Integration Tests)", () => {
const emitSpy = sandbox.spy(process, "emit")
// Run a command that produces predictable output
await process.run(terminal, "echo 'Line 1' 'Line 2'")
const runPromise = process.run(terminal, "echo 'Line 1' 'Line 2'")
// If terminal doesn't have shell integration, advance timer
if (!terminal.shellIntegration) {
await sandbox.clock.tickAsync(3000)
}
await runPromise
// Check that the events were emitted
;(emitSpy as sinon.SinonSpy).calledWith("completed").should.be.true()
@@ -146,7 +174,14 @@ describe("TerminalProcess (Integration Tests)", () => {
const emitSpy = sandbox.spy(process, "emit")
// Run a command that produces predictable output
await process.run(terminal, "echo \"Line 1\" && echo 'Line 2'")
const runPromise = process.run(terminal, "echo \"Line 1\" && echo 'Line 2'")
// If terminal doesn't have shell integration, advance timer
if (!terminal.shellIntegration) {
await sandbox.clock.tickAsync(3000)
}
await runPromise
// Check that the events were emitted
;(emitSpy as sinon.SinonSpy).calledWith("completed").should.be.true()
@@ -169,8 +204,14 @@ describe("TerminalProcess (Integration Tests)", () => {
// Spy on the emit function to verify events
const emitSpy = sandbox.spy(process, "emit")
// Run the command
await process.run(terminal, "test-command")
// Run the command - this returns a promise
const runPromise = process.run(terminal, "test-command")
// Advance the fake timer by 3 seconds to trigger the setTimeout
await sandbox.clock.tickAsync(3000)
// Now wait for the promise to resolve
await runPromise
// Check that the correct methods were called and events emitted
sendTextStub.calledWith("test-command", true).should.be.true()
+25 -2
View File
@@ -2,6 +2,7 @@ import { EventEmitter } from "events"
import { stripAnsi } from "./ansiUtils"
import * as vscode from "vscode"
import { Logger } from "@services/logging/Logger"
import { getLatestTerminalOutput } from "./get-latest-output"
export interface TerminalProcessEvents {
line: [line: string]
@@ -31,6 +32,19 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
// super()
async run(terminal: vscode.Terminal, command: string) {
// When command does not produce any output, we can assume the shell integration API failed and as a fallback return the current terminal contents
const emitCurrentTerminalContents = async () => {
try {
const terminalSnapshot = await getLatestTerminalOutput()
if (terminalSnapshot && terminalSnapshot.trim()) {
const fallbackMessage = `The command's output could not be captured due to some technical issue, however it has been executed successfully. Here's the current terminal's content to help you get the command's output:\n\n${terminalSnapshot}`
this.emit("line", fallbackMessage)
}
} catch (error) {
console.error("Error capturing terminal output:", error)
}
}
// Clear any existing grace period timer from previous commands
if (this.gracePeriodTimer) {
clearTimeout(this.gracePeriodTimer)
@@ -249,7 +263,8 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
if (!didEmitEmptyLine) {
console.log(`[TerminalProcess] Emitting fallback empty line for no-output command`)
this.emit("line", "") // empty line to show proceed button
this.emit("line", "[Command completed with no output]")
// this.emit("line", "[Command completed with no output]")
await emitCurrentTerminalContents()
didEmitEmptyLine = true
}
}
@@ -295,7 +310,8 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
console.log(`[TerminalProcess] WARNING: Process completed but no output was captured`)
// Ensure we emit at least one line for UI feedback
if (!didEmitEmptyLine) {
this.emit("line", "[Command completed silently]")
// this.emit("line", "[Command completed silently]")
await emitCurrentTerminalContents()
}
}
@@ -314,7 +330,14 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
this.startGracePeriod()
}
} else {
// no shell integration detected, we'll fallback to running the command and capturing the terminal's output after some time
terminal.sendText(command, true)
// wait 3 seconds for the command to run
await new Promise((resolve) => setTimeout(resolve, 3000))
// For terminals without shell integration, also try to capture terminal content
await emitCurrentTerminalContents()
// For terminals without shell integration, we can't know when the command completes
// So we'll just emit the continue event after a delay
this.emit("completed")
+6 -6
View File
@@ -1353,7 +1353,7 @@ export const ChatRowContent = memo(
style={{
display: "flex",
flexDirection: "column",
backgroundColor: "rgba(255, 191, 0, 0.1)",
backgroundColor: "var(--vscode-textBlockQuote-background)",
padding: 8,
borderRadius: 3,
fontSize: 12,
@@ -1368,19 +1368,19 @@ export const ChatRowContent = memo(
className="codicon codicon-warning"
style={{
marginRight: 8,
fontSize: 18,
color: "#FFA500",
fontSize: 14,
color: "var(--vscode-descriptionForeground)",
}}></i>
<span
style={{
fontWeight: 500,
color: "#FFA500",
color: "var(--vscode-foreground)",
}}>
Shell Integration Unavailable
</span>
</div>
<div>
Cline won't be able to view the command's output. Please update VSCode (
<div style={{ color: "var(--vscode-foreground)", opacity: 0.8 }}>
Cline may have trouble viewing the command's output. Please update VSCode (
<code>CMD/CTRL + Shift + P</code> → "Update") and make sure you're using a supported
shell: zsh, bash, fish, or PowerShell (<code>CMD/CTRL + Shift + P</code> "Terminal:
Select Default Profile").{" "}