Skip to content

Commit 717c9df

Browse files
authored
Merge pull request microsoft#209136 from cpendery/fix/improve-marker-placements
fix: improve terminal marker placements on windows
2 parents 6294089 + 3864790 commit 717c9df

File tree

6 files changed

+81
-19
lines changed

6 files changed

+81
-19
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ export interface ICommandDetectionCapability {
182182
readonly onCurrentCommandInvalidated: Event<ICommandInvalidationRequest>;
183183
setContinuationPrompt(value: string): void;
184184
setCwd(value: string): void;
185+
setPromptHeight(value: number): void;
185186
setIsWindowsPty(value: boolean): void;
186187
setIsCommandStorageDisabled(): void;
187188
/**

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ export class TerminalCommand implements ITerminalCommand {
213213

214214
export interface ICurrentPartialCommand {
215215
promptStartMarker?: IMarker;
216+
promptHeight?: number;
216217

217218
commandStartMarker?: IMarker;
218219
commandStartX?: number;
@@ -244,12 +245,23 @@ export interface ICurrentPartialCommand {
244245
*/
245246
isInvalid?: boolean;
246247

248+
/**
249+
* Whether the command start marker has been adjusted on Windows.
250+
*/
251+
isAdjusted?: boolean;
252+
253+
/**
254+
* Whether the command start marker adjustment has been attempt on new terminal input.
255+
*/
256+
isInputAdjusted?: boolean;
257+
247258
getPromptRowCount(): number;
248259
getCommandRowCount(): number;
249260
}
250261

251262
export class PartialTerminalCommand implements ICurrentPartialCommand {
252263
promptStartMarker?: IMarker;
264+
promptHeight?: number;
253265

254266
commandStartMarker?: IMarker;
255267
commandStartX?: number;

src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
154154
};
155155
this._register(this._terminal.onResize(e => this._handleResize(e)));
156156
this._register(this._terminal.onCursorMove(() => this._handleCursorMove()));
157+
this._register(this._terminal.onData(() => this._handleInput()));
157158
}
158159

159160
private _handleResize(e: { cols: number; rows: number }) {
@@ -207,6 +208,10 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
207208
this._cwd = value;
208209
}
209210

211+
setPromptHeight(value: number) {
212+
this._currentCommand.promptHeight = value;
213+
}
214+
210215
setIsWindowsPty(value: boolean) {
211216
if (value && !(this._ptyHeuristics.value instanceof WindowsPtyHeuristics)) {
212217
const that = this;
@@ -280,6 +285,10 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe
280285
return undefined;
281286
}
282287

288+
private _handleInput(): void {
289+
this._ptyHeuristics.value.handleInput();
290+
}
291+
283292
handlePromptStart(options?: IHandleCommandOptions): void {
284293
// Adjust the last command's finished marker when needed. The standard position for the
285294
// finished marker `D` to appear is at the same position as the following prompt started
@@ -499,6 +508,8 @@ class UnixPtyHeuristics extends Disposable {
499508
}));
500509
}
501510

511+
handleInput() { }
512+
502513
handleCommandStart(options?: IHandleCommandOptions) {
503514
this._hooks.commitCommandFinished();
504515

@@ -652,6 +663,28 @@ class WindowsPtyHeuristics extends Disposable {
652663
}
653664
}
654665

666+
/**
667+
* Attempt to adjust the command start marker when input is handled for the first time.
668+
*/
669+
handleInput() {
670+
const currentY = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY;
671+
672+
const hasWrappingInPrompt = Array.from({ length: (this._capability.currentCommand.promptHeight ?? 0) + 1 }, (_, i) => currentY - i).find(y => this._terminal.buffer.active.getLine(y)?.isWrapped) !== undefined;
673+
const hasActiveCommand = this._capability.currentCommand.commandStartX !== undefined && this._capability.currentCommand.commandExecutedX === undefined;
674+
const hasAdjusted = this._capability.currentCommand.isAdjusted === true || this._capability.currentCommand.isInputAdjusted === true;
675+
676+
if (!hasActiveCommand || hasAdjusted || hasWrappingInPrompt) {
677+
return;
678+
}
679+
this._capability.currentCommand.isInputAdjusted = true;
680+
this._logService.debug('CommandDetectionCapability#handleInput attempting start marker adjustment');
681+
682+
this._tryAdjustCommandStartMarkerScannedLineCount = 0;
683+
this._tryAdjustCommandStartMarkerPollCount = 0;
684+
this._tryAdjustCommandStartMarkerScheduler = new RunOnceScheduler(() => this._tryAdjustCommandStartMarker(this._terminal.registerMarker(0)!), AdjustCommandStartMarkerConstants.Interval);
685+
this._tryAdjustCommandStartMarkerScheduler.schedule();
686+
}
687+
655688
handleCommandStart() {
656689
this._capability.currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
657690

@@ -713,21 +746,24 @@ class WindowsPtyHeuristics extends Disposable {
713746
if (prompt) {
714747
const adjustedPrompt = typeof prompt === 'string' ? prompt : prompt.prompt;
715748
this._capability.currentCommand.commandStartMarker = this._terminal.registerMarker(0)!;
716-
if (typeof prompt === 'object' && prompt.likelySingleLine) {
717-
this._logService.debug('CommandDetectionCapability#_tryAdjustCommandStartMarker adjusted promptStart', `${this._capability.currentCommand.promptStartMarker?.line} -> ${this._capability.currentCommand.commandStartMarker.line}`);
718-
this._capability.currentCommand.promptStartMarker?.dispose();
719-
this._capability.currentCommand.promptStartMarker = cloneMarker(this._terminal, this._capability.currentCommand.commandStartMarker);
720-
// Adjust the last command if it's not in the same position as the following
721-
// prompt start marker
722-
const lastCommand = this._capability.commands.at(-1);
723-
if (lastCommand && this._capability.currentCommand.commandStartMarker.line !== lastCommand.endMarker?.line) {
724-
lastCommand.endMarker?.dispose();
725-
lastCommand.endMarker = cloneMarker(this._terminal, this._capability.currentCommand.commandStartMarker);
726-
}
749+
750+
// Adjust the prompt start marker to the command start marker
751+
this._logService.debug('CommandDetectionCapability#_tryAdjustCommandStartMarker adjusted promptStart', `${this._capability.currentCommand.promptStartMarker?.line} -> ${this._capability.currentCommand.commandStartMarker.line}`);
752+
this._capability.currentCommand.promptStartMarker?.dispose();
753+
this._capability.currentCommand.promptStartMarker = cloneMarker(this._terminal, this._capability.currentCommand.commandStartMarker, -((this._capability.currentCommand.promptHeight ?? 1) - 1));
754+
755+
// Adjust the last command if it's not in the same position as the following
756+
// prompt start marker
757+
const lastCommand = this._capability.commands.at(-1);
758+
if (lastCommand && this._capability.currentCommand.commandStartMarker.line !== lastCommand.endMarker?.line) {
759+
lastCommand.endMarker?.dispose();
760+
lastCommand.endMarker = cloneMarker(this._terminal, this._capability.currentCommand.commandStartMarker, -((this._capability.currentCommand.promptHeight ?? 1) - 1));
727761
}
762+
728763
// use the regex to set the position as it's possible input has occurred
729764
this._capability.currentCommand.commandStartX = adjustedPrompt.length;
730765
this._logService.debug('CommandDetectionCapability#_tryAdjustCommandStartMarker adjusted commandStart', `${start.line} -> ${this._capability.currentCommand.commandStartMarker.line}:${this._capability.currentCommand.commandStartX}`);
766+
this._capability.currentCommand.isAdjusted = true;
731767
this._flushPendingHandleCommandStartTask();
732768
return;
733769
}
@@ -1049,5 +1085,7 @@ function getXtermLineContent(buffer: IBuffer, lineStart: number, lineEnd: number
10491085
}
10501086

10511087
function cloneMarker(xterm: Terminal, marker: IXtermMarker, offset: number = 0): IXtermMarker | undefined {
1052-
return xterm.registerMarker(marker.line - (xterm.buffer.active.baseY + xterm.buffer.active.cursorY) + offset);
1088+
const cursorY = xterm.buffer.active.baseY + xterm.buffer.active.cursorY;
1089+
const cursorYOffset = marker.line - cursorY + offset;
1090+
return xterm.registerMarker((cursorY + cursorYOffset) < 0 ? -cursorY : cursorYOffset);
10531091
}

src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
403403
this.capabilities.get(TerminalCapability.CommandDetection)?.setIsCommandStorageDisabled();
404404
return true;
405405
}
406+
case 'PromptHeight': {
407+
this.capabilities.get(TerminalCapability.CommandDetection)?.setPromptHeight(parseInt(value));
408+
return true;
409+
}
406410
}
407411
}
408412
case VSCodeOscPt.SetMark: {

src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ __vsc_update_cwd() {
182182
builtin printf '\e]633;P;Cwd=%s\a' "$(__vsc_escape_value "$__vsc_cwd")"
183183
}
184184

185+
__vsc_update_prompt_height() {
186+
__vsc_prompt_height="$(("$(builtin printf "%s" "${PS1@P}" | wc -l)" + 1))"
187+
builtin printf '\e]633;P;PromptHeight=%s\a' "$(__vsc_escape_value "$__vsc_prompt_height")"
188+
}
189+
185190
__vsc_command_output_start() {
186191
builtin printf '\e]633;E;%s;%s\a' "$(__vsc_escape_value "${__vsc_current_command}")" $__vsc_nonce
187192
builtin printf '\e]633;C\a'
@@ -229,6 +234,7 @@ __vsc_precmd() {
229234
__vsc_command_complete "$__vsc_status"
230235
__vsc_current_command=""
231236
__vsc_update_prompt
237+
__vsc_update_prompt_height
232238
__vsc_first_prompt=1
233239
}
234240

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,12 @@ if ($env:VSCODE_ENV_APPEND) {
5252
function Global:__VSCode-Escape-Value([string]$value) {
5353
# NOTE: In PowerShell v6.1+, this can be written `$value -replace '…', { … }` instead of `[regex]::Replace`.
5454
# Replace any non-alphanumeric characters.
55-
$Result = [regex]::Replace($value, '[\\\n;]', { param($match)
55+
[regex]::Replace($value, "[$([char]0x1b)\\\n;]", { param($match)
5656
# Encode the (ascii) matches as `\x<hex>`
5757
-Join (
5858
[System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ }
5959
)
6060
})
61-
# `e is only availabel in pwsh 6+
62-
if ($PSVersionTable.PSVersion.Major -lt 6) {
63-
$Result = $Result -replace "`e", '\x1b'
64-
}
65-
$Result
6661
}
6762

6863
function Global:Prompt() {
@@ -95,7 +90,13 @@ function Global:Prompt() {
9590
Write-Error "failure" -ea ignore
9691
}
9792
# Run the original prompt
98-
$Result += $Global:__VSCodeOriginalPrompt.Invoke()
93+
$OriginalPrompt += $Global:__VSCodeOriginalPrompt.Invoke()
94+
$Result += $OriginalPrompt
95+
96+
# Prompt height
97+
# OSC 633 ; <Property>=<Value> ST
98+
$Result += "$([char]0x1b)]633;P;PromptHeight=$(__VSCode-Escape-Value ($OriginalPrompt -Split '\n').Count)`a"
99+
99100
# Write command started
100101
$Result += "$([char]0x1b)]633;B`a"
101102
$Global:__LastHistoryId = $LastHistoryEntry.Id

0 commit comments

Comments
 (0)