1
>
/*---------------------------------------------------------------------------------------------
promptInputModel.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import type { IBuffer, IBufferCell, IBufferLine, IMarker, Terminal } from '@xterm/headless';
7
>
import { throttle } from '../../../../../base/common/decorators.js';
8
>
import { Emitter, Event } from '../../../../../base/common/event.js';
9
>
import { Disposable } from '../../../../../base/common/lifecycle.js';
10
>
import { ILogService, LogLevel } from '../../../../log/common/log.js';
11
>
import { PosixShellType, TerminalShellType } from '../../terminal.js';
12
>
import type { ITerminalCommand } from '../capabilities.js';
13
>
14
>
export const enum PromptInputState {
15
>
Unknown = 0,
16
>
Input = 1,
17
>
Execute = 2,
18
>
}
19
>
20
>
/**
21
>
* A model of the prompt input state using shell integration and analyzing the terminal buffer. This
22
>
* may not be 100% accurate but provides a best guess.
23
>
*/
24
>
export interface IPromptInputModel extends IPromptInputModelState {
25
>
readonly state: PromptInputState;
26
>
27
>
readonly onDidStartInput: Event<IPromptInputModelState>;
28
>
readonly onDidChangeInput: Event<IPromptInputModelState>;
29
>
readonly onDidFinishInput: Event<IPromptInputModelState>;
30
>
/**
31
>
* Fires immediately before {@link onDidFinishInput} when a SIGINT/Ctrl+C/^C is detected.
32
>
*/
33
>
readonly onDidInterrupt: Event<IPromptInputModelState>;
34
>
35
>
/**
36
>
* Gets the prompt input as a user-friendly string where `|` is the cursor position and `[` and
37
>
* `]` wrap any ghost text.
38
>
*
39
>
* @param emptyStringWhenEmpty If true, an empty string is returned when the prompt input is
40
>
* empty (as opposed to '|').
41
>
*/
42
>
getCombinedString(emptyStringWhenEmpty?: boolean): string;
43
>
44
>
setShellType(shellType?: TerminalShellType): void;
45
>
}
46
>
47
>
export interface IPromptInputModelState {
48
>
/**
49
>
* The full prompt input include ghost text.
50
>
*/
51
>
readonly value: string;
52
>
/**
53
>
* The prompt input up to the cursor index, this will always exclude the ghost text.
54
>
*/
55
>
readonly prefix: string;
56
>
/**
57
>
* The prompt input from the cursor to the end, this _does not_ include ghost text.
58
>
*/
59
>
readonly suffix: string;
60
>
/**
61
>
* The index of the cursor in {@link value}.
62
>
*/
63
>
readonly cursorIndex: number;
64
>
/**
65
>
* The index of the start of ghost text in {@link value}. This is -1 when there is no ghost
66
>
* text.
67
>
*/
68
>
readonly ghostTextIndex: number;
69
>
}
70
>
71
>
export interface ISerializedPromptInputModel {
72
>
readonly modelState: IPromptInputModelState;
73
>
readonly commandStartX: number;
74
>
readonly lastPromptLine: string | undefined;
75
>
readonly continuationPrompt: string | undefined;
76
>
readonly lastUserInput: string;
77
>
}
78
>
79
>
export class PromptInputModel extends Disposable implements IPromptInputModel {
80
>
private _state: PromptInputState = PromptInputState.Unknown;
81
>
get state() { return this._state; }
82
>
83
>
private _commandStartMarker: IMarker | undefined;
84
>
private _commandStartX: number = 0;
85
>
private _lastPromptLine: string | undefined;
86
>
private _continuationPrompt: string | undefined;
87
>
private _shellType: TerminalShellType | undefined;
88
>
89
>
private _lastUserInput: string = '';
90
>
91
>
private _value: string = '';
92
>
get value() { return this._value; }
93
>
get prefix() { return this._value.substring(0, this._cursorIndex); }
94
>
get suffix() { return this._value.substring(this._cursorIndex, this._ghostTextIndex === -1 ? undefined : this._ghostTextIndex); }
95
>
96
>
private _cursorIndex: number = 0;
97
>
get cursorIndex() { return this._cursorIndex; }
98
>
99
>
private _ghostTextIndex: number = -1;
100
>
get ghostTextIndex() { return this._ghostTextIndex; }
101
>
102
>
private readonly _onDidStartInput = this._register(new Emitter<IPromptInputModelState>());
103
>
readonly onDidStartInput = this._onDidStartInput.event;
104
>
private readonly _onDidChangeInput = this._register(new Emitter<IPromptInputModelState>());
105
>
readonly onDidChangeInput = this._onDidChangeInput.event;
106
>
private readonly _onDidFinishInput = this._register(new Emitter<IPromptInputModelState>());
107
>
readonly onDidFinishInput = this._onDidFinishInput.event;
108
>
private readonly _onDidInterrupt = this._register(new Emitter<IPromptInputModelState>());
109
>
readonly onDidInterrupt = this._onDidInterrupt.event;
110
>
111
>
constructor(
112
>
private readonly _xterm: Terminal,
113
>
onCommandStart: Event<ITerminalCommand>,
114
>
onCommandStartChanged: Event<void>,
115
>
onCommandExecuted: Event<ITerminalCommand>,
116
>
onCommandFinished: Event<ITerminalCommand>,
117
>
@ILogService private readonly _logService: ILogService
118
>
) {
119
>
super();
120
>
121
>
this._register(Event.any(
122
>
this._xterm.onCursorMove,
123
>
this._xterm.onData,
124
>
this._xterm.onWriteParsed,
125
>
)(() => this._sync()));
126
>
this._register(this._xterm.onData(e => this._handleUserInput(e)));
127
>
128
>
this._register(onCommandStart(e => this._handleCommandStart(e as { marker: IMarker })));
129
>
this._register(onCommandStartChanged(() => this._handleCommandStartChanged()));
130
>
this._register(onCommandExecuted(() => this._handleCommandExecuted()));
131
>
this._register(onCommandFinished(() => this._handleCommandFinished()));
132
>
133
>
this._register(this.onDidStartInput(() => this._logCombinedStringIfTrace('PromptInputModel#onDidStartInput')));
134
>
this._register(this.onDidChangeInput(() => this._logCombinedStringIfTrace('PromptInputModel#onDidChangeInput')));
135
>
this._register(this.onDidFinishInput(() => this._logCombinedStringIfTrace('PromptInputModel#onDidFinishInput')));
136
>
this._register(this.onDidInterrupt(() => this._logCombinedStringIfTrace('PromptInputModel#onDidInterrupt')));
137
>
}
138
>
139
>
private _logCombinedStringIfTrace(message: string) {
140
>
// Only generate the combined string if trace
141
>
if (this._logService.getLevel() === LogLevel.Trace) {
142
this._logService.trace(message, this.getCombinedString());
143
}
145
>
146
>
setShellType(shellType: TerminalShellType): void {
147
this._shellType = shellType;
148
}
150
>
setContinuationPrompt(value: string): void {
151
this._continuationPrompt = value;
152
this._sync();
153
}
155
>
setLastPromptLine(value: string): void {
156
this._lastPromptLine = value;
157
this._sync();
158
}
160
>
setConfidentCommandLine(value: string): void {
161
if (this._value !== value) {
162
this._value = value;