@@ -7,11 +7,11 @@ import { Emitter, type Event } from 'vs/base/common/event';
7
7
import { Disposable } from 'vs/base/common/lifecycle' ;
8
8
import { ILogService , LogLevel } from 'vs/platform/log/common/log' ;
9
9
import type { ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities' ;
10
+ import { debounce } from 'vs/base/common/decorators' ;
10
11
11
12
// Importing types is safe in any layer
12
13
// eslint-disable-next-line local/code-import-patterns
13
- import type { Terminal , IMarker } from '@xterm/headless' ;
14
- import { debounce } from 'vs/base/common/decorators' ;
14
+ import type { Terminal , IMarker , IBufferLine , IBuffer } from '@xterm/headless' ;
15
15
16
16
const enum PromptInputState {
17
17
Unknown ,
@@ -75,6 +75,8 @@ export class PromptInputModel extends Disposable implements IPromptInputModel {
75
75
this . _state = PromptInputState . Input ;
76
76
this . _commandStartMarker = command . marker ;
77
77
this . _commandStartX = this . _xterm . buffer . active . cursorX ;
78
+ this . _value = '' ;
79
+ this . _cursorIndex = 0 ;
78
80
this . _onDidStartInput . fire ( ) ;
79
81
}
80
82
@@ -102,49 +104,53 @@ export class PromptInputModel extends Disposable implements IPromptInputModel {
102
104
}
103
105
104
106
const commandStartY = this . _commandStartMarker ?. line ;
105
- if ( ! commandStartY ) {
107
+ if ( commandStartY === undefined ) {
106
108
return ;
107
109
}
108
110
109
111
const buffer = this . _xterm . buffer . active ;
110
- const commandLine = buffer . getLine ( commandStartY ) ?. translateToString ( true ) ;
111
- if ( ! commandLine ) {
112
+ let line = buffer . getLine ( commandStartY ) ;
113
+ const commandLine = line ?. translateToString ( true , this . _commandStartX ) ;
114
+ if ( ! commandLine || ! line ) {
112
115
this . _logService . trace ( `PromptInputModel#_sync: no line` ) ;
113
116
return ;
114
117
}
115
118
116
119
// Command start line
117
- this . _value = commandLine . substring ( this . _commandStartX ) ;
118
- this . _cursorIndex = Math . max ( buffer . cursorX - this . _commandStartX , 0 ) ;
120
+ this . _value = commandLine ;
121
+
122
+ // Get cursor index
123
+ const absoluteCursorY = buffer . baseY + buffer . cursorY ;
124
+ this . _cursorIndex = absoluteCursorY === commandStartY ? this . _getRelativeCursorIndex ( this . _commandStartX , buffer , line ) : commandLine . length + 1 ;
119
125
120
- // IDEA: Reinforce knowledge of prompt to avoid incorrect commandStart
121
- // IDEA: Detect ghost text based on SGR and cursor
126
+ // IDEA: Detect ghost text based on SGR and cursor. This might work by checking for italic
127
+ // or dim only to avoid false positives from shells that do immediate coloring.
128
+ // IDEA: Detect line continuation if it's not set
122
129
123
130
// From command start line to cursor line
124
- const absoluteCursorY = buffer . baseY + buffer . cursorY ;
125
131
for ( let y = commandStartY + 1 ; y <= absoluteCursorY ; y ++ ) {
126
- let lineText = buffer . getLine ( y ) ?. translateToString ( true ) ;
127
- if ( lineText ) {
132
+ line = buffer . getLine ( y ) ;
133
+ let lineText = line ?. translateToString ( true ) ;
134
+ if ( lineText && line ) {
128
135
// Verify continuation prompt if we have it, if this line doesn't have it then the
129
136
// user likely just pressed enter
130
137
if ( this . _continuationPrompt === undefined || this . _lineContainsContinuationPrompt ( lineText ) ) {
131
138
lineText = this . _trimContinuationPrompt ( lineText ) ;
132
139
this . _value += `\n${ lineText } ` ;
133
- if ( y === absoluteCursorY ) {
134
- // TODO: Wide/emoji length support
135
- this . _cursorIndex = Math . max ( this . _value . length - lineText . length - ( this . _continuationPrompt ?. length ?? 0 ) + buffer . cursorX , 0 ) ;
136
- }
140
+ this . _cursorIndex += ( absoluteCursorY === y
141
+ ? this . _getRelativeCursorIndex ( this . _getContinuationPromptCellWidth ( line , lineText ) , buffer , line )
142
+ : lineText . length + 1 ) ;
137
143
} else {
138
- this . _cursorIndex = this . _value . length ;
139
144
break ;
140
145
}
141
146
}
142
147
}
143
148
144
149
// Below cursor line
145
150
for ( let y = absoluteCursorY + 1 ; y < buffer . baseY + this . _xterm . rows ; y ++ ) {
146
- const lineText = buffer . getLine ( y ) ?. translateToString ( true ) ;
147
- if ( lineText ) {
151
+ line = buffer . getLine ( y ) ;
152
+ const lineText = line ?. translateToString ( true ) ;
153
+ if ( lineText && line ) {
148
154
if ( this . _continuationPrompt === undefined || this . _lineContainsContinuationPrompt ( lineText ) ) {
149
155
this . _value += `\n${ this . _trimContinuationPrompt ( lineText ) } ` ;
150
156
} else {
@@ -161,7 +167,6 @@ export class PromptInputModel extends Disposable implements IPromptInputModel {
161
167
}
162
168
163
169
private _trimContinuationPrompt ( lineText : string ) : string {
164
- // TODO: Detect line continuation if it's not set
165
170
if ( this . _lineContainsContinuationPrompt ( lineText ) ) {
166
171
lineText = lineText . substring ( this . _continuationPrompt ! . length ) ;
167
172
}
@@ -171,4 +176,20 @@ export class PromptInputModel extends Disposable implements IPromptInputModel {
171
176
private _lineContainsContinuationPrompt ( lineText : string ) : boolean {
172
177
return ! ! ( this . _continuationPrompt && lineText . startsWith ( this . _continuationPrompt ) ) ;
173
178
}
179
+
180
+ private _getContinuationPromptCellWidth ( line : IBufferLine , lineText : string ) : number {
181
+ if ( ! this . _continuationPrompt || ! lineText . startsWith ( this . _continuationPrompt ) ) {
182
+ return 0 ;
183
+ }
184
+ let buffer : string = '' ;
185
+ let x = 0 ;
186
+ while ( buffer !== this . _continuationPrompt ) {
187
+ buffer += line . getCell ( x ++ ) ! . getChars ( ) ;
188
+ }
189
+ return x ;
190
+ }
191
+
192
+ private _getRelativeCursorIndex ( startCellX : number , buffer : IBuffer , line : IBufferLine ) : number {
193
+ return line ?. translateToString ( true , startCellX , buffer . cursorX ) . length ?? 0 ;
194
+ }
174
195
}
0 commit comments