3
3
4
4
import convert from "color-convert" ;
5
5
import { IBufferCell , IBufferLine , IMarker , Terminal } from "@xterm/headless" ;
6
- import os from "node:os" ;
7
6
import { getShellPromptRewrites , Shell } from "../utils/shell.js" ;
8
7
import log from "../utils/log.js" ;
9
8
10
- const maxPromptPollDistance = 10 ;
11
-
12
9
type TerminalCommand = {
13
10
promptStartMarker ?: IMarker ;
14
11
promptEndMarker ?: IMarker ;
@@ -27,153 +24,55 @@ export type CommandState = {
27
24
export class CommandManager {
28
25
#activeCommand: TerminalCommand ;
29
26
#terminal: Terminal ;
30
- #previousCommandLines : Set < number > ;
27
+ #acceptedCommandLines : Set < number > ;
31
28
#maxCursorY: number ;
32
29
#shell: Shell ;
33
30
#promptRewrites: boolean ;
34
- readonly #supportsProperOscPlacements = os . platform ( ) !== "win32" ;
35
- promptTerminator : string = "" ;
36
31
37
32
constructor ( terminal : Terminal , shell : Shell ) {
38
33
this . #terminal = terminal ;
39
34
this . #shell = shell ;
40
35
this . #activeCommand = { } ;
41
36
this . #maxCursorY = 0 ;
42
- this . #previousCommandLines = new Set ( ) ;
37
+ this . #acceptedCommandLines = new Set ( ) ;
43
38
this . #promptRewrites = getShellPromptRewrites ( shell ) ;
44
39
45
- if ( this . #supportsProperOscPlacements) {
46
- this . #terminal. parser . registerCsiHandler ( { final : "J" } , ( params ) => {
47
- if ( params . at ( 0 ) == 3 || params . at ( 0 ) == 2 ) {
48
- this . handleClear ( ) ;
49
- }
50
- return false ;
51
- } ) ;
52
- }
40
+ this . #terminal. parser . registerCsiHandler ( { final : "J" } , ( params ) => {
41
+ if ( params . at ( 0 ) == 3 || params . at ( 0 ) == 2 ) {
42
+ this . handleClear ( ) ;
43
+ }
44
+ return false ;
45
+ } ) ;
53
46
}
54
47
handlePromptStart ( ) {
55
48
this . #activeCommand = { promptStartMarker : this . #terminal. registerMarker ( 0 ) , hasOutput : false , cursorTerminated : false } ;
56
49
}
57
50
58
51
handlePromptEnd ( ) {
59
52
if ( this . #activeCommand. promptEndMarker != null ) return ;
53
+ if ( this . #hasBeenAccepted( ) ) {
54
+ this . #activeCommand = { } ;
55
+ return ;
56
+ }
60
57
61
58
this . #activeCommand. promptEndMarker = this . #terminal. registerMarker ( 0 ) ;
62
59
if ( this . #activeCommand. promptEndMarker ?. line === this . #terminal. buffer . active . cursorY ) {
63
60
this . #activeCommand. promptEndX = this . #terminal. buffer . active . cursorX ;
64
61
}
65
- if ( this . #supportsProperOscPlacements) {
66
- this . #activeCommand. promptText = this . #terminal. buffer . active . getLine ( this . #activeCommand. promptEndMarker ?. line ?? 0 ) ?. translateToString ( true ) ;
67
- this . #previousCommandLines. add ( this . #activeCommand. promptEndMarker ?. line ?? - 1 ) ;
68
- }
69
- }
70
62
71
- handleClear ( ) {
72
- this . handlePromptStart ( ) ;
73
- this . #maxCursorY = 0 ;
74
- this . #previousCommandLines = new Set ( ) ;
63
+ this . #activeCommand. promptText = this . #terminal. buffer . active . getLine ( this . #activeCommand. promptEndMarker ?. line ?? 0 ) ?. translateToString ( true ) ;
75
64
}
76
65
77
- private _getWindowsPrompt ( y : number ) {
78
- const line = this . #terminal. buffer . active . getLine ( y ) ;
79
- if ( ! line ) {
80
- return ;
81
- }
82
- const lineText = line . translateToString ( true ) ;
83
- if ( ! lineText ) {
84
- return ;
85
- }
86
-
87
- // dynamic prompt terminator
88
- if ( this . promptTerminator && lineText . trim ( ) . endsWith ( this . promptTerminator ) ) {
89
- const adjustedPrompt = this . _adjustPrompt ( lineText , lineText , this . promptTerminator ) ;
90
- if ( adjustedPrompt ) {
91
- return adjustedPrompt ;
92
- }
93
- }
94
-
95
- // User defined prompt
96
- if ( this . #shell == Shell . Bash ) {
97
- const bashPrompt = lineText . match ( / ^ (?< prompt > \$ \s ? ) / ) ?. groups ?. prompt ;
98
- if ( bashPrompt ) {
99
- const adjustedPrompt = this . _adjustPrompt ( bashPrompt , lineText , "$" ) ;
100
- if ( adjustedPrompt ) {
101
- return adjustedPrompt ;
102
- }
103
- }
104
- }
105
-
106
- if ( this . #shell == Shell . Fish ) {
107
- const fishPrompt = lineText . match ( / (?< prompt > .* > \s ? ) / ) ?. groups ?. prompt ;
108
- if ( fishPrompt ) {
109
- const adjustedPrompt = this . _adjustPrompt ( fishPrompt , lineText , ">" ) ;
110
- if ( adjustedPrompt ) {
111
- return adjustedPrompt ;
112
- }
113
- }
114
- }
115
-
116
- if ( this . #shell == Shell . Nushell ) {
117
- const nushellPrompt = lineText . match ( / (?< prompt > .* > \s ? ) / ) ?. groups ?. prompt ;
118
- if ( nushellPrompt ) {
119
- const adjustedPrompt = this . _adjustPrompt ( nushellPrompt , lineText , ">" ) ;
120
- if ( adjustedPrompt ) {
121
- return adjustedPrompt ;
122
- }
123
- }
124
- }
125
-
126
- if ( this . #shell == Shell . Xonsh ) {
127
- let xonshPrompt = lineText . match ( / (?< prompt > .* @ \s ? ) / ) ?. groups ?. prompt ;
128
- if ( xonshPrompt ) {
129
- const adjustedPrompt = this . _adjustPrompt ( xonshPrompt , lineText , "@" ) ;
130
- if ( adjustedPrompt ) {
131
- return adjustedPrompt ;
132
- }
133
- }
134
-
135
- xonshPrompt = lineText . match ( / (?< prompt > .* > \s ? ) / ) ?. groups ?. prompt ;
136
- if ( xonshPrompt ) {
137
- const adjustedPrompt = this . _adjustPrompt ( xonshPrompt , lineText , ">" ) ;
138
- if ( adjustedPrompt ) {
139
- return adjustedPrompt ;
140
- }
141
- }
142
- }
143
-
144
- if ( this . #shell == Shell . Powershell || this . #shell == Shell . Pwsh ) {
145
- const pwshPrompt = lineText . match ( / (?< prompt > ( \( .+ \) \s ) ? (?: P S .+ > \s ? ) ) / ) ?. groups ?. prompt ;
146
- if ( pwshPrompt ) {
147
- const adjustedPrompt = this . _adjustPrompt ( pwshPrompt , lineText , ">" ) ;
148
- if ( adjustedPrompt ) {
149
- return adjustedPrompt ;
150
- }
151
- }
152
- }
153
-
154
- if ( this . #shell == Shell . Cmd ) {
155
- return lineText . match ( / ^ (?< prompt > ( \( .+ \) \s ) ? (?: [ A - Z ] : \\ .* > ) | ( > ) ) / ) ?. groups ?. prompt ;
156
- }
157
-
158
- // Custom prompts like starship end in the common \u276f character
159
- const customPrompt = lineText . match ( / .* \u276f (? = [ ^ \u276f ] * $ ) / g) ?. [ 0 ] ;
160
- if ( customPrompt ) {
161
- const adjustedPrompt = this . _adjustPrompt ( customPrompt , lineText , "\u276f" ) ;
162
- if ( adjustedPrompt ) {
163
- return adjustedPrompt ;
164
- }
165
- }
66
+ #hasBeenAccepted( ) {
67
+ const commandLine = this . #activeCommand. promptStartMarker ?. line ?? - 1 ;
68
+ const hasBeenAccepted = this . #acceptedCommandLines. has ( commandLine ) && commandLine != - 1 ;
69
+ return this . #promptRewrites && hasBeenAccepted ; // this is a prompt + command that was accepted and is now being re-written by the shell for display purposes (e.g. nu)
166
70
}
167
71
168
- private _adjustPrompt ( prompt : string | undefined , lineText : string , char : string ) : string | undefined {
169
- if ( ! prompt ) {
170
- return ;
171
- }
172
- // Conpty may not 'render' the space at the end of the prompt
173
- if ( lineText === prompt && prompt . endsWith ( char ) ) {
174
- prompt += " " ;
175
- }
176
- return prompt ;
72
+ handleClear ( ) {
73
+ this . handlePromptStart ( ) ;
74
+ this . #maxCursorY = 0 ;
75
+ this . #acceptedCommandLines. clear ( ) ;
177
76
}
178
77
179
78
private _getFgPaletteColor ( cell : IBufferCell | undefined ) : number | undefined {
@@ -205,6 +104,7 @@ export class CommandManager {
205
104
}
206
105
207
106
clearActiveCommand ( ) {
107
+ this . #acceptedCommandLines. add ( this . #activeCommand. promptEndMarker ?. line ?? - 1 ) ;
208
108
this . #activeCommand = { } ;
209
109
}
210
110
@@ -283,7 +183,6 @@ export class CommandManager {
283
183
}
284
184
285
185
const globalCursorPosition = this . #terminal. buffer . active . baseY + this . #terminal. buffer . active . cursorY ;
286
- const withinPollDistance = globalCursorPosition < this . #activeCommand. promptEndMarker . line + 5 ;
287
186
this . #maxCursorY = Math . max ( this . #maxCursorY, globalCursorPosition ) ;
288
187
289
188
if ( globalCursorPosition < this . #activeCommand. promptStartMarker . line || globalCursorPosition < this . #maxCursorY) {
@@ -294,21 +193,6 @@ export class CommandManager {
294
193
295
194
if ( this . #activeCommand. promptEndMarker == null ) return ;
296
195
297
- // if we haven't fond the prompt yet, poll over the next 5 lines searching for it
298
- if ( this . #activeCommand. promptText == null && withinPollDistance ) {
299
- for ( let i = globalCursorPosition ; i < this . #activeCommand. promptEndMarker . line + maxPromptPollDistance ; i ++ ) {
300
- if ( this . #previousCommandLines. has ( i ) && ! this . #promptRewrites) continue ;
301
- const promptResult = this . _getWindowsPrompt ( i ) ;
302
- if ( promptResult != null ) {
303
- this . #activeCommand. promptEndMarker = this . #terminal. registerMarker ( i - globalCursorPosition ) ;
304
- this . #activeCommand. promptEndX = promptResult . length ;
305
- this . #activeCommand. promptText = promptResult ;
306
- this . #previousCommandLines. add ( i ) ;
307
- break ;
308
- }
309
- }
310
- }
311
-
312
196
// if the prompt is set, now parse out the values from the terminal
313
197
if ( this . #activeCommand. promptText != null ) {
314
198
const commandLines = this . _getCommandLines ( ) ;
0 commit comments