@@ -36,6 +36,115 @@ export async function waitForIdle(onData: Event<unknown>, idleDurationMs: number
3636 return deferred . p . finally ( ( ) => store . dispose ( ) ) ;
3737}
3838
39+ export interface IPromptDetectionResult {
40+ /**
41+ * Whether a prompt was detected.
42+ */
43+ detected : boolean ;
44+ /**
45+ * The reason for logging.
46+ */
47+ reason ?: string ;
48+ }
49+
50+ /**
51+ * Detects if the given text content appears to end with a common prompt pattern.
52+ */
53+ export function detectsCommonPromptPattern ( cursorLine : string ) : IPromptDetectionResult {
54+ if ( cursorLine . trim ( ) . length === 0 ) {
55+ return { detected : false , reason : 'Content is empty or contains only whitespace' } ;
56+ }
57+
58+ // PowerShell prompt: PS C:\> or similar patterns
59+ if ( / P S \s + [ A - Z ] : \\ .* > \s * $ / . test ( cursorLine ) ) {
60+ return { detected : true , reason : `PowerShell prompt pattern detected: "${ cursorLine } "` } ;
61+ }
62+
63+ // Command Prompt: C:\path>
64+ if ( / ^ [ A - Z ] : \\ .* > \s * $ / . test ( cursorLine ) ) {
65+ return { detected : true , reason : `Command Prompt pattern detected: "${ cursorLine } "` } ;
66+ }
67+
68+ // Bash-style prompts ending with $
69+ if ( / \$ \s * $ / . test ( cursorLine ) ) {
70+ return { detected : true , reason : `Bash-style prompt pattern detected: "${ cursorLine } "` } ;
71+ }
72+
73+ // Root prompts ending with #
74+ if ( / # \s * $ / . test ( cursorLine ) ) {
75+ return { detected : true , reason : `Root prompt pattern detected: "${ cursorLine } "` } ;
76+ }
77+
78+ // Python REPL prompt
79+ if ( / ^ > > > \s * $ / . test ( cursorLine ) ) {
80+ return { detected : true , reason : `Python REPL prompt pattern detected: "${ cursorLine } "` } ;
81+ }
82+
83+ // Custom prompts ending with the starship character (\u276f)
84+ if ( / \u276f \s * $ / . test ( cursorLine ) ) {
85+ return { detected : true , reason : `Starship prompt pattern detected: "${ cursorLine } "` } ;
86+ }
87+
88+ // Generic prompts ending with common prompt characters
89+ if ( / [ > % ] \s * $ / . test ( cursorLine ) ) {
90+ return { detected : true , reason : `Generic prompt pattern detected: "${ cursorLine } "` } ;
91+ }
92+
93+ return { detected : false , reason : `No common prompt pattern found in last line: "${ cursorLine } "` } ;
94+ }
95+
96+ /**
97+ * Enhanced version of {@link waitForIdle} that uses prompt detection heuristics. After the terminal
98+ * idles for the specified period, checks if the terminal's cursor line looks like a common prompt.
99+ * If not, extends the timeout to give the command more time to complete.
100+ */
101+ export async function waitForIdleWithPromptHeuristics (
102+ onData : Event < unknown > ,
103+ instance : ITerminalInstance ,
104+ idlePollIntervalMs : number ,
105+ extendedTimeoutMs : number ,
106+ ) : Promise < IPromptDetectionResult > {
107+ await waitForIdle ( onData , idlePollIntervalMs ) ;
108+
109+ const xterm = await instance . xtermReadyPromise ;
110+ if ( ! xterm ) {
111+ return { detected : false , reason : `Xterm not available, using ${ idlePollIntervalMs } ms timeout` } ;
112+ }
113+ const startTime = Date . now ( ) ;
114+
115+ // Attempt to detect a prompt pattern after idle
116+ while ( Date . now ( ) - startTime < extendedTimeoutMs ) {
117+ try {
118+ let content = '' ;
119+ const buffer = xterm . raw . buffer . active ;
120+ const line = buffer . getLine ( buffer . baseY + buffer . cursorY ) ;
121+ if ( line ) {
122+ content = line . translateToString ( true ) ;
123+ }
124+ const promptResult = detectsCommonPromptPattern ( content ) ;
125+ if ( promptResult . detected ) {
126+ return promptResult ;
127+ }
128+ } catch ( error ) {
129+ // Continue polling even if there's an error reading terminal content
130+ }
131+ await waitForIdle ( onData , Math . min ( idlePollIntervalMs , extendedTimeoutMs - ( Date . now ( ) - startTime ) ) ) ;
132+ }
133+
134+ // Extended timeout reached without detecting a prompt
135+ try {
136+ let content = '' ;
137+ const buffer = xterm . raw . buffer . active ;
138+ const line = buffer . getLine ( buffer . baseY + buffer . cursorY ) ;
139+ if ( line ) {
140+ content = line . translateToString ( true ) + '\n' ;
141+ }
142+ return { detected : false , reason : `Extended timeout reached without prompt detection. Last line: "${ content . trim ( ) } "` } ;
143+ } catch ( error ) {
144+ return { detected : false , reason : `Extended timeout reached. Error reading terminal content: ${ error } ` } ;
145+ }
146+ }
147+
39148/**
40149 * Tracks the terminal for being idle on a prompt input. This must be called before `executeCommand`
41150 * is called.
0 commit comments