Move all Windows-specific logic into standard named functions

Fixes #145391
This commit is contained in:
Daniel Imms
2022-03-18 09:25:51 -07:00
parent 97d358236c
commit 27f1e26959
@@ -73,6 +73,14 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
}
private _handleResize(e: { cols: number; rows: number }) {
if (this._isWindowsPty) {
this._preHandleResizeWindows(e);
}
this._dimensions.cols = e.cols;
this._dimensions.rows = e.rows;
}
private _preHandleResizeWindows(e: { cols: number; rows: number }) {
// Resize behavior is different under conpty; instead of bringing parts of the scrollback
// back into the viewport, new lines are inserted at the bottom (ie. the same behavior as if
// there was no scrollback).
@@ -84,45 +92,41 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
// on the xterm.js buffer to move the markers.
//
// While a bit hacky, this approach is quite safe and seems to work great at least for pwsh.
if (this._isWindowsPty) {
const baseY = this._terminal.buffer.active.baseY;
const rowsDifference = e.rows - this._dimensions.rows;
// Only do when rows increase, do in the next frame as this needs to happen after
// conpty reprints the screen
if (rowsDifference > 0) {
this._waitForCursorMove().then(() => {
// Calculate the number of lines the content may have shifted, this will max out at
// scrollback count since the standard behavior will be used then
const potentialShiftedLineCount = Math.min(rowsDifference, baseY);
// For each command within the viewport, assume commands are in the correct order
for (let i = this.commands.length - 1; i >= 0; i--) {
const command = this.commands[i];
if (!command.marker || command.marker.line < baseY || command.commandStartLineContent === undefined) {
break;
}
const line = this._terminal.buffer.active.getLine(command.marker.line);
if (!line || line.translateToString(true) === command.commandStartLineContent) {
continue;
}
const shiftedY = command.marker.line - potentialShiftedLineCount;
const shiftedLine = this._terminal.buffer.active.getLine(shiftedY);
if (shiftedLine?.translateToString(true) !== command.commandStartLineContent) {
continue;
}
// HACK: xterm.js doesn't expose this by design as it's an internal core
// function an embedder could easily do damage with. Additionally, this
// can't really be upstreamed since the event relies on shell integration to
// verify the shifting is necessary.
(this._terminal as any)._core._bufferService.buffer.lines.onDeleteEmitter.fire({
index: this._terminal.buffer.active.baseY,
amount: potentialShiftedLineCount
});
const baseY = this._terminal.buffer.active.baseY;
const rowsDifference = e.rows - this._dimensions.rows;
// Only do when rows increase, do in the next frame as this needs to happen after
// conpty reprints the screen
if (rowsDifference > 0) {
this._waitForCursorMove().then(() => {
// Calculate the number of lines the content may have shifted, this will max out at
// scrollback count since the standard behavior will be used then
const potentialShiftedLineCount = Math.min(rowsDifference, baseY);
// For each command within the viewport, assume commands are in the correct order
for (let i = this.commands.length - 1; i >= 0; i--) {
const command = this.commands[i];
if (!command.marker || command.marker.line < baseY || command.commandStartLineContent === undefined) {
break;
}
});
}
const line = this._terminal.buffer.active.getLine(command.marker.line);
if (!line || line.translateToString(true) === command.commandStartLineContent) {
continue;
}
const shiftedY = command.marker.line - potentialShiftedLineCount;
const shiftedLine = this._terminal.buffer.active.getLine(shiftedY);
if (shiftedLine?.translateToString(true) !== command.commandStartLineContent) {
continue;
}
// HACK: xterm.js doesn't expose this by design as it's an internal core
// function an embedder could easily do damage with. Additionally, this
// can't really be upstreamed since the event relies on shell integration to
// verify the shifting is necessary.
(this._terminal as any)._core._bufferService.buffer.lines.onDeleteEmitter.fire({
index: this._terminal.buffer.active.baseY,
amount: potentialShiftedLineCount
});
}
});
}
this._dimensions.cols = e.cols;
this._dimensions.rows = e.rows;
}
private _waitForCursorMove(): Promise<void> {
@@ -202,58 +206,57 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
}
handleCommandStart(): void {
this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
// On Windows track all cursor movements after the command start sequence
if (this._isWindowsPty) {
this._commandMarkers.length = 0;
// HACK: Fire command started on the following frame on Windows to allow the cursor
// position to update as conpty often prints the sequence on a different line to the
// actual line the command started on.
timeout(0).then(() => {
if (!this._currentCommand.commandExecutedMarker) {
this._onCursorMoveListener = this._terminal.onCursorMove(() => {
if (this._commandMarkers.length === 0 || this._commandMarkers[this._commandMarkers.length - 1].line !== this._terminal.buffer.active.cursorY) {
const marker = this._terminal.registerMarker(0);
if (marker) {
this._commandMarkers.push(marker);
}
}
});
}
this._currentCommand.commandStartMarker = this._terminal.registerMarker(0);
if (this._currentCommand.commandStartMarker) {
const line = this._terminal.buffer.active.getLine(this._currentCommand.commandStartMarker.line);
if (line) {
this._currentCommand.commandStartLineContent = line.translateToString(true);
}
}
this._onCommandStarted.fire({ marker: this._currentCommand.commandStartMarker } as ITerminalCommand);
});
} else {
this._currentCommand.commandStartMarker = this._terminal.registerMarker(0);
this._onCommandStarted.fire({ marker: this._currentCommand.commandStartMarker } as ITerminalCommand);
this._handleCommandStartWindows();
return;
}
this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
this._currentCommand.commandStartMarker = this._terminal.registerMarker(0);
this._onCommandStarted.fire({ marker: this._currentCommand.commandStartMarker } as ITerminalCommand);
this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line);
}
private _handleCommandStartWindows(): void {
this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
// On Windows track all cursor movements after the command start sequence
this._commandMarkers.length = 0;
// HACK: Fire command started on the following frame on Windows to allow the cursor
// position to update as conpty often prints the sequence on a different line to the
// actual line the command started on.
timeout(0).then(() => {
if (!this._currentCommand.commandExecutedMarker) {
this._onCursorMoveListener = this._terminal.onCursorMove(() => {
if (this._commandMarkers.length === 0 || this._commandMarkers[this._commandMarkers.length - 1].line !== this._terminal.buffer.active.cursorY) {
const marker = this._terminal.registerMarker(0);
if (marker) {
this._commandMarkers.push(marker);
}
}
});
}
this._currentCommand.commandStartMarker = this._terminal.registerMarker(0);
if (this._currentCommand.commandStartMarker) {
const line = this._terminal.buffer.active.getLine(this._currentCommand.commandStartMarker.line);
if (line) {
this._currentCommand.commandStartLineContent = line.translateToString(true);
}
}
this._onCommandStarted.fire({ marker: this._currentCommand.commandStartMarker } as ITerminalCommand);
this._logService.debug('CommandDetectionCapability#_handleCommandStartWindows', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line);
});
}
handleCommandExecuted(): void {
// On Windows, use the gathered cursor move markers to correct the command start and
// executed markers
if (this._isWindowsPty) {
this._onCursorMoveListener?.dispose();
this._onCursorMoveListener = undefined;
this._handleCommandExecutedWindows();
return;
}
this._currentCommand.commandExecutedMarker = this._terminal.registerMarker(0);
this._currentCommand.commandExecutedX = this._terminal.buffer.active.cursorX;
this._logService.debug('CommandDetectionCapability#handleCommandExecuted', this._currentCommand.commandExecutedX, this._currentCommand.commandExecutedMarker?.line);
// Don't get the command on Windows, rely on the command line sequence for this
if (this._isWindowsPty) {
return;
}
// Sanity check optional props
if (!this._currentCommand.commandStartMarker || !this._currentCommand.commandExecutedMarker || this._currentCommand.commandStartX === undefined) {
return;
@@ -279,31 +282,19 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
}
}
handleCommandFinished(exitCode: number | undefined): void {
private _handleCommandExecutedWindows(): void {
// On Windows, use the gathered cursor move markers to correct the command start and
// executed markers. This is done on command finished just in case command executed never
// happens (for example PSReadLine tab completion)
// executed markers
this._onCursorMoveListener?.dispose();
this._onCursorMoveListener = undefined;
this._currentCommand.commandExecutedMarker = this._terminal.registerMarker(0);
this._currentCommand.commandExecutedX = this._terminal.buffer.active.cursorX;
this._logService.debug('CommandDetectionCapability#handleCommandExecuted', this._currentCommand.commandExecutedX, this._currentCommand.commandExecutedMarker?.line);
}
handleCommandFinished(exitCode: number | undefined): void {
if (this._isWindowsPty) {
if (this._commandMarkers.length === 0) {
// If the command start timeout doesn't happen before command finished, just use the
// current marker.
if (!this._currentCommand.commandStartMarker) {
this._currentCommand.commandStartMarker = this._terminal.registerMarker(0);
}
if (this._currentCommand.commandStartMarker) {
this._commandMarkers.push(this._currentCommand.commandStartMarker);
}
} else {
this._commandMarkers = this._commandMarkers.sort((a, b) => a.line - b.line);
}
this._currentCommand.commandStartMarker = this._commandMarkers[0];
if (this._currentCommand.commandStartMarker) {
const line = this._terminal.buffer.active.getLine(this._currentCommand.commandStartMarker.line);
if (line) {
this._currentCommand.commandStartLineContent = line.translateToString(true);
}
}
this._currentCommand.commandExecutedMarker = this._commandMarkers[this._commandMarkers.length - 1];
this._preHandleCommandFinishedWindows();
}
this._currentCommand.commandFinishedMarker = this._terminal.registerMarker(0);
@@ -352,6 +343,32 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
this._currentCommand = {};
}
private _preHandleCommandFinishedWindows(): void {
// On Windows, use the gathered cursor move markers to correct the command start and
// executed markers. This is done on command finished just in case command executed never
// happens (for example PSReadLine tab completion)
if (this._commandMarkers.length === 0) {
// If the command start timeout doesn't happen before command finished, just use the
// current marker.
if (!this._currentCommand.commandStartMarker) {
this._currentCommand.commandStartMarker = this._terminal.registerMarker(0);
}
if (this._currentCommand.commandStartMarker) {
this._commandMarkers.push(this._currentCommand.commandStartMarker);
}
} else {
this._commandMarkers = this._commandMarkers.sort((a, b) => a.line - b.line);
}
this._currentCommand.commandStartMarker = this._commandMarkers[0];
if (this._currentCommand.commandStartMarker) {
const line = this._terminal.buffer.active.getLine(this._currentCommand.commandStartMarker.line);
if (line) {
this._currentCommand.commandStartLineContent = line.translateToString(true);
}
}
this._currentCommand.commandExecutedMarker = this._commandMarkers[this._commandMarkers.length - 1];
}
setCommandLine(commandLine: string) {
this._logService.debug('CommandDetectionCapability#setCommandLine', commandLine);
this._currentCommand.command = commandLine;