Skip to content

Commit 8b54686

Browse files
committed
More progress
1 parent 63f7da4 commit 8b54686

File tree

6 files changed

+544
-516
lines changed

6 files changed

+544
-516
lines changed
Lines changed: 259 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,272 @@
1-
import type { RooTerminal, RooTerminalCallbacks, RooTerminalProcess, RooTerminalProcessResultPromise } from "./types"
1+
import { truncateOutput, applyRunLengthEncoding } from "../misc/extract-text"
2+
3+
import type {
4+
RooTerminalProvider,
5+
RooTerminal,
6+
RooTerminalCallbacks,
7+
RooTerminalProcess,
8+
RooTerminalProcessResultPromise,
9+
ExitCodeDetails,
10+
} from "./types"
211

312
export abstract class BaseTerminal implements RooTerminal {
4-
public process?: RooTerminalProcess
13+
public readonly provider: RooTerminalProvider
14+
public readonly id: number
15+
public readonly initialCwd: string
516

6-
protected readonly initialCwd: string
17+
public busy: boolean
18+
public running: boolean
19+
protected streamClosed: boolean
20+
21+
public taskId?: string
22+
public process?: RooTerminalProcess
23+
public completedProcesses: RooTerminalProcess[] = []
724

8-
constructor(cwd: string) {
25+
constructor(provider: RooTerminalProvider, id: number, cwd: string) {
26+
this.provider = provider
27+
this.id = id
928
this.initialCwd = cwd
29+
this.busy = false
30+
this.running = false
31+
this.streamClosed = false
1032
}
1133

1234
public getCurrentWorkingDirectory(): string {
1335
return this.initialCwd
1436
}
1537

38+
abstract isClosed(): boolean
39+
1640
abstract runCommand(command: string, callbacks: RooTerminalCallbacks): RooTerminalProcessResultPromise
41+
42+
/**
43+
* Sets the active stream for this terminal and notifies the process
44+
* @param stream The stream to set, or undefined to clean up
45+
* @throws Error if process is undefined when a stream is provided
46+
*/
47+
public setActiveStream(stream: AsyncIterable<string> | undefined): void {
48+
if (stream) {
49+
if (!this.process) {
50+
this.running = false
51+
52+
console.warn(
53+
`[Terminal ${this.provider}/${this.id}] process is undefined, so cannot set terminal stream (probably user-initiated non-Roo command)`,
54+
)
55+
56+
return
57+
}
58+
59+
this.running = true
60+
this.streamClosed = false
61+
this.process.emit("stream_available", stream)
62+
} else {
63+
this.streamClosed = true
64+
}
65+
}
66+
67+
/**
68+
* Handles shell execution completion for this terminal.
69+
* @param exitDetails The exit details of the shell execution
70+
*/
71+
public shellExecutionComplete(exitDetails: ExitCodeDetails) {
72+
this.busy = false
73+
this.running = false
74+
75+
if (this.process) {
76+
// Add to the front of the queue (most recent first).
77+
if (this.process.hasUnretrievedOutput()) {
78+
this.completedProcesses.unshift(this.process)
79+
}
80+
81+
this.process.emit("shell_execution_complete", exitDetails)
82+
this.process = undefined
83+
}
84+
}
85+
86+
public get isStreamClosed(): boolean {
87+
return this.streamClosed
88+
}
89+
90+
/**
91+
* Gets the last executed command
92+
* @returns The last command string or empty string if none
93+
*/
94+
public getLastCommand(): string {
95+
// Return the command from the active process or the most recent process in the queue
96+
if (this.process) {
97+
return this.process.command || ""
98+
} else if (this.completedProcesses.length > 0) {
99+
return this.completedProcesses[0].command || ""
100+
}
101+
102+
return ""
103+
}
104+
105+
/**
106+
* Cleans the process queue by removing processes that no longer have unretrieved output
107+
* or don't belong to the current task
108+
*/
109+
public cleanCompletedProcessQueue(): void {
110+
// Keep only processes with unretrieved output
111+
this.completedProcesses = this.completedProcesses.filter((process) => process.hasUnretrievedOutput())
112+
}
113+
114+
/**
115+
* Gets all processes with unretrieved output
116+
* @returns Array of processes with unretrieved output
117+
*/
118+
public getProcessesWithOutput(): RooTerminalProcess[] {
119+
// Clean the queue first to remove any processes without output
120+
this.cleanCompletedProcessQueue()
121+
return [...this.completedProcesses]
122+
}
123+
124+
/**
125+
* Gets all unretrieved output from both active and completed processes
126+
* @returns Combined unretrieved output from all processes
127+
*/
128+
public getUnretrievedOutput(): string {
129+
let output = ""
130+
131+
// First check completed processes to maintain chronological order
132+
for (const process of this.completedProcesses) {
133+
const processOutput = process.getUnretrievedOutput()
134+
135+
if (processOutput) {
136+
output += processOutput
137+
}
138+
}
139+
140+
// Then check active process for most recent output
141+
const activeOutput = this.process?.getUnretrievedOutput()
142+
143+
if (activeOutput) {
144+
output += activeOutput
145+
}
146+
147+
this.cleanCompletedProcessQueue()
148+
return output
149+
}
150+
151+
public static defaultShellIntegrationTimeout = 5_000
152+
private static shellIntegrationTimeout: number = BaseTerminal.defaultShellIntegrationTimeout
153+
private static commandDelay: number = 0
154+
private static powershellCounter: boolean = false
155+
private static terminalZshClearEolMark: boolean = true
156+
private static terminalZshOhMy: boolean = false
157+
private static terminalZshP10k: boolean = false
158+
private static terminalZdotdir: boolean = false
159+
160+
/**
161+
* Compresses terminal output by applying run-length encoding and truncating to line limit
162+
* @param input The terminal output to compress
163+
* @returns The compressed terminal output
164+
*/
165+
public static setShellIntegrationTimeout(timeoutMs: number): void {
166+
BaseTerminal.shellIntegrationTimeout = timeoutMs
167+
}
168+
169+
public static getShellIntegrationTimeout(): number {
170+
return Math.min(BaseTerminal.shellIntegrationTimeout, BaseTerminal.defaultShellIntegrationTimeout)
171+
}
172+
173+
/**
174+
* Sets the command delay in milliseconds
175+
* @param delayMs The delay in milliseconds
176+
*/
177+
public static setCommandDelay(delayMs: number): void {
178+
BaseTerminal.commandDelay = delayMs
179+
}
180+
181+
/**
182+
* Gets the command delay in milliseconds
183+
* @returns The command delay in milliseconds
184+
*/
185+
public static getCommandDelay(): number {
186+
return BaseTerminal.commandDelay
187+
}
188+
189+
/**
190+
* Sets whether to use the PowerShell counter workaround
191+
* @param enabled Whether to enable the PowerShell counter workaround
192+
*/
193+
public static setPowershellCounter(enabled: boolean): void {
194+
BaseTerminal.powershellCounter = enabled
195+
}
196+
197+
/**
198+
* Gets whether to use the PowerShell counter workaround
199+
* @returns Whether the PowerShell counter workaround is enabled
200+
*/
201+
public static getPowershellCounter(): boolean {
202+
return BaseTerminal.powershellCounter
203+
}
204+
205+
/**
206+
* Sets whether to clear the ZSH EOL mark
207+
* @param enabled Whether to clear the ZSH EOL mark
208+
*/
209+
public static setTerminalZshClearEolMark(enabled: boolean): void {
210+
BaseTerminal.terminalZshClearEolMark = enabled
211+
}
212+
213+
/**
214+
* Gets whether to clear the ZSH EOL mark
215+
* @returns Whether the ZSH EOL mark clearing is enabled
216+
*/
217+
public static getTerminalZshClearEolMark(): boolean {
218+
return BaseTerminal.terminalZshClearEolMark
219+
}
220+
221+
/**
222+
* Sets whether to enable Oh My Zsh shell integration
223+
* @param enabled Whether to enable Oh My Zsh shell integration
224+
*/
225+
public static setTerminalZshOhMy(enabled: boolean): void {
226+
BaseTerminal.terminalZshOhMy = enabled
227+
}
228+
229+
/**
230+
* Gets whether Oh My Zsh shell integration is enabled
231+
* @returns Whether Oh My Zsh shell integration is enabled
232+
*/
233+
public static getTerminalZshOhMy(): boolean {
234+
return BaseTerminal.terminalZshOhMy
235+
}
236+
237+
/**
238+
* Sets whether to enable Powerlevel10k shell integration
239+
* @param enabled Whether to enable Powerlevel10k shell integration
240+
*/
241+
public static setTerminalZshP10k(enabled: boolean): void {
242+
BaseTerminal.terminalZshP10k = enabled
243+
}
244+
245+
/**
246+
* Gets whether Powerlevel10k shell integration is enabled
247+
* @returns Whether Powerlevel10k shell integration is enabled
248+
*/
249+
public static getTerminalZshP10k(): boolean {
250+
return BaseTerminal.terminalZshP10k
251+
}
252+
253+
public static compressTerminalOutput(input: string, lineLimit: number): string {
254+
return truncateOutput(applyRunLengthEncoding(input), lineLimit)
255+
}
256+
257+
/**
258+
* Sets whether to enable ZDOTDIR handling for zsh
259+
* @param enabled Whether to enable ZDOTDIR handling
260+
*/
261+
public static setTerminalZdotdir(enabled: boolean): void {
262+
BaseTerminal.terminalZdotdir = enabled
263+
}
264+
265+
/**
266+
* Gets whether ZDOTDIR handling is enabled
267+
* @returns Whether ZDOTDIR handling is enabled
268+
*/
269+
public static getTerminalZdotdir(): boolean {
270+
return BaseTerminal.terminalZdotdir
271+
}
17272
}

src/integrations/terminal/ExecaTerminal.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ import { ExecaTerminalProcess } from "./ExecaTerminalProcess"
44
import { mergePromise } from "./mergePromise"
55

66
export class ExecaTerminal extends BaseTerminal {
7+
constructor(id: number, cwd: string) {
8+
super("execa", id, cwd)
9+
}
10+
11+
public override isClosed(): boolean {
12+
return false
13+
}
14+
715
public override runCommand(command: string, callbacks: RooTerminalCallbacks): RooTerminalProcessResultPromise {
816
const process = new ExecaTerminalProcess(this)
917
process.command = command

0 commit comments

Comments
 (0)