33
44import convert from "color-convert" ;
55import { IBufferCell , IBufferLine , IMarker , Terminal } from "@xterm/headless" ;
6- import os from "node:os" ;
76import { getShellPromptRewrites , Shell } from "../utils/shell.js" ;
87import log from "../utils/log.js" ;
98
10- const maxPromptPollDistance = 10 ;
11-
129type TerminalCommand = {
1310 promptStartMarker ?: IMarker ;
1411 promptEndMarker ?: IMarker ;
@@ -27,153 +24,55 @@ export type CommandState = {
2724export class CommandManager {
2825 #activeCommand: TerminalCommand ;
2926 #terminal: Terminal ;
30- #previousCommandLines : Set < number > ;
27+ #acceptedCommandLines : Set < number > ;
3128 #maxCursorY: number ;
3229 #shell: Shell ;
3330 #promptRewrites: boolean ;
34- readonly #supportsProperOscPlacements = os . platform ( ) !== "win32" ;
35- promptTerminator : string = "" ;
3631
3732 constructor ( terminal : Terminal , shell : Shell ) {
3833 this . #terminal = terminal ;
3934 this . #shell = shell ;
4035 this . #activeCommand = { } ;
4136 this . #maxCursorY = 0 ;
42- this . #previousCommandLines = new Set ( ) ;
37+ this . #acceptedCommandLines = new Set ( ) ;
4338 this . #promptRewrites = getShellPromptRewrites ( shell ) ;
4439
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+ } ) ;
5346 }
5447 handlePromptStart ( ) {
5548 this . #activeCommand = { promptStartMarker : this . #terminal. registerMarker ( 0 ) , hasOutput : false , cursorTerminated : false } ;
5649 }
5750
5851 handlePromptEnd ( ) {
5952 if ( this . #activeCommand. promptEndMarker != null ) return ;
53+ if ( this . #hasBeenAccepted( ) ) {
54+ this . #activeCommand = { } ;
55+ return ;
56+ }
6057
6158 this . #activeCommand. promptEndMarker = this . #terminal. registerMarker ( 0 ) ;
6259 if ( this . #activeCommand. promptEndMarker ?. line === this . #terminal. buffer . active . cursorY ) {
6360 this . #activeCommand. promptEndX = this . #terminal. buffer . active . cursorX ;
6461 }
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- }
7062
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 ) ;
7564 }
7665
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)
16670 }
16771
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 ( ) ;
17776 }
17877
17978 private _getFgPaletteColor ( cell : IBufferCell | undefined ) : number | undefined {
@@ -205,6 +104,7 @@ export class CommandManager {
205104 }
206105
207106 clearActiveCommand ( ) {
107+ this . #acceptedCommandLines. add ( this . #activeCommand. promptEndMarker ?. line ?? - 1 ) ;
208108 this . #activeCommand = { } ;
209109 }
210110
@@ -283,7 +183,6 @@ export class CommandManager {
283183 }
284184
285185 const globalCursorPosition = this . #terminal. buffer . active . baseY + this . #terminal. buffer . active . cursorY ;
286- const withinPollDistance = globalCursorPosition < this . #activeCommand. promptEndMarker . line + 5 ;
287186 this . #maxCursorY = Math . max ( this . #maxCursorY, globalCursorPosition ) ;
288187
289188 if ( globalCursorPosition < this . #activeCommand. promptStartMarker . line || globalCursorPosition < this . #maxCursorY) {
@@ -294,21 +193,6 @@ export class CommandManager {
294193
295194 if ( this . #activeCommand. promptEndMarker == null ) return ;
296195
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-
312196 // if the prompt is set, now parse out the values from the terminal
313197 if ( this . #activeCommand. promptText != null ) {
314198 const commandLines = this . _getCommandLines ( ) ;
0 commit comments