Skip to content

Commit 432af8d

Browse files
committed
Fix pwsh continuation serializing, stricter continuation
1 parent c7cf579 commit 432af8d

File tree

5 files changed

+67
-87
lines changed

5 files changed

+67
-87
lines changed

src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class PromptInputModel extends Disposable implements IPromptInputModel {
6565
}
6666

6767
setContinuationPrompt(value: string): void {
68+
console.log('setContinuationPrompt', value);
6869
this._continuationPrompt = value;
6970
}
7071

@@ -115,33 +116,37 @@ export class PromptInputModel extends Disposable implements IPromptInputModel {
115116
this._value = commandLine.substring(this._commandStartX);
116117
this._cursorIndex = Math.max(buffer.cursorX - this._commandStartX, 0);
117118

119+
// IDEA: Reinforce knowledge of prompt to avoid incorrect commandStart
120+
// IDEA: Detect ghost text based on SGR and cursor
121+
118122
// Add multi-lines
119123
const absoluteCursorY = buffer.baseY + buffer.cursorY;
120124
for (let y = commandStartY + 1; y <= absoluteCursorY; y++) {
121125
let lineText = buffer.getLine(y)?.translateToString(true);
122126
if (lineText) {
123-
if (this._continuationPrompt && lineText.startsWith(this._continuationPrompt)) {
124-
lineText = lineText.substring(this._continuationPrompt.length);
125-
}
126-
this._value += `\n${lineText}`;
127-
if (y === absoluteCursorY) {
128-
// TODO: Detect continuation
129-
// For pwsh: (Get-PSReadLineOption).ContinuationPrompt
130-
// TODO: Wide/emoji length support
131-
this._cursorIndex = Math.max(this._value.length - lineText.length - (this._continuationPrompt?.length ?? 0) + buffer.cursorX, 0);
127+
// Verify continuation prompt if we have it, if this line doesn't have it then the
128+
// user likely just pressed enter
129+
if (this._continuationPrompt === undefined || this._lineContainsContinuationPrompt(lineText)) {
130+
lineText = this._trimContinuationPrompt(lineText);
131+
this._value += `\n${lineText}`;
132+
if (y === absoluteCursorY) {
133+
// TODO: Wide/emoji length support
134+
this._cursorIndex = Math.max(this._value.length - lineText.length - (this._continuationPrompt?.length ?? 0) + buffer.cursorX, 0);
135+
}
136+
} else {
137+
this._cursorIndex = this._value.length;
138+
break;
132139
}
133140
}
134141
}
135142

136-
// TODO: Check below the cursor for continuations
143+
// Check lines below the cursor for continuations
137144
for (let y = absoluteCursorY + 1; y < buffer.baseY + this._xterm.rows; y++) {
138-
let lineText = buffer.getLine(y)?.translateToString(true);
139-
if (lineText) {
140-
// TODO: Detect line continuation if it's not set
141-
if (this._continuationPrompt && lineText.startsWith(this._continuationPrompt)) {
142-
lineText = lineText.substring(this._continuationPrompt.length);
143-
}
144-
this._value += `\n${lineText}`;
145+
const lineText = buffer.getLine(y)?.translateToString(true);
146+
if (lineText && this._lineContainsContinuationPrompt(lineText)) {
147+
this._value += `\n${this._trimContinuationPrompt(lineText)}`;
148+
} else {
149+
break;
145150
}
146151
}
147152

@@ -151,4 +156,16 @@ export class PromptInputModel extends Disposable implements IPromptInputModel {
151156

152157
this._onDidChangeInput.fire();
153158
}
159+
160+
private _trimContinuationPrompt(lineText: string): string {
161+
// TODO: Detect line continuation if it's not set
162+
if (this._lineContainsContinuationPrompt(lineText)) {
163+
lineText = lineText.substring(this._continuationPrompt!.length);
164+
}
165+
return lineText;
166+
}
167+
168+
private _lineContainsContinuationPrompt(lineText: string): boolean {
169+
return !!(this._continuationPrompt && lineText.startsWith(this._continuationPrompt));
170+
}
154171
}

src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
7+
import type { PromptInputModel } from 'vs/platform/terminal/common/capabilities/commandDetection/promptInputModel';
78

89
suite('RequestStore', () => {
910
const store = ensureNoDisposablesAreLeakedInTestSuite();
11+
let promptInputModel: PromptInputModel;
1012

1113
setup(() => {
12-
instantiationService = new TestInstantiationService();
13-
instantiationService.stub(ILogService, new LogService(new ConsoleLogger()));
14+
1415
});
1516
});

src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function Global:__VSCode-Escape-Value([string]$value) {
5757
-Join (
5858
[System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ }
5959
)
60-
})
60+
}) -replace "`e", '\x1b'
6161
}
6262

6363
function Global:Prompt() {

src/vs/workbench/contrib/terminalContrib/developer/browser/media/developer.css

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,6 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
.monaco-workbench .xterm.dev-mode .xterm-helper-textarea {
7-
z-index: 36 !important;
8-
opacity: 0.8 !important;
9-
right: 0 !important;
10-
left: initial !important;
11-
width: 50% !important;
12-
background-color: var(--vscode-terminal-background, var(--vscode-panel-background));
13-
color: var(--vscode-terminal-foreground);
14-
transform: translateY(-100%);
15-
}
16-
.monaco-workbench .xterm.dev-mode .xterm-helper-textarea:focus {
17-
opacity: 0.8 !important;
18-
}
19-
.monaco-workbench .xterm.dev-mode .xterm-helpers {
20-
/* This could maybe be done outside of .dev-mode, but I'm scared to break something */
21-
left: 0;
22-
right: 0;
23-
}
24-
.monaco-workbench .xterm.dev-mode .xterm-helper-textarea:hover {
25-
opacity: 0.25 !important;
26-
}
27-
286
.monaco-workbench .xterm.dev-mode .xterm-sequence-decoration {
297
background-color: var(--vscode-terminal-background, var(--vscode-panel-background));
308
display: none !important;

src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
1616
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
1717
import { ITerminalLogService, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
1818
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
19-
import { IInternalXtermTerminal, ITerminalConfigurationService, ITerminalContribution, ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
19+
import { IInternalXtermTerminal, ITerminalContribution, ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
2020
import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
2121
import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions';
2222
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
2323
import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
2424
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
2525
import type { Terminal } from '@xterm/xterm';
26-
import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
27-
import { getWindow } from 'vs/base/browser/dom';
26+
import { ITerminalCommand, TerminalCapability, type ICommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
2827
import { IStatusbarService, StatusbarAlignment, type IStatusbarEntry, type IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/browser/statusbar';
2928

3029
registerTerminalAction({
@@ -123,14 +122,13 @@ class DevModeContribution extends Disposable implements ITerminalContribution {
123122
private _currentColor = 0;
124123

125124
private _statusbarEntry: IStatusbarEntry | undefined;
126-
private _statusbarEntryAccessor: IStatusbarEntryAccessor | undefined;
125+
private readonly _statusbarEntryAccessor: MutableDisposable<IStatusbarEntryAccessor> = this._register(new MutableDisposable());
127126

128127
constructor(
129128
private readonly _instance: ITerminalInstance,
130129
processManager: ITerminalProcessManager,
131130
widgetManager: TerminalWidgetManager,
132131
@IConfigurationService private readonly _configurationService: IConfigurationService,
133-
@ITerminalConfigurationService private readonly _terminalConfigurationService: ITerminalConfigurationService,
134132
@IStatusbarService private readonly _statusbarService: IStatusbarService,
135133
) {
136134
super();
@@ -139,19 +137,6 @@ class DevModeContribution extends Disposable implements ITerminalContribution {
139137
this._updateDevMode();
140138
}
141139
}));
142-
143-
setTimeout(() => {
144-
const commandDetection = this._instance.capabilities.get(TerminalCapability.CommandDetection)
145-
if (commandDetection) {
146-
// TODO: Listen to capability add
147-
// TODO: Add util for when capability is available?
148-
commandDetection.promptInputModel.onDidChangeInput(() => {
149-
// TODO: Create a status bar item sync
150-
// TODO: Only show focused instance status bar item
151-
this._updateDevMode();
152-
});
153-
}
154-
}, 2000);
155140
}
156141

157142
xtermReady(xterm: IXtermTerminal & { raw: Terminal }): void {
@@ -163,38 +148,16 @@ class DevModeContribution extends Disposable implements ITerminalContribution {
163148
const devMode: boolean = this._isEnabled();
164149
this._xterm?.raw.element?.classList.toggle('dev-mode', devMode);
165150

166-
const promptInputModel = this._instance.capabilities.get(TerminalCapability.CommandDetection)?.promptInputModel
167-
if (promptInputModel) {
168-
// Text area syncing
169-
const promptInput = promptInputModel.value.replaceAll('\n', '\u23CE');
170-
this._statusbarEntry = {
171-
name: localize('terminalDevMode', 'Terminal Dev Mode'),
172-
text: `$(terminal) ${promptInput.substring(0, promptInputModel.cursorIndex)}|${promptInput.substring(promptInputModel.cursorIndex)}`,
173-
// tooltip: localize('nonResponsivePtyHost', "The connection to the terminal's pty host process is unresponsive, terminals may stop working. Click to manually restart the pty host."),
174-
ariaLabel: localize('ptyHostStatus.ariaLabel', 'test'),
175-
// command: TerminalCommandId.RestartPtyHost,
176-
kind: 'warning'
177-
};
178-
if (!this._statusbarEntryAccessor) {
179-
console.log('1');
180-
this._statusbarEntryAccessor = this._statusbarService.addEntry(this._statusbarEntry, 'terminal.promptInput', StatusbarAlignment.LEFT);
181-
} else {
182-
console.log('2');
183-
this._statusbarEntryAccessor.update(this._statusbarEntry);
184-
}
185-
}
186-
if (this._xterm?.raw.textarea) {
187-
const font = this._terminalConfigurationService.getFont(getWindow(this._xterm.raw.textarea));
188-
this._xterm.raw.textarea.style.fontFamily = font.fontFamily;
189-
this._xterm.raw.textarea.style.fontSize = `${font.fontSize}px`;
190-
}
191-
192-
// Sequence markers
193151
const commandDetection = this._instance.capabilities.get(TerminalCapability.CommandDetection);
194152
if (devMode) {
195153
if (commandDetection) {
196154
const commandDecorations = new Map<ITerminalCommand, IDisposable[]>();
197155
this._activeDevModeDisposables.value = combinedDisposable(
156+
// Prompt input
157+
this._instance.onDidBlur(() => this._updateDevMode()),
158+
this._instance.onDidFocus(() => this._updateDevMode()),
159+
commandDetection.promptInputModel.onDidChangeInput(() => this._updateDevMode()),
160+
// Sequence markers
198161
commandDetection.onCommandFinished(command => {
199162
const colorClass = `color-${this._currentColor}`;
200163
const decorations: IDisposable[] = [];
@@ -261,6 +224,8 @@ class DevModeContribution extends Disposable implements ITerminalContribution {
261224
}
262225
})
263226
);
227+
228+
this._updatePromptInputStatusBar(commandDetection);
264229
} else {
265230
this._activeDevModeDisposables.value = this._instance.capabilities.onDidAddCapabilityType(e => {
266231
if (e === TerminalCapability.CommandDetection) {
@@ -276,6 +241,25 @@ class DevModeContribution extends Disposable implements ITerminalContribution {
276241
private _isEnabled(): boolean {
277242
return this._configurationService.getValue(TerminalSettingId.DevMode) || false;
278243
}
244+
245+
private _updatePromptInputStatusBar(commandDetection: ICommandDetectionCapability) {
246+
const promptInputModel = commandDetection.promptInputModel;
247+
if (promptInputModel) {
248+
const promptInput = promptInputModel.value.replaceAll('\n', '\u23CE');
249+
this._statusbarEntry = {
250+
name: localize('terminalDevMode', 'Terminal Dev Mode'),
251+
text: `$(terminal) ${promptInput.substring(0, promptInputModel.cursorIndex)}|${promptInput.substring(promptInputModel.cursorIndex)}`,
252+
ariaLabel: localize('terminalDevMode', 'Terminal Dev Mode'),
253+
kind: 'warning'
254+
};
255+
if (!this._statusbarEntryAccessor.value) {
256+
this._statusbarEntryAccessor.value = this._statusbarService.addEntry(this._statusbarEntry, `terminal.promptInput.${this._instance.instanceId}`, StatusbarAlignment.LEFT);
257+
} else {
258+
this._statusbarEntryAccessor.value.update(this._statusbarEntry);
259+
}
260+
this._statusbarService.updateEntryVisibility(`terminal.promptInput.${this._instance.instanceId}`, this._instance.hasFocus);
261+
}
262+
}
279263
}
280264

281265
registerTerminalContribution(DevModeContribution.ID, DevModeContribution);

0 commit comments

Comments
 (0)