@@ -36,6 +36,115 @@ export async function waitForIdle(onData: Event<unknown>, idleDurationMs: number
36
36
return deferred . p . finally ( ( ) => store . dispose ( ) ) ;
37
37
}
38
38
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
+
39
148
/**
40
149
* Tracks the terminal for being idle on a prompt input. This must be called before `executeCommand`
41
150
* is called.
0 commit comments